Docker Compose Basics
Docker Compose is a tool for defining and running multi-container Docker applications. With a single YAML file, you can configure all your application's services, networks, and volumes. This guide covers everything you need to get started with Docker Compose.
Docker Compose is a tool that allows you to define and run multi-container Docker applications. Instead of running multiple docker run commands with complex networking and volume configurations, you define everything in a single YAML file called docker-compose.yml. With one command, Compose starts all your services, connects them on a shared network, and manages their lifecycle together.
Compose is ideal for development, testing, and staging environments, as well as simple production deployments. It's the standard way to orchestrate multiple containers on a single host. Every service defined in the compose file runs as a separate container.
docker compose version (modern) or docker-compose --version (legacy).
# Docker Desktop (macOS/Windows)
# Compose is included automatically
# Linux (Ubuntu/Debian) - install via apt
sudo apt update
sudo apt install docker-compose-plugin
# Or manually install latest version
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Verify installation
docker compose version
# or legacy
docker-compose --version
A Docker Compose file has three main sections: services (define your containers), networks (define custom networks), and volumes (define persistent storage). The services section is the only required section, but networks and volumes are commonly used.
# docker-compose.yml - Basic structure
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
networks:
- app-network
app:
build: ./app
environment:
- NODE_ENV=production
depends_on:
- database
networks:
- app-network
database:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
postgres_data:
Each service represents a container. You can define services using existing images (with image) or build from a Dockerfile (with build). Services can have ports, volumes, environment variables, dependencies, and more.
services:
# Using an existing image
web:
image: nginx:alpine
ports:
- "80:80"
restart: always
# Building from a Dockerfile
app:
build:
context: ./app
dockerfile: Dockerfile.prod
args:
NODE_VERSION: 18
image: myapp:latest
container_name: myapp-container
working_dir: /app
user: node
# With environment variables
api:
image: myapi:latest
environment:
- DATABASE_URL=postgresql://db:5432/mydb
- API_KEY=secret123
env_file:
- .env.production
# With command override
worker:
image: myapp:latest
command: npm run worker
entrypoint: /bin/sh -c "wait-for-it db:5432 && npm run worker"
Compose automatically creates a default network for all services. Services can reach each other by their service name (e.g., database resolves to the database container's IP). You can also define custom networks for more control over isolation.
services:
frontend:
image: nginx
networks:
- frontend-net
- backend-net
backend:
image: node:18
networks:
- backend-net
# Cannot access frontend-net (isolated)
database:
image: postgres
networks:
- backend-net
networks:
frontend-net:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.1
backend-net:
driver: bridge
external: true # Use existing network
database:5432.
Volumes persist data beyond container lifecycles. You can use named volumes (managed by Docker) or bind mounts (host directories). Named volumes are recommended for production data.
services:
postgres:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data # Named volume
- ./backups:/backups:ro # Bind mount (read-only)
- type: tmpfs
target: /dev/shm
tmpfs:
size: 100M
redis:
image: redis:alpine
volumes:
- redis_data:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf:ro
volumes:
postgres_data:
driver: local
driver_opts:
type: none
device: /mnt/ssd/postgres
o: bind
redis_data:
external: true # Use existing volume
# docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
networks:
- webnet
app:
build: .
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
networks:
- webnet
- backend
db:
image: postgres:15
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=secret
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
redis:
image: redis:alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- backend
volumes:
postgres_data:
redis_data:
networks:
webnet:
backend:
internal: true # No external access to backend network
# Start all services (detached mode)
docker compose up -d
# Build images before starting
docker compose up -d --build
# View logs
docker compose logs
docker compose logs -f web # Follow logs for web service
# List running services
docker compose ps
# Stop all services
docker compose down
# Stop and remove volumes (caution: deletes data)
docker compose down -v
# Run one-off command
docker compose run app npm test
# Execute command in running container
docker compose exec app bash
# Build images without starting
docker compose build
# Pull latest images
docker compose pull
# Restart services
docker compose restart web
# View resource usage
docker compose top
Compose supports environment variables in three ways: directly in the YAML file, from an env_file, or from the shell environment. This makes it easy to configure different environments (dev, staging, prod).
# .env file (auto-loaded by compose)
DB_PASSWORD=secret
APP_VERSION=1.2.3
# docker-compose.yml
services:
app:
image: myapp:${APP_VERSION:-latest}
environment:
- DATABASE_URL=postgresql://db:5432/mydb
- API_KEY=${API_KEY} # From shell environment
env_file:
- .env.production
- ./config/secrets.env
# Run with custom environment variables
export API_KEY=abc123
docker compose up -d
You can use multiple compose files to override configurations for different environments. This is a powerful pattern for development vs production setups.
# docker-compose.yml (base)
services:
app:
image: myapp
ports:
- "3000:3000"
# docker-compose.dev.yml (development overrides)
services:
app:
build: .
volumes:
- .:/app
environment:
- NODE_ENV=development
# docker-compose.prod.yml (production overrides)
services:
app:
restart: always
environment:
- NODE_ENV=production
# Run development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Run production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
- Use version 3.8+ - The latest compose file format supports all modern features.
- Use depends_on for startup order - But remember Compose only waits for container start, not service readiness.
- Add health checks to services - Allows Compose to know when services are actually ready.
- Use named volumes for persistent data - Never use bind mounts for production databases.
- Keep compose files in version control - This is your infrastructure as code.
- Use .env file for secrets - Never hardcode passwords in compose files.
- Set resource limits - Prevent containers from consuming all host resources.
- Use restart policies -
restart: unless-stoppedfor production services.
# Resource limits example
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
docker run starts a single container with options. docker compose starts multiple containers defined in a YAML file, handles networking, volumes, and dependencies automatically. Use compose for multi-container apps.docker compose up -d --no-deps --build <service>. For zero-downtime, use rolling updates with replicas and a load balancer, or use orchestration tools like Swarm/Kubernetes.docker compose logs -f <service-name> to tail logs for that service. Use docker compose logs for all services.depends_on waits for container to start (not ready). Health checks wait for the service to be actually functional. Use both: depends_on for start order, health checks for readiness.docker compose up -d --scale service=3. Compose will run 3 instances of that service. Note: not all services are designed to scale horizontally.docker compose exec app ping database to test connectivity.docker compose down -v. This stops containers and removes networks AND volumes. Warning: This deletes persistent data. Use without -v to preserve volumes.Docker Compose transforms complex multi-container setups into simple, reproducible configurations. Start with a basic compose file and gradually add features as you learn.