@hrefcl/apidoc - v4.0.5
    Preparing search index...

    🐳 Docker & Container Usage

    Complete guide for containerizing APIDoc documentation with Docker, including production-ready configurations, multi-stage builds, and orchestration patterns.

    # Dockerfile
    FROM node:18-alpine

    # Set working directory
    WORKDIR /app

    # Copy package files
    COPY package*.json ./

    # Install dependencies
    RUN npm ci --only=production && npm cache clean --force

    # Copy source code
    COPY . .

    # Generate documentation
    RUN npx apidoc -i src/ -o docs/

    # Expose port
    EXPOSE 8080

    # Health check
    HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1

    # Start static server
    CMD ["npx", "http-server", "docs", "-p", "8080", "-c-1"]
    # Build the image
    docker build -t my-api-docs .

    # Run the container
    docker run -p 8080:8080 my-api-docs

    # Access documentation
    curl http://localhost:8080
    # Multi-stage Dockerfile for production
    FROM node:18-alpine AS builder

    # Set working directory
    WORKDIR /app

    # Install build dependencies
    RUN apk add --no-cache git

    # Copy package files
    COPY package*.json ./

    # Install all dependencies (including dev)
    RUN npm ci

    # Copy source code and configuration
    COPY src/ ./src/
    COPY apidoc.json ./
    COPY template/ ./template/
    COPY assets/ ./assets/

    # Generate documentation
    RUN npx apidoc -i src/ -o docs/ --template template/

    # Production stage
    FROM nginx:alpine AS production

    # Copy nginx configuration
    COPY docker/nginx.conf /etc/nginx/nginx.conf

    # Copy generated documentation from builder stage
    COPY --from=builder /app/docs /usr/share/nginx/html

    # Copy custom assets
    COPY --from=builder /app/assets /usr/share/nginx/html/assets

    # Create nginx user
    RUN addgroup -g 1001 -S nginx && \
    adduser -S -D -H -u 1001 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx

    # Set ownership
    RUN chown -R nginx:nginx /usr/share/nginx/html && \
    chown -R nginx:nginx /var/cache/nginx && \
    chown -R nginx:nginx /var/log/nginx && \
    chown -R nginx:nginx /etc/nginx/conf.d

    # Switch to non-root user
    USER nginx

    # Health check
    HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1

    # Expose port
    EXPOSE 8080

    # Start nginx
    CMD ["nginx", "-g", "daemon off;"]
    # docker/nginx.conf
    worker_processes auto;
    error_log /var/log/nginx/error.log warn;
    pid /tmp/nginx.pid;

    events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
    }

    http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 16M;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml+rss
    application/atom+xml
    image/svg+xml;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;

    server {
    listen 8080;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # Enable browser caching for static assets
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    }

    # API documentation
    location / {
    try_files $uri $uri/ /index.html;
    add_header X-Frame-Options "SAMEORIGIN";
    }

    # Health check endpoint
    location /health {
    access_log off;
    return 200 "healthy\n";
    add_header Content-Type text/plain;
    }
    }
    }
    # Dockerfile with authentication
    FROM node:18-alpine AS builder

    WORKDIR /app

    # Copy package files
    COPY package*.json ./
    RUN npm ci

    # Copy source and config
    COPY src/ ./src/
    COPY apidoc.json ./
    COPY template/ ./template/

    # Generate documentation with auth
    RUN npx apidoc -i src/ -o docs/ --template template/

    # Production stage with authentication server
    FROM node:18-alpine AS production

    WORKDIR /app

    # Install production dependencies
    COPY package*.json ./
    RUN npm ci --only=production

    # Copy authentication server
    COPY server/ ./server/
    COPY --from=builder /app/docs ./docs/

    # Create non-root user
    RUN addgroup -g 1001 -S appgroup && \
    adduser -S -D -H -u 1001 -h /app -s /sbin/nologin -G appgroup -g appgroup appuser

    # Set ownership
    RUN chown -R appuser:appgroup /app

    # Switch to non-root user
    USER appuser

    # Health check
    HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

    # Expose port
    EXPOSE 3000

    # Start authentication server
    CMD ["node", "server/app.js"]
    // server/app.js
    const express = require('express');
    const session = require('express-session');
    const path = require('path');
    const fs = require('fs');

    const app = express();
    const PORT = process.env.PORT || 3000;

    // Session configuration
    app.use(session({
    secret: process.env.SESSION_SECRET || 'change-this-secret',
    resave: false,
    saveUninitialized: false,
    cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: parseInt(process.env.SESSION_TIMEOUT || '3600000') // 1 hour
    }
    }));

    app.use(express.json());
    app.use(express.urlencoded({ extended: true }));

    // Authentication middleware
    function requireAuth(req, res, next) {
    if (req.session.authenticated) {
    next();
    } else {
    res.redirect('/login');
    }
    }

    // Login page
    app.get('/login', (req, res) => {
    res.send(`
    <!DOCTYPE html>
    <html>
    <head>
    <title>API Documentation - Login</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    body { font-family: Arial, sans-serif; background: #f5f5f5; }
    .container { max-width: 400px; margin: 100px auto; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
    .form-group { margin-bottom: 15px; }
    label { display: block; margin-bottom: 5px; }
    input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
    button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
    button:hover { background: #0056b3; }
    .error { color: red; margin-top: 10px; }
    </style>
    </head>
    <body>
    <div class="container">
    <h2>API Documentation Access</h2>
    <form method="POST" action="/login">
    <div class="form-group">
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required>
    </div>
    <div class="form-group">
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required>
    </div>
    <button type="submit">Login</button>
    ${req.query.error ? `<div class="error">Invalid credentials</div>` : ''}
    </form>
    </div>
    </body>
    </html>
    `);
    });

    // Login handler
    app.post('/login', (req, res) => {
    const { username, password } = req.body;

    // Simple authentication (replace with your auth logic)
    const validUsers = {
    'admin': 'admin123',
    'developer': 'dev123',
    'viewer': 'view123'
    };

    if (validUsers[username] && validUsers[username] === password) {
    req.session.authenticated = true;
    req.session.user = username;
    res.redirect('/');
    } else {
    res.redirect('/login?error=1');
    }
    });

    // Logout
    app.post('/logout', (req, res) => {
    req.session.destroy((err) => {
    res.redirect('/login');
    });
    });

    // Health check
    app.get('/health', (req, res) => {
    res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
    });

    // Serve documentation (protected)
    app.use('/', requireAuth, express.static(path.join(__dirname, '../docs')));

    app.listen(PORT, '0.0.0.0', () => {
    console.log(`Server running on http://0.0.0.0:${PORT}`);
    });
    # docker-compose.yml
    version: '3.8'

    services:
    api-docs:
    build: .
    ports:
    - "8080:8080"
    environment:
    - NODE_ENV=production
    volumes:
    - ./logs:/app/logs
    restart: unless-stopped
    healthcheck:
    test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s

    # Optional: Add reverse proxy
    nginx:
    image: nginx:alpine
    ports:
    - "80:80"
    - "443:443"
    volumes:
    - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
    - api-docs
    restart: unless-stopped
    # docker-compose.prod.yml
    version: '3.8'

    services:
    api-docs:
    build:
    context: .
    target: production
    environment:
    - NODE_ENV=production
    - SESSION_SECRET=${SESSION_SECRET}
    - AUTH_TYPE=${AUTH_TYPE}
    - AUTH_ENDPOINT=${AUTH_ENDPOINT}
    volumes:
    - ./logs:/var/log/nginx
    - ./data:/app/data:ro
    restart: unless-stopped
    healthcheck:
    test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
    interval: 30s
    timeout: 10s
    retries: 3

    nginx:
    image: nginx:alpine
    ports:
    - "80:80"
    - "443:443"
    volumes:
    - ./nginx/prod.conf:/etc/nginx/nginx.conf:ro
    - ./ssl:/etc/nginx/ssl:ro
    - ./logs/nginx:/var/log/nginx
    depends_on:
    - api-docs
    restart: unless-stopped
    healthcheck:
    test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/health"]
    interval: 30s
    timeout: 10s
    retries: 3

    networks:
    default:
    name: apidoc-network
    # docker-compose.dev.yml
    version: '3.8'

    services:
    api-docs-dev:
    build:
    context: .
    dockerfile: Dockerfile.dev
    ports:
    - "3000:3000"
    - "35729:35729" # LiveReload
    environment:
    - NODE_ENV=development
    - CHOKIDAR_USEPOLLING=true
    volumes:
    - ./src:/app/src:ro
    - ./docs:/app/docs
    - ./apidoc.json:/app/apidoc.json:ro
    - /app/node_modules
    restart: unless-stopped
    command: npm run dev
    # .github/workflows/docker.yml
    name: Build and Deploy Docker Image

    on:
    push:
    branches: [ main, develop ]
    pull_request:
    branches: [ main ]

    env:
    REGISTRY: ghcr.io
    IMAGE_NAME: ${{ github.repository }}/api-docs

    jobs:
    build:
    runs-on: ubuntu-latest
    permissions:
    contents: read
    packages: write

    steps:
    - name: Checkout repository
    uses: actions/checkout@v3

    - name: Log in to Container Registry
    uses: docker/login-action@v2
    with:
    registry: ${{ env.REGISTRY }}
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

    - name: Extract metadata
    id: meta
    uses: docker/metadata-action@v4
    with:
    images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
    tags: |
    type=ref,event=branch
    type=ref,event=pr
    type=sha

    - name: Build and push Docker image
    uses: docker/build-push-action@v4
    with:
    context: .
    push: true
    tags: ${{ steps.meta.outputs.tags }}
    labels: ${{ steps.meta.outputs.labels }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

    - name: Deploy to staging
    if: github.ref == 'refs/heads/develop'
    run: |
    echo "Deploying to staging..."
    # Add your staging deployment commands here

    - name: Deploy to production
    if: github.ref == 'refs/heads/main'
    run: |
    echo "Deploying to production..."
    # Add your production deployment commands here
    # .gitlab-ci.yml
    stages:
    - build
    - test
    - deploy

    variables:
    DOCKER_REGISTRY: $CI_REGISTRY
    DOCKER_IMAGE: $CI_REGISTRY_IMAGE
    DOCKER_TAG: $CI_COMMIT_SHORT_SHA

    build:
    stage: build
    image: docker:latest
    services:
    - docker:dind
    before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    script:
    - docker build -t $DOCKER_IMAGE:$DOCKER_TAG .
    - docker push $DOCKER_IMAGE:$DOCKER_TAG
    only:
    - main
    - develop

    test:
    stage: test
    image: $DOCKER_IMAGE:$DOCKER_TAG
    script:
    - wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1
    only:
    - main
    - develop

    deploy_staging:
    stage: deploy
    script:
    - docker pull $DOCKER_IMAGE:$DOCKER_TAG
    - docker stop api-docs-staging || true
    - docker run -d --name api-docs-staging -p 8081:8080 $DOCKER_IMAGE:$DOCKER_TAG
    environment:
    name: staging
    url: https://api-docs-staging.example.com
    only:
    - develop

    deploy_production:
    stage: deploy
    script:
    - docker pull $DOCKER_IMAGE:$DOCKER_TAG
    - docker stop api-docs-prod || true
    - docker run -d --name api-docs-prod -p 8080:8080 $DOCKER_IMAGE:$DOCKER_TAG
    environment:
    name: production
    url: https://api-docs.example.com
    only:
    - main
    when: manual
    # k8s/deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: api-docs
    labels:
    app: api-docs
    spec:
    replicas: 3
    selector:
    matchLabels:
    app: api-docs
    template:
    metadata:
    labels:
    app: api-docs
    spec:
    containers:
    - name: api-docs
    image: my-registry/api-docs:latest
    ports:
    - containerPort: 8080
    env:
    - name: NODE_ENV
    value: "production"
    - name: SESSION_SECRET
    valueFrom:
    secretKeyRef:
    name: api-docs-secrets
    key: session-secret
    resources:
    requests:
    memory: "128Mi"
    cpu: "100m"
    limits:
    memory: "256Mi"
    cpu: "200m"
    livenessProbe:
    httpGet:
    path: /health
    port: 8080
    initialDelaySeconds: 30
    periodSeconds: 10
    readinessProbe:
    httpGet:
    path: /health
    port: 8080
    initialDelaySeconds: 5
    periodSeconds: 5
    imagePullSecrets:
    - name: registry-secret

    ---
    apiVersion: v1
    kind: Service
    metadata:
    name: api-docs-service
    spec:
    selector:
    app: api-docs
    ports:
    - protocol: TCP
    port: 80
    targetPort: 8080
    type: ClusterIP

    ---
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
    name: api-docs-ingress
    annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    spec:
    tls:
    - hosts:
    - api-docs.example.com
    secretName: api-docs-tls
    rules:
    - host: api-docs.example.com
    http:
    paths:
    - path: /
    pathType: Prefix
    backend:
    service:
    name: api-docs-service
    port:
    number: 80
    # k8s/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: api-docs-config
    data:
    NODE_ENV: "production"
    AUTH_TYPE: "remote"
    AUTH_ENDPOINT: "https://auth.example.com/api/validate"

    ---
    apiVersion: v1
    kind: Secret
    metadata:
    name: api-docs-secrets
    type: Opaque
    data:
    session-secret: <base64-encoded-secret>
    api-token: <base64-encoded-token>
    # helm/values.yaml
    replicaCount: 3

    image:
    repository: my-registry/api-docs
    tag: latest
    pullPolicy: IfNotPresent

    service:
    type: ClusterIP
    port: 80

    ingress:
    enabled: true
    className: "nginx"
    annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    hosts:
    - host: api-docs.example.com
    paths:
    - path: /
    pathType: Prefix
    tls:
    - secretName: api-docs-tls
    hosts:
    - api-docs.example.com

    resources:
    limits:
    cpu: 200m
    memory: 256Mi
    requests:
    cpu: 100m
    memory: 128Mi

    autoscaling:
    enabled: true
    minReplicas: 2
    maxReplicas: 10
    targetCPUUtilizationPercentage: 80

    config:
    nodeEnv: production
    authType: remote
    authEndpoint: https://auth.example.com/api/validate

    secrets:
    sessionSecret: ""
    apiToken: ""
    # Dockerfile.multi-env
    FROM node:18-alpine AS base

    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --only=production

    # Development stage
    FROM base AS development
    RUN npm ci
    COPY . .
    EXPOSE 3000
    CMD ["npm", "run", "dev"]

    # Test stage
    FROM base AS test
    RUN npm ci
    COPY . .
    RUN npm test

    # Build stage
    FROM base AS build
    RUN npm ci
    COPY . .
    RUN npx apidoc -i src/ -o docs/

    # Production stage
    FROM nginx:alpine AS production
    COPY --from=build /app/docs /usr/share/nginx/html
    COPY nginx.conf /etc/nginx/nginx.conf
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]
    # docker-compose.volumes.yml
    version: '3.8'

    services:
    api-docs:
    build: .
    ports:
    - "8080:8080"
    volumes:
    # Mount source code for development
    - ./src:/app/src:ro
    - ./apidoc.json:/app/apidoc.json:ro

    # Mount output directory
    - docs-volume:/app/docs

    # Mount configuration
    - ./config:/app/config:ro

    # Mount logs
    - ./logs:/app/logs
    environment:
    - NODE_ENV=development
    - WATCH_FILES=true
    restart: unless-stopped

    volumes:
    docs-volume:
    driver: local
    # docker-compose.logging.yml
    version: '3.8'

    services:
    api-docs:
    build: .
    ports:
    - "8080:8080"
    logging:
    driver: "json-file"
    options:
    max-size: "10m"
    max-file: "3"
    labels:
    - "traefik.enable=true"
    - "traefik.http.routers.api-docs.rule=Host(`api-docs.example.com`)"

    # Log aggregation
    filebeat:
    image: elastic/filebeat:7.17.0
    volumes:
    - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
    - /var/lib/docker/containers:/var/lib/docker/containers:ro
    - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
    - api-docs
    // health.js
    const express = require('express');
    const fs = require('fs');
    const path = require('path');

    const app = express();

    app.get('/health', (req, res) => {
    const health = {
    uptime: process.uptime(),
    timestamp: new Date().toISOString(),
    status: 'healthy',
    checks: {}
    };

    // Check if documentation files exist
    try {
    const docsPath = path.join(__dirname, 'docs', 'index.html');
    fs.accessSync(docsPath, fs.constants.F_OK);
    health.checks.documentation = 'healthy';
    } catch (error) {
    health.checks.documentation = 'unhealthy';
    health.status = 'unhealthy';
    }

    // Check memory usage
    const memUsage = process.memoryUsage();
    health.checks.memory = {
    rss: Math.round(memUsage.rss / 1024 / 1024) + 'MB',
    heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + 'MB',
    status: memUsage.heapUsed < 200 * 1024 * 1024 ? 'healthy' : 'warning'
    };

    const statusCode = health.status === 'healthy' ? 200 : 503;
    res.status(statusCode).json(health);
    });

    app.listen(3001, () => {
    console.log('Health check server running on port 3001');
    });
    • βœ… Use non-root users in containers
    • βœ… Scan images for vulnerabilities
    • βœ… Keep base images updated
    • βœ… Use minimal base images (Alpine)
    • βœ… Set resource limits
    • βœ… Use multi-stage builds
    • βœ… Optimize image layers
    • βœ… Enable compression
    • βœ… Implement caching strategies
    • βœ… Use health checks
    • βœ… Implement proper logging
    • βœ… Monitor resource usage
    • βœ… Set up alerts
    • βœ… Regular backups
    • βœ… Document deployment procedures

    APIDoc's Docker integration provides scalable, production-ready containerization for documentation deployment across all environments.