docker-compose is more of a development and testing tool than a production deployment system, and going live with it without understanding its limitations is a common pitfall.

Here’s what you need to fix before docker-compose up becomes docker-compose live:

1. Networking: Port Conflicts and Unpredictable IP Addresses

docker-compose creates a default bridge network for your services. While convenient, this network uses ephemeral IP addresses that can change, and ports are mapped directly to the host.

  • Diagnosis: Before deploying, run docker network ls to see your networks. Then, docker network inspect <network_name> (often projectname_default) to see the IP ranges. Check docker ps for existing port mappings on your host that might conflict.
  • Fix: Define a static IP address for your docker-compose network and assign static IPs to your services.
    version: '3.8'
    services:
      web:
        image: nginx
        ports:
          - "80:80"
        networks:
          app_net:
            ipv4_address: 172.20.0.10
      db:
        image: postgres
        networks:
          app_net:
            ipv4_address: 172.20.0.11
    networks:
      app_net:
        driver: bridge
        ipam:
          config:
            - subnet: 172.20.0.0/24
    
    This assigns fixed IPs to your services within the app_net network, preventing internal communication issues.
  • Why it works: Static IPs ensure that service discovery within your docker-compose setup is reliable, and docker ps will show your intended host port mappings rather than dynamic ones.

2. Health Checks: Ignoring Service Readiness

By default, docker-compose up starts all services and considers them "up" even if their applications are not yet ready to accept connections.

  • Diagnosis: Observe your application logs. You’ll likely see requests failing with connection refused errors immediately after docker-compose up completes, because the database or API service isn’t fully initialized.
  • Fix: Implement healthcheck directives in your docker-compose.yml and use depends_on with condition: service_healthy.
    version: '3.8'
    services:
      web:
        image: myapp
        depends_on:
          db:
            condition: service_healthy
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
          interval: 30s
          timeout: 10s
          retries: 5
      db:
        image: postgres
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U postgres"]
          interval: 10s
          timeout: 5s
          retries: 5
    
  • Why it works: depends_on with service_healthy ensures that dependent services are not only started but are also reported as healthy by their healthcheck before the dependent service proceeds to start.

3. Restart Policies: No Automatic Recovery

If a container crashes or the Docker daemon restarts, your services will remain stopped unless you manually restart them.

  • Diagnosis: Manually stop a container (docker-compose stop <service_name>) and observe that it doesn’t come back up automatically. Or, simulate a crash by killing a process inside a container.
  • Fix: Set restart: always or restart: unless-stopped for each service.
    version: '3.8'
    services:
      web:
        image: nginx
        restart: always
      db:
        image: postgres
        restart: unless-stopped
    
  • Why it works: restart: always will restart the container whenever it exits, regardless of the exit code. restart: unless-stopped is similar but won’t restart if the container was explicitly stopped by the user.

4. Logging: Inadequate Storage and Rotation

Docker’s default logging driver (json-file) can lead to log files growing indefinitely, consuming disk space and impacting performance.

  • Diagnosis: Check disk usage on your Docker host. If you see large *.log files in /var/lib/docker/containers/<container_id>/, this is your culprit.
  • Fix: Configure a more robust logging driver like syslog, journald, or an external logging service, and set size/rotation limits.
    version: '3.8'
    services:
      web:
        image: nginx
        logging:
          driver: "json-file"
          options:
            max-size: "10m"
            max-file: "3"
      db:
        image: postgres
        logging:
          driver: "syslog"
          options:
            syslog-address: "udp://127.0.0.1:514"
    
  • Why it works: json-file with max-size and max-file limits will automatically rotate logs, preventing unbounded growth. syslog or journald offload logging to a dedicated system.

5. Volumes: Data Persistence and Backup Strategies

docker-compose uses named volumes or bind mounts for data persistence. Without a clear strategy, data can be lost if containers are removed or the host fails.

  • Diagnosis: Run docker volume ls to see your named volumes. Run docker inspect <volume_name> to find their locations on the host (usually under /var/lib/docker/volumes/). Understand that these are just directories on the host, not managed backup solutions.
  • Fix: Implement a robust backup strategy for your volume data. This might involve:
    • Scheduled Backups: Using docker exec to run pg_dump or mysqldump from within a running container to a mounted backup volume or an external location.
    • Volume Snapshotting: If your storage backend supports it, snapshotting the underlying volume storage.
    • Dedicated Backup Container: A separate container whose sole purpose is to back up data from other volumes.
    # Example using a backup container for PostgreSQL
    version: '3.8'
    services:
      db:
        image: postgres
        volumes:
          - db_data:/var/lib/postgresql/data
      backup:
        image: postgres:13
        volumes:
          - db_data:/var/lib/postgresql/data
        command: >
          /bin/sh -c "while :; do
            pg_dump -Fc -U postgres mydatabase > /backups/mydatabase_$(date +%Y-%m-%d_%H-%M-%S).dump
            sleep 1d
          done"
        volumes:
          - ./backups:/backups # Local directory for backups
    volumes:
      db_data:
    
  • Why it works: Explicitly managing data backups ensures that critical information is preserved independently of the container lifecycle or host integrity.

6. Secrets Management: Hardcoded Credentials

Storing sensitive information like database passwords or API keys directly in docker-compose.yml or environment variables is a major security risk.

  • Diagnosis: Search your docker-compose.yml and any associated .env files for sensitive strings.
  • Fix: Use Docker Secrets (if using Swarm or Kubernetes) or integrate with a dedicated secrets management tool (like HashiCorp Vault, AWS Secrets Manager, or Kubernetes Secrets). For simpler docker-compose setups, environment files (.env) are better than inline variables, but still not ideal for production.
    # docker-compose.yml
    version: '3.8'
    services:
      db:
        image: postgres
        environment:
          POSTGRES_PASSWORD_FILE: /run/secrets/db_password
        secrets:
          - db_password
    
    secrets:
      db_password:
        file: ./secrets/db_password.txt # This file should NOT be committed to VCS
    
  • Why it works: Docker Secrets mounts sensitive data into containers as temporary files in /run/secrets/, which are automatically removed when the container stops. This keeps secrets out of your compose file and code.

The next error you’ll likely encounter is related to resource exhaustion or performance bottlenecks, as docker-compose itself doesn’t inherently provide robust resource limiting or scaling.

Want structured learning?

Take the full Docker course →