Part 3 of 10

    Docker Compose Generation

    Generate production-ready Docker Compose files, configure multi-service stacks with health checks, and debug container issues—all through natural conversation on your RamNode VPS.

    Docker Compose
    Health Checks
    Traefik Integration

    In Parts 1 and 2, we covered server hardening and OpenStack infrastructure. Now we're diving into what most self-hosters spend the majority of their time on: deploying containerized applications.

    Docker Compose files look simple until you need health checks, proper networking, volume permissions, restart policies, and reverse proxy integration all working together. Claude Code transforms this from documentation-hunting into conversation.

    1

    Prerequisites

    • Claude Code installed (see Part 1)
    • Docker and Docker Compose installed on your VPS
    • Basic familiarity with containers

    Quick Docker Installation

    Install Docker
    # Install Docker
    curl -fsSL https://get.docker.com | sh
    sudo usermod -aG docker $USER
    
    # Log out and back in, then verify
    docker run hello-world
    
    # Docker Compose is included with modern Docker
    docker compose version
    2

    The Compose Generation Workflow

    The pattern is straightforward:

    1. Describe your stack in plain English
    2. Claude Code generates the compose file
    3. Review, refine, deploy

    Let's work through progressively complex examples.

    3

    Example 1: Simple Single-Service Deployment

    Start with something basic to see the workflow:

    "Generate a Docker Compose file for Uptime Kuma (monitoring tool) with:

    • • Persistent data volume
    • • Restart unless stopped
    • • Exposed on port 3001
    • • Health check"
    docker-compose.yml
    services:
      uptime-kuma:
        image: louislam/uptime-kuma:1
        container_name: uptime-kuma
        restart: unless-stopped
        ports:
          - "3001:3001"
        volumes:
          - uptime-kuma-data:/app/data
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:3001/"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 40s
    
    volumes:
      uptime-kuma-data:

    Simple, but notice Claude Code included a proper health check—something often forgotten in quick deployments.

    4

    Example 2: Multi-Service Stack with Networking

    Now let's deploy something real—a complete Nextcloud setup:

    "Create a production-ready Docker Compose for Nextcloud with:

    • • Nextcloud (latest stable, Apache variant)
    • • MariaDB for database
    • • Redis for caching
    • • All services on an internal network
    • • Health checks on all services
    • • Nextcloud waits for MariaDB and Redis to be healthy
    • • Proper environment for reverse proxy"
    docker-compose.yml
    version: '3.8'
    
    services:
      nextcloud:
        image: nextcloud:stable-apache
        container_name: nextcloud
        restart: unless-stopped
        ports:
          - "8080:80"
        environment:
          - MYSQL_HOST=mariadb
          - MYSQL_DATABASE=${MYSQL_DATABASE:-nextcloud}
          - MYSQL_USER=${MYSQL_USER:-nextcloud}
          - MYSQL_PASSWORD=${MYSQL_PASSWORD:?Database password required}
          - REDIS_HOST=redis
          - TRUSTED_PROXIES=172.16.0.0/12
          - OVERWRITEPROTOCOL=https
        volumes:
          - nextcloud-html:/var/www/html
          - nextcloud-data:/var/www/html/data
        networks:
          - nextcloud-internal
        depends_on:
          mariadb:
            condition: service_healthy
          redis:
            condition: service_healthy
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost/status.php"]
          interval: 30s
          timeout: 10s
          retries: 5
          start_period: 120s
    
      mariadb:
        image: mariadb:11
        container_name: nextcloud-db
        restart: unless-stopped
        environment:
          - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:?Root password required}
          - MYSQL_DATABASE=${MYSQL_DATABASE:-nextcloud}
          - MYSQL_USER=${MYSQL_USER:-nextcloud}
          - MYSQL_PASSWORD=${MYSQL_PASSWORD:?Database password required}
        volumes:
          - mariadb-data:/var/lib/mysql
        networks:
          - nextcloud-internal
        command: >
          --transaction-isolation=READ-COMMITTED
          --log-bin=binlog
          --binlog-format=ROW
        healthcheck:
          test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
          interval: 10s
          timeout: 5s
          retries: 5
          start_period: 30s
    
      redis:
        image: redis:7-alpine
        container_name: nextcloud-redis
        restart: unless-stopped
        command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
        volumes:
          - redis-data:/data
        networks:
          - nextcloud-internal
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 10s
          timeout: 5s
          retries: 5
    
    networks:
      nextcloud-internal:
        driver: bridge
    
    volumes:
      nextcloud-html:
      nextcloud-data:
      mariadb-data:
      redis-data:

    Notice what Claude Code included:

    • MariaDB tuned for Nextcloud (transaction isolation, binlog format)
    • Redis configured with memory limits and eviction policy
    • Proper health check dependencies (Nextcloud waits for healthy DB/Redis)
    • Environment variable validation with ${VAR:?error message} syntax
    5

    Example 3: Complete Stack with Traefik

    Add automatic SSL and routing with Traefik:

    "Add Traefik as a reverse proxy to the Nextcloud stack with:

    • • Let's Encrypt SSL certificates (HTTP challenge)
    • • Dashboard on traefik.mydomain.com with basic auth
    • • HTTP to HTTPS redirect
    • • Update Nextcloud service with Traefik labels"
    Traefik service (added to compose)
    traefik:
      image: traefik:v3.0
      container_name: traefik
      restart: unless-stopped
      command:
        - --api.dashboard=true
        - --providers.docker=true
        - --providers.docker.exposedbydefault=false
        - --providers.docker.network=traefik-public
        - --entrypoints.web.address=:80
        - --entrypoints.websecure.address=:443
        - --entrypoints.web.http.redirections.entryPoint.to=websecure
        - --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}
        - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
        - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
      ports:
        - 80:80
        - 443:443
      volumes:
        - /var/run/docker.sock:/var/run/docker.sock:ro
        - traefik-certs:/letsencrypt
      networks:
        - traefik-public
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)"
        - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
        - "traefik.http.routers.dashboard.service=api@internal"
        - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
        - "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}"
      healthcheck:
        test: ["CMD", "traefik", "healthcheck"]
        interval: 30s
        timeout: 10s
        retries: 3

    Nextcloud Traefik Labels

    Labels for Nextcloud service
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nextcloud.rule=Host(`${NEXTCLOUD_HOST}`)"
      - "traefik.http.routers.nextcloud.entrypoints=websecure"
      - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
      - "traefik.http.services.nextcloud.loadbalancer.server.port=80"
      # Nextcloud-specific headers
      - "traefik.http.routers.nextcloud.middlewares=nextcloud-headers,nextcloud-caldav"
      - "traefik.http.middlewares.nextcloud-headers.headers.stsSeconds=31536000"
      - "traefik.http.middlewares.nextcloud-caldav.redirectregex.permanent=true"
      - "traefik.http.middlewares.nextcloud-caldav.redirectregex.regex=^https://(.*)/.well-known/ca(l|rd)dav"
      - "traefik.http.middlewares.nextcloud-caldav.redirectregex.replacement=https://${1}/remote.php/dav/"
    6

    Example 4: Development Stack Generation

    Claude Code isn't just for production. Generate development environments instantly:

    "Create a local development stack for a Node.js application with:

    • • Node.js 20 with hot reload
    • • PostgreSQL 16 with initial schema from ./init.sql
    • • Redis for sessions
    • • Mailpit for email testing
    • • pgAdmin for database management"
    docker-compose.dev.yml
    services:
      app:
        image: node:20-alpine
        container_name: dev-app
        working_dir: /app
        command: npm run dev
        ports:
          - "3000:3000"
        environment:
          - NODE_ENV=development
          - DATABASE_URL=postgresql://${POSTGRES_USER:-dev}:${POSTGRES_PASSWORD:-devpass}@postgres:5432/${POSTGRES_DB:-devdb}
          - REDIS_URL=redis://redis:6379
          - SMTP_HOST=mailpit
          - SMTP_PORT=1025
        volumes:
          - ./:/app
          - /app/node_modules  # Preserve node_modules in container
        networks:
          - dev-network
        depends_on:
          postgres:
            condition: service_healthy
          redis:
            condition: service_healthy
    
      postgres:
        image: postgres:16-alpine
        container_name: dev-postgres
        environment:
          - POSTGRES_USER=${POSTGRES_USER:-dev}
          - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-devpass}
          - POSTGRES_DB=${POSTGRES_DB:-devdb}
        ports:
          - "5432:5432"
        volumes:
          - postgres-data:/var/lib/postgresql/data
          - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
        networks:
          - dev-network
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-dev}"]
          interval: 5s
          timeout: 5s
          retries: 5
    
      redis:
        image: redis:7-alpine
        container_name: dev-redis
        ports:
          - "6379:6379"
        networks:
          - dev-network
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 5s
          timeout: 5s
          retries: 5
    
      pgadmin:
        image: dpage/pgadmin4:latest
        container_name: dev-pgadmin
        environment:
          - PGADMIN_DEFAULT_EMAIL=admin@local.dev
          - PGADMIN_DEFAULT_PASSWORD=admin
          - PGADMIN_CONFIG_SERVER_MODE=False
        ports:
          - "5050:80"
        networks:
          - dev-network
    
      mailpit:
        image: axllent/mailpit:latest
        container_name: dev-mailpit
        ports:
          - "8025:8025"  # Web UI
          - "1025:1025"  # SMTP
        networks:
          - dev-network
    
    networks:
      dev-network:
        driver: bridge
    
    volumes:
      postgres-data:
    7

    Debugging Container Issues

    When things go wrong, Claude Code helps diagnose problems:

    "My Nextcloud container keeps restarting. Here's the output of docker logs nextcloud:

    [paste logs]

    And docker inspect nextcloud:
    [paste relevant sections]

    What's wrong?"

    Claude Code analyzes the logs, identifies the issue (permissions, missing dependencies, configuration errors), and provides targeted fixes.

    Common Issues Claude Code Can Diagnose

    SymptomTypical Causes
    Container restart loopHealth check failure, missing dependencies, permission errors
    Network connectivityWrong network, DNS resolution, firewall rules
    Volume permissionsUID/GID mismatch, SELinux contexts
    Memory issuesOOM kills, no swap, misconfigured limits
    Slow startupMissing health check dependencies, race conditions
    9

    Tips for Better Compose Generation

    • Specify versions. "MariaDB 11" is better than "MariaDB" to avoid breaking changes.
    • Describe the networking model. "Internal network for database, public network for Traefik" prevents confusion.
    • Mention your reverse proxy. Traefik labels differ from Caddy or nginx-proxy configurations.
    • Include operational requirements. Backups, logging, monitoring, and resource limits are easy to add in the initial generation.

    Quick Reference: Compose Prompts

    NeedPrompt Pattern
    New service"Add [service] with [requirements] to the stack"
    SSL/Routing"Add Traefik labels for [domain] with Let's Encrypt"
    Database"Use [PostgreSQL/MariaDB] with persistent storage and health checks"
    Development"Create a dev compose with [services] and hot reload"
    Debugging"Container [name] shows [error]. Here are the logs: [logs]"
    Migration"Convert this docker run command to compose: [command]"

    What's Next

    You can now generate complete container deployments through conversation. In Part 4, we'll cover Automated Server Monitoring & Alerting Pipelines—building observability stacks from scratch with Prometheus, Grafana, and custom alerting.

    Continue to Part 4