Docker Compose Complete Guide
Docker Compose defines and runs multi-container applications using a single YAML file. Instead of writing long docker run commands with many flags, you define your entire application stack in docker-compose.yml and bring it up with docker compose up. This makes the application configuration reproducible, version-controllable, and portable between environments. Compose v2 (the modern version) is included with Docker CE as a plugin.
Why Docker Compose?
Without Compose (maintaining multiple docker run commands):
docker run -d --name db -e MYSQL_ROOT_PASSWORD=secret -v db-data:/var/lib/mysql mysql:8.0
docker run -d --name app --link db:db -e DB_HOST=db -p 8080:8080 myapp:latest
# Manual dependency, manual network, manual volumes
With Compose (one file, one command):
docker compose up -d
# Auto-creates network, volumes, starts in correct orderCompose file structure
nano docker-compose.yml
docker-compose.yml — full web application example
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD: "${DB_PASSWORD}"
volumes:
- db-data:/var/lib/mysql
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3
app:
image: myapp:latest
environment:
DB_HOST: db
DB_NAME: appdb
DB_USER: appuser
DB_PASS: "${DB_PASSWORD}"
ports:
- "8080:8080"
depends_on:
db:
condition: service_healthy # Wait until DB passes healthcheck
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
depends_on:
- app
restart: unless-stopped
volumes:
db-data: # Named volume managed by Docker
Essential Compose commands
# Start all services in detached mode:
docker compose up -d
# Stop all services:
docker compose down # Stops and removes containers
docker compose down -v # Also removes named volumes (DATA LOSS!)
# View logs:
docker compose logs # All services
docker compose logs -f app # Follow specific service
# Restart a single service:
docker compose restart app
# Pull latest images and recreate containers:
docker compose pull && docker compose up -d
# Run a command in a service container:
docker compose exec app bash
docker compose exec db mysql -u root -p
Production-ready Compose patterns
# Use .env file for secrets (never hardcode passwords in compose file):
cat .env
# DB_ROOT_PASSWORD=StrongRootPass!42
# DB_PASSWORD=AppPass!42
# .env is referenced automatically by docker compose
# Add .env to .gitignore:
echo ".env" >> .gitignore
# Use override files for dev vs production:
# docker-compose.yml — base config
# docker-compose.override.yml — dev overrides (auto-loaded)
# docker-compose.prod.yml — production overrides
# Production deployment:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Conclusion
Every multi-container application should have a docker-compose.yml. Use the depends_on with condition: service_healthy to ensure containers start in the right order after health checks pass, not just when the container starts. Never put passwords directly in the compose file — use environment variables from a .env file, which stays on the server and out of version control. The restart: unless-stopped policy ensures containers automatically restart after a server reboot.
Need help with Ubuntu administration?
Work directly with Muhammad Irfan Aslam for Ubuntu Server, Linux, cloud, Docker, DevOps, CI/CD, or infrastructure troubleshooting support.
Work with Muhammad Irfan Aslam