Docker Compose Getting Started Guide (2026): Complete Tutorial
Last updated: March 2026
Docker Compose defines and runs multi-container applications from a single YAML file. In 2026, Compose v2 is built into the Docker CLI as docker compose (with a space). This complete tutorial covers installation, the compose.yml structure, a real-world multi-container application with a web app, PostgreSQL, and Redis, environment variables, health-check dependencies, and the commands you will use every day.
Docker Compose v1 vs v2
Before starting, clarify which version you are using:
| Compose v1 | Compose v2 | |
|---|---|---|
| Command | docker-compose (hyphen) |
docker compose (space) |
| Implementation | Standalone Python binary | Go plugin built into Docker CLI |
| Status | End-of-life July 2023 | Current standard |
| File name | docker-compose.yml |
compose.yml (also accepts docker-compose.yml) |
Always use docker compose (v2) for new projects. Compose v1 is no longer maintained.
Installing Docker Compose v2
Ubuntu 22.04 / 24.04 / Debian 12
# Install the Docker Compose plugin
sudo apt-get update
sudo apt-get install -y docker-compose-plugin
# Verify
docker compose version
# Docker Compose version v2.24.0
RHEL 9 / CentOS Stream 9 / Fedora
sudo dnf install -y docker-compose-plugin
docker compose version
Using Docker Desktop (macOS / Windows / Linux)
Docker Desktop bundles Compose v2. No additional installation required.
docker compose version
Manual installation (any Linux)
# Install the latest release
COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep tag_name | cut -d '"' -f 4)
sudo curl -SL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-x86_64" \
-o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
docker compose version
The compose.yml File Structure
A Compose file has three top-level sections:
services: # Container definitions
volumes: # Named volumes
networks: # Custom networks
The minimum valid Compose file:
services:
web:
image: nginx:alpine
ports:
- "80:80"
Run it:
docker compose up -d
docker compose ps
docker compose down
Service Configuration Reference
services:
myapp:
# Image to use (from registry or local)
image: myapp:latest
# Or build from a Dockerfile
build:
context: .
dockerfile: Dockerfile
target: production # Multi-stage build target
# Container name (optional)
container_name: myapp
# Port mapping (host:container)
ports:
- "8080:3000"
- "127.0.0.1:9229:9229" # Bind to localhost only
# Environment variables
environment:
NODE_ENV: production
PORT: "3000"
# Or load from a file
env_file:
- .env
- .env.production
# Volume mounts
volumes:
- ./src:/app/src # Bind mount (dev)
- app_data:/app/data # Named volume (prod)
# Networks
networks:
- frontend
- backend
# Dependency ordering
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
# Restart policy
restart: unless-stopped
# Resource limits
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
# Healthcheck
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Real-World Example: Web App + PostgreSQL + Redis
This example defines a Python web application with a PostgreSQL database and Redis for caching/sessions.
# compose.yml
services:
web:
build: .
container_name: webapp
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://appuser:apppassword@db:5432/appdb
REDIS_URL: redis://redis:6379/0
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
DEBUG: "false"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
- static_files:/app/static
networks:
- app_network
restart: unless-stopped
db:
image: postgres:16-alpine
container_name: postgres
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: apppassword
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # Run on first start
networks:
- app_network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: redis:7-alpine
container_name: redis
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- app_network
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- static_files:/var/www/static:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- web
networks:
- app_network
restart: unless-stopped
volumes:
postgres_data:
redis_data:
static_files:
networks:
app_network:
driver: bridge
Companion .env file:
# .env — never commit this file
DJANGO_SECRET_KEY=your-very-long-random-secret-key-here
POSTGRES_PASSWORD=apppassword
Add .env to .gitignore:
echo ".env" >> .gitignore
Essential Docker Compose Commands
Start and Stop
# Start all services in the background
docker compose up -d
# Start and rebuild images first
docker compose up -d --build
# Start only specific services
docker compose up -d web db
# Stop all services (containers preserved)
docker compose stop
# Stop and remove containers, networks
docker compose down
# Stop and remove everything including volumes (CAUTION: deletes data)
docker compose down -v
# Stop and remove including images
docker compose down --rmi all
Status and Logs
# Show running services
docker compose ps
# Show all services including stopped
docker compose ps -a
# View logs (all services)
docker compose logs
# Follow logs
docker compose logs -f
# Logs for a specific service
docker compose logs -f web
# Last 50 lines
docker compose logs --tail 50 web
Execute Commands Inside Containers
# Open a shell in a running container
docker compose exec web bash
docker compose exec web sh # Alpine containers use sh
# Run a one-off command
docker compose exec db psql -U appuser -d appdb
# Run a command in a new container (does not require the service to be running)
docker compose run --rm web python manage.py migrate
docker compose run --rm web python manage.py createsuperuser
Build and Pull
# Build all services
docker compose build
# Build a specific service
docker compose build web
# Build without using cache
docker compose build --no-cache web
# Pull latest images
docker compose pull
Scaling
# Run 3 instances of the web service
docker compose up -d --scale web=3
# Note: cannot scale services with specific container_name or host port bindings
Environment Variables in Compose
Docker Compose resolves environment variables from multiple sources in this order (later sources override earlier):
- Shell environment variables
.envfile in the same directory ascompose.yml- Environment files specified with
env_file: - Variables defined under
environment:in the service definition
services:
web:
image: myapp:${APP_VERSION:-latest} # Default to 'latest' if APP_VERSION not set
environment:
NODE_ENV: ${NODE_ENV:-production}
PORT: "3000"
env_file:
- .env
- .env.${NODE_ENV:-production} # e.g., .env.production
# Override at runtime
APP_VERSION=2.1.0 docker compose up -d
# Use a different env file
docker compose --env-file .env.staging up -d
depends_on and Service Ordering
depends_on controls start order and (with conditions) readiness:
depends_on:
db:
condition: service_healthy # Wait for healthcheck to pass
redis:
condition: service_started # Wait for container to start (not necessarily ready)
migrations:
condition: service_completed_successfully # Wait for one-shot container to exit 0
The service_healthy condition requires a healthcheck on the dependency. Without it, service_started is used and the dependent container may start before the dependency is ready — leading to connection errors at startup.
Multiple Compose Files (Override Pattern)
Use multiple Compose files to handle environment differences:
# Base file
compose.yml
# Development overrides
compose.override.yml # Automatically applied in development
# Production overrides
compose.prod.yml # Apply explicitly with -f
# compose.override.yml (development)
services:
web:
volumes:
- .:/app # Live code mounting
environment:
DEBUG: "true"
command: python manage.py runserver 0.0.0.0:8000
# Production (use explicit files)
docker compose -f compose.yml -f compose.prod.yml up -d
Related Articles
- Docker Configuration & Troubleshooting Guide 2026 — Complete Docker reference
- Docker Container Networking — Understand Compose networking in depth
- Docker Healthcheck Guide — Configure healthchecks for depends_on
- Docker Logging Drivers — Configure logging for Compose services
- Fix Docker Permission Denied — If docker compose commands fail with permission errors
FAQ
Q: What is the difference between docker compose down and docker compose stop?
docker compose stop stops the containers but preserves them and their data. You can restart with docker compose start. docker compose down stops the containers and removes them along with the networks Compose created. Named volumes are preserved unless you add -v. Use stop/start for quick pauses; use down/up when you want a clean slate.
Q: How do I run database migrations before the web app starts? Use a one-shot service that exits after running migrations:
services:
migrations:
build: .
command: python manage.py migrate
depends_on:
db:
condition: service_healthy
web:
build: .
depends_on:
migrations:
condition: service_completed_successfully
Q: How do I pass secrets to Docker Compose without committing them?
Use a .env file (never committed to version control) for development. In production, use Docker secrets (docker secret create) with Swarm mode, or pass secrets via environment variables from your CI/CD system (GitHub Actions secrets, GitLab CI variables, HashiCorp Vault). Reference them in compose.yml with ${SECRET_NAME} and inject them via the CI runner's environment.