Writing Docker Compose
Docker Compose defines and runs multi-container applications from a single YAML file. Instead of starting each container manually with docker run, Compose orchestrates services, networks, and volumes together.
For single-container workflows, see the Docker CLI Cheat Sheet. For building custom images, see Writing Dockerfiles. To run a local database for SQL practice, see Local database with Docker.
1. Basic structure
Section titled “1. Basic structure”Create a compose.yaml (or docker-compose.yaml) in your project root:
services: web: image: nginx:alpine ports: - "8080:80" restart: unless-stopped
api: build: . ports: - "3000:3000" environment: APP_ENV: production depends_on: - db
db: image: postgres:16-alpine environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: appdb volumes: - db-data:/var/lib/postgresql/data
volumes: db-data:Start the stack:
docker compose up -d2. Top-level keys
Section titled “2. Top-level keys”| Key | Description |
|---|---|
services | Containers that make up the application (required) |
networks | Custom networks for service communication |
volumes | Named volumes for persistent data |
configs | External configuration files mounted into services |
secrets | Sensitive data (passwords, tokens) managed separately |
3. Service options
Section titled “3. Service options”| Option | Description |
|---|---|
image | Use a pre-built image from a registry |
build | Build from a Dockerfile (. or { context: ., dockerfile: Dockerfile.prod }) |
ports | Publish ports ("host:container") |
expose | Expose ports to other services only (not the host) |
environment | Environment variables (map or list) |
env_file | Load variables from a .env file |
volumes | Mount named volumes or bind mounts |
networks | Attach the service to one or more networks |
depends_on | Start order dependency (does not wait for the service to be ready) |
restart | Restart policy: no, always, on-failure, unless-stopped |
command | Override the default container command |
entrypoint | Override the default entrypoint |
healthcheck | Define a health probe for the service |
profiles | Optional services activated with --profile |
3.1 build context
Section titled “3.1 build context”Build from the current directory:
services: api: build: .Build with a custom Dockerfile and build arguments:
services: api: build: context: . dockerfile: Dockerfile.prod args: JAVA_VERSION: "21"3.2 Volumes
Section titled “3.2 Volumes”Named volume — managed by Docker, survives container restarts:
services: db: volumes: - db-data:/var/lib/postgresql/data
volumes: db-data:Bind mount — maps a host path into the container (common in development):
services: api: volumes: - ./src:/app/src3.3 Networks
Section titled “3.3 Networks”By default, all services join a default network and can reach each other by service name. Define custom networks to isolate groups:
services: frontend: networks: - public
backend: networks: - public - internal
db: networks: - internal
networks: public: internal: internal: truebackend can talk to both frontend and db; db is only reachable from internal.
3.4 Environment variables
Section titled “3.4 Environment variables”Inline map:
environment: APP_ENV: production LOG_LEVEL: infoFrom a file:
env_file: - .env - .env.productionUse a .env file in the same directory for local defaults (not committed if it contains secrets):
APP_ENV=developmentPOSTGRES_PASSWORD=localdev4. Common commands
Section titled “4. Common commands”| Command | Description |
|---|---|
docker compose up | Create and start all services |
docker compose up -d | Start in detached mode (background) |
docker compose down | Stop and remove containers, networks |
docker compose down -v | Also remove named volumes |
docker compose ps | List running services |
docker compose logs | View logs from all services |
docker compose logs -f api | Follow logs for a single service |
docker compose build | Build or rebuild service images |
docker compose pull | Pull images for all services |
docker compose restart api | Restart a single service |
docker compose exec api sh | Run a command inside a running container |
Rebuild after Dockerfile changes:
docker compose up -d --build5. Example: web app with cache and database
Section titled “5. Example: web app with cache and database”services: app: build: . ports: - "8080:8080" environment: DATABASE_URL: postgres://app:secret@db:5432/appdb REDIS_URL: redis://cache:6379 depends_on: - db - cache restart: unless-stopped
db: image: postgres:16-alpine environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: appdb volumes: - db-data:/var/lib/postgresql/data
cache: image: redis:7-alpine volumes: - redis-data:/data
volumes: db-data: redis-data:Services resolve each other by name — app connects to db:5432 and cache:6379 over the default network.
6. Profiles
Section titled “6. Profiles”Run optional services only when needed (e.g., debugging tools):
services: api: image: my-api:latest
debug: image: nicolaka/netshoot profiles: - debug network_mode: service:apidocker compose --profile debug up -d7. Quick checklist
Section titled “7. Quick checklist”- Use service names as hostnames — no need for hardcoded IPs.
- Store secrets in
.envor Docker secrets, not in the compose file itself. - Pin image tags (
postgres:16-alpine, notpostgres:latest). - Use named volumes for database persistence.
- Add
restart: unless-stoppedfor services that should survive host reboots. - Use
docker compose configto validate and render the final YAML before deploying.