What is Docker Compose and how do I use it for multi-container applications?
Docker Compose is a tool for defining and running multi-container Docker applications using a single YAML configuration file (docker-compose.yml). One command deploys your entire application stack — web server, database, cache, queue — with correct networking and dependency order.
DETAILED EXPLANATION:
In production applications, you rarely run a single container. A typical web application needs: web server (Nginx), application (PHP/Node.js), database (MySQL), cache (Redis), queue (RabbitMQ). Manually running docker run commands for each is error-prone and hard to reproduce. Compose declares the entire stack as code.
Compose V2 (current) is built into the Docker CLI: docker compose (vs old docker-compose).
WHEN TO USE:
- Local development environment setup (share docker-compose.yml with team)
- Single-server production deployments
- Staging environments that mirror production
- Any application with multiple services
STEP-BY-STEP — Production WordPress + MySQL + Redis + Nginx:
# docker-compose.yml
version: "3.9"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certbot/conf:/etc/letsencrypt
- wordpress_files:/var/www/html
depends_on:
- wordpress
wordpress:
image: wordpress:php8.1-fpm
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_NAME: wpdb
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_CONFIG_EXTRA: |
define( "WP_REDIS_HOST", "redis" );
volumes:
- wordpress_files:/var/www/html
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: wpdb
MYSQL_USER: wpuser
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
command: --innodb-buffer-pool-size=256M
redis:
image: redis:alpine
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
volumes:
wordpress_files:
mysql_data:
redis_data:
# Deploy
docker compose up -d
# View logs
docker compose logs -f
# Restart single service
docker compose restart wordpress
# Update (pull new image + recreate container)
docker compose pull && docker compose up -d
FLOW:
[ docker-compose.yml ] → docker compose up → [ Nginx (port 80/443) ] → [ WordPress FPM ] → [ MySQL ] + [ Redis Cache ]
KEY POINTS:
- Store sensitive values in .env file (not in docker-compose.yml)
- Named volumes persist data across container recreations
- health checks ensure dependencies are ready before dependent services start
- Compose is not for multi-server clusters (use Kubernetes or Docker Swarm)
COMMON MISTAKES:
- Hardcoding passwords in docker-compose.yml (commit to git = exposed)
- Not defining resource limits (use deploy.resources.limits)
- Binding database port to 0.0.0.0 on production server (security risk)
QUICK FIX:
Services not communicating → Verify both in same network. Docker Compose auto-creates network, services communicate by service name as hostname
DIFFICULTY: Intermediate
RELATED: Docker, Kubernetes, VPS Hosting, CI/CD Pipelines
DETAILED EXPLANATION:
In production applications, you rarely run a single container. A typical web application needs: web server (Nginx), application (PHP/Node.js), database (MySQL), cache (Redis), queue (RabbitMQ). Manually running docker run commands for each is error-prone and hard to reproduce. Compose declares the entire stack as code.
Compose V2 (current) is built into the Docker CLI: docker compose (vs old docker-compose).
WHEN TO USE:
- Local development environment setup (share docker-compose.yml with team)
- Single-server production deployments
- Staging environments that mirror production
- Any application with multiple services
STEP-BY-STEP — Production WordPress + MySQL + Redis + Nginx:
# docker-compose.yml
version: "3.9"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certbot/conf:/etc/letsencrypt
- wordpress_files:/var/www/html
depends_on:
- wordpress
wordpress:
image: wordpress:php8.1-fpm
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_NAME: wpdb
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_CONFIG_EXTRA: |
define( "WP_REDIS_HOST", "redis" );
volumes:
- wordpress_files:/var/www/html
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: wpdb
MYSQL_USER: wpuser
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
command: --innodb-buffer-pool-size=256M
redis:
image: redis:alpine
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
volumes:
wordpress_files:
mysql_data:
redis_data:
# Deploy
docker compose up -d
# View logs
docker compose logs -f
# Restart single service
docker compose restart wordpress
# Update (pull new image + recreate container)
docker compose pull && docker compose up -d
FLOW:
[ docker-compose.yml ] → docker compose up → [ Nginx (port 80/443) ] → [ WordPress FPM ] → [ MySQL ] + [ Redis Cache ]
KEY POINTS:
- Store sensitive values in .env file (not in docker-compose.yml)
- Named volumes persist data across container recreations
- health checks ensure dependencies are ready before dependent services start
- Compose is not for multi-server clusters (use Kubernetes or Docker Swarm)
COMMON MISTAKES:
- Hardcoding passwords in docker-compose.yml (commit to git = exposed)
- Not defining resource limits (use deploy.resources.limits)
- Binding database port to 0.0.0.0 on production server (security risk)
QUICK FIX:
Services not communicating → Verify both in same network. Docker Compose auto-creates network, services communicate by service name as hostname
DIFFICULTY: Intermediate
RELATED: Docker, Kubernetes, VPS Hosting, CI/CD Pipelines