Production Docker Compose: Best Practices, Networking & Secrets

Docker Compose has evolved from a local developer tool into a battle-tested container orchestration standard for single-node production servers. While Kubernetes commands enterprise multi-datacenter clusters, Docker Compose offers remarkable simplicity, minimal resource overhead, and instant reproducibility when deploying multi-container stacks on a high-performance Linux VPS.

However, running Docker Compose in production requires fundamentally different patterns than local development. Storing credentials in plain text .env files, running containers as root, omitting resource limits, and relying on default bridge networks create severe stability and security hazards. This comprehensive masterclass covers battle-tested production Docker Compose architectures, secure secret provisioning, isolated networking topologies, health-check automation, and automated container lifecycle management.

1. The Production Docker Compose Architecture

In production, an orchestration stack must guarantee three operational tenets: zero unintended port exposure, automated container restarts during system reboots or runtime panics, and deterministic dependency startup ordering. Below is an enterprise reference architecture deploying a hardened Node.js/Python web application paired with Redis caching and a MariaDB database:

services:
  app:
    image: ghcr.io/organization/core-app:v2.4.1
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1.50'
          memory: 1536M
        reservations:
          cpus: '0.50'
          memory: 512M
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp:rw,noexec,nosuid,size=64m
    networks:
      - frontend_net
      - backend_net
    secrets:
      - db_password
      - app_api_key
    environment:
      - NODE_ENV=production
      - DB_HOST=db
      - DB_NAME=production_db
      - DB_USER=app_user
      - DB_PASSWORD_FILE=/run/secrets/db_password
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 20s

  redis:
    image: redis:7.2-alpine
    restart: unless-stopped
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--maxmemory", "512mb", "--maxmemory-policy", "allkeys-lru"]
    networks:
      - backend_net
    volumes:
      - redis_data:/data
    deploy:
      resources:
        limits:
          memory: 600M

  db:
    image: mariadb:11.4
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
      MARIADB_DATABASE: production_db
      MARIADB_USER: app_user
      MARIADB_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_root_password
      - db_password
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - backend_net
    healthcheck:
      test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-u", "root", "-p$$(cat /run/secrets/db_root_password)"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

networks:
  frontend_net:
    driver: bridge
    internal: false
  backend_net:
    driver: bridge
    internal: true

secrets:
  db_password:
    file: ./secrets/db_password.txt
  db_root_password:
    file: ./secrets/db_root_password.txt
  app_api_key:
    file: ./secrets/app_api_key.txt

volumes:
  db_data:
  redis_data:

2. Advanced Multi-Tier Network Segmentation

One of the most dangerous container misconfigurations is exposing backend database ports directly to the public host interface using ports: ["3306:3306"]. If your VPS firewall experiences a temporary policy flush during a Docker daemon restart, your database becomes exposed to automated brute-force attacks across the public internet.

In our reference configuration above, notice the dual network architecture:

  • frontend_net: Connects the application container to the public ingress proxy (such as Nginx, Traefik, or Caddy). The database and cache never join this network.
  • backend_net with internal: true: This flag commands Docker’s underlying iptables rules to drop all external packet routing. Containers within backend_net can communicate with each other over DNS service names, but they cannot route packets to the public internet, completely preventing accidental database telemetry leaks or unauthorized remote ingress.

3. Production Secret Management vs .env Files

While .env files are convenient for defining non-sensitive configuration keys like port numbers, logging levels, and hostnames, storing cryptographic tokens or database passwords in environment variables exposes them to serious security vectors:

  1. Any sub-process or third-party dependency inside the container can inspect the global environment via process.env or /proc/1/environ.
  2. Container inspection commands like docker inspect <container_id> expose all environment variables in plain JSON text to any user with Docker group privileges.
  3. Application error trackers (such as Sentry or Datadog) frequently log full environment variables during uncaught runtime exceptions.

Docker Compose Secrets solve this problem by mounting individual secret values as read-only virtual files located at /run/secrets/<secret_name> inside container memory (using in-memory tmpfs). Modern software frameworks natively support reading passwords directly from file paths (e.g., MARIADB_PASSWORD_FILE). For custom applications, implement a simple startup routine that inspects _FILE environment suffixes and loads the file content securely into local memory.

4. Enforcing CPU, Memory Limits & Health Checks

In an unconstrained Docker environment, a memory leak in a single Node.js worker or Python process can consume 100% of host RAM. When physical RAM is exhausted, the Linux kernel Out-Of-Memory (OOM) killer indiscriminately terminates random processes, frequently crashing your SSH daemon, Nginx reverse proxy, or critical database instances.

To ensure deterministic stability, always enforce strict memory limits and CPU constraints under the deploy.resources block:

  • limits.memory: The hard boundary. If a container exceeds this threshold, Docker terminates only that specific container and restarts it cleanly.
  • reservations.memory: The baseline memory guarantee guaranteed to the container upon instantiation.
  • security_opt: no-new-privileges:true: Blocks privilege escalation vulnerabilities inside child processes (e.g., executing setuid binaries).
  • read_only: true: Mounts the container root filesystem as immutable read-only, restricting all writable operations exclusively to designated tmpfs directories or persistent volumes.

Automating Zero-Downtime Rolling Restarts

When updating container images in production, avoid running docker compose down && docker compose up -d. This causes explicit service downtime. Instead, pull new images and perform in-place replacements using docker compose pull && docker compose up -d --no-deps --remove-orphans app. When combined with health checks and an external reverse proxy, traffic transitions smoothly without dropping active TCP connections.

5. Logging Optimization and Log Rotation

By default, Docker captures all stdout and stderr output in JSON-formatted log files located in /var/lib/docker/containers/. In high-traffic production environments, unmanaged Docker logs will silently consume tens of gigabytes of disk space, eventually locking your filesystem and corrupting databases.

To establish globally enforced log limits, configure /etc/docker/daemon.json before deploying your Compose stacks:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "5"
  }
}

Restart the Docker daemon via sudo systemctl restart docker to ensure every running container is automatically capped at a maximum of 100MB of historical log archives.

Deploy Docker Infrastructure on High-Performance VPS

Looking for bare-metal performance, NVMe storage speeds, and dedicated CPU cores for your containerized microservices? Experience complete root control, automated snapshots, and ultra-low latency with CpanelFree hosting solutions.

Explore High-Performance VPS Plans →

Leave a Comment