01Introduction
02Mistake #1: Using :latest Tags in Production
1# BAD: Unpredictable2services: 3 postgres: 4 image: postgres:latest56 app: 7 image: nginx:latest89# GOOD: Pinned versions10services: 11 postgres: 12 image: postgres:15.4-alpine1314 app: 15 image: nginx:1.25.3-alpine1617# ACCEPTABLE: Major version pinning (still gets security updates)18services: 19 postgres: 20 image: postgres:15-alpineCheck image changelogs before updating. Pin at least the major version in production.
03Mistake #2: Forgetting Restart Policies
1# BAD: No restart policy2services: 3 app: 4 image: myapp:1.05 # Container stays dead after crash or reboot67# GOOD: Automatic restart8services: 9 app: 10 image: myapp:1.011 restart: unless-stopped # Restarts unless you manually stop it1213# Other options:14# restart: always # Always restart, even after manual stop15# restart: on-failure # Only restart on non-zero exit code16# restart: "no" # Never restart (default)Use unless-stopped for most services. Use on-failure for one-shot jobs or init containers.
04Mistake #3: Exposing Ports to the World
1# BAD: Database accessible from internet2services: 3 postgres: 4 image: postgres:155 ports: 6 - "5432:5432" # Binds to 0.0.0.0:543278# GOOD: Only accessible from localhost9services: 10 postgres: 11 image: postgres:1512 ports: 13 - "127.0.0.1:5432:5432"1415# BETTER: Don't expose at all, use Docker networks16services: 17 app: 18 networks: 19 - backend2021 postgres: 22 networks: 23 - backend24 # No ports exposed - only app can reach it2526networks: 27 backend: Never expose database ports publicly. Use 127.0.0.1 binding or Docker networks.
05Mistake #4: Forgetting Persistent Volumes
1# BAD: Database data lost on container recreation2services: 3 postgres: 4 image: postgres:155 environment: 6 - POSTGRES_PASSWORD=secret7 # No volume = data gone after "docker compose down"89# GOOD: Data persists10services: 11 postgres: 12 image: postgres:1513 environment: 14 - POSTGRES_PASSWORD=secret15 volumes: 16 - postgres_data:/var/lib/postgresql/data1718volumes: 19 postgres_data: Check the image's documentation for which paths need persistence. Look for VOLUME instructions in the Dockerfile.
06Mistake #5: Mounting Docker Socket Carelessly
1# RISKY: Full Docker access2services: 3 portainer: 4 image: portainer/portainer-ce5 volumes: 6 - /var/run/docker.sock:/var/run/docker.sock78# The container can:9# - Start/stop any container10# - Mount any host directory11# - Effectively run any command as root1213# SAFER: Use with trusted images only, consider alternatives14# - Use Portainer's agent mode15# - Use Docker socket proxy (tecnativa/docker-socket-proxy)16# - Limit with read-only mode (still risky)Only mount Docker socket in containers you fully trust. Consider using a socket proxy to limit accessible API endpoints.
07Mistake #6: Hardcoding Secrets
1# BAD: Secrets visible in plain text2services: 3 db: 4 image: postgres:155 environment: 6 - POSTGRES_PASSWORD=my_super_secret_password12378# GOOD: Use environment variables9services: 10 db: 11 image: postgres:1512 environment: 13 - POSTGRES_PASSWORD=${DB_PASSWORD}1415# .env file (not committed to git)16# DB_PASSWORD=my_super_secret_password12308Mistake #7: Not Understanding Docker Networking
1# Common mistake: Using localhost to reach other containers2services: 3 app: 4 image: myapp:1.05 environment: 6 # BAD: localhost means the app container itself7 - DATABASE_URL=postgres://localhost:5432/mydb89 # GOOD: Use the service name10 - DATABASE_URL=postgres://db:5432/mydb1112 db: 13 image: postgres:151415# Cross-project communication requires external networks16networks: 17 shared: 18 external: true19 name: shared_networkInside Docker, use service names as hostnames. localhost refers to the container itself, not the host or other containers.
09Mistake #8: No Resource Limits
1services: 2 app: 3 image: myapp:1.04 deploy: 5 resources: 6 limits: 7 cpus: '2'8 memory: 2G9 reservations: 10 cpus: '0.5'11 memory: 512M1213# Note: deploy.resources works with "docker compose up"14# For older compose, use:15# mem_limit: 2g16# cpus: 2Start with generous limits and tighten based on actual usage. Use docker stats to monitor resource consumption.
10Mistake #9: Not Managing Logs
1services: 2 app: 3 image: myapp:1.04 logging: 5 driver: "json-file"6 options: 7 max-size: "10m"8 max-file: "3"910# Or set globally in /etc/docker/daemon.json11# {12# "log-driver": "json-file",13# "log-opts": {14# "max-size": "10m",15# "max-file": "3"16# }17# }11Mistake #10: Running as Root
1# BETTER: Run as non-root user2services: 3 app: 4 image: myapp:1.05 user: "1000:1000" # Run as UID/GID 100067 # Many images support PUID/PGID8 linuxserver-app: 9 image: linuxserver/sonarr10 environment: 11 - PUID=100012 - PGID=10001314 # Check if image supports non-root15 # docker run --rm myapp:1.0 id16 # If it shows "uid=0(root)", the image runs as root by defaultPrefer images that run as non-root by default. The linuxserver.io images are excellent for this.