TL;DR โ Docker Compose in 30 Seconds
Docker Compose lets you define and run multi-container applications with a single YAML file. Run docker compose up -d to start all services, docker compose down to stop them. Use named volumes for databases, bind mounts for source code, healthchecks + depends_on for startup ordering, andprofiles to separate dev/prod services. Pin image tags in production and always set restart policies.
Key Takeaways
- A
docker-compose.ymlhas four top-level keys:version,services,networks, andvolumes - Use
depends_onwithcondition: service_healthyto wait for real readiness, not just container start - Named volumes persist data; bind mounts sync host files into containers for development
- The
.envfile is automatically loaded for variable substitution in Compose files - Override files (
docker-compose.override.yml) layer dev settings on top of the base config - Profiles (
profiles: [debug]) let you selectively start optional services - In production: pin image tags, set restart policies, define resource limits, and use health checks
What Is Docker Compose?
Docker Compose is a tool for defining and running multi-container Docker applications. You describe your entire application stack โ web servers, databases, caches, message queues โ in a singledocker-compose.yml file, and then spin everything up with one command.
The current version is Docker Compose V2 (written in Go, bundled with Docker Desktop as docker compose). The older Python-based V1 (docker-compose with a hyphen) reached end-of-life in 2023. All examples in this guide use the V2 syntax.
Want to generate a docker-compose.yml visually? Try the free Docker Compose Generator on DevToolBox โ select services, configure options, and download a ready-to-use file in seconds.
docker-compose.yml Structure: The Four Top-Level Keys
Every Compose file can contain four top-level keys. Only services is required; the rest are optional.
# docker-compose.yml โ annotated skeleton
version: "3.9" # optional in Compose V2; omit or set to "3.9"
services: # required โ defines each container
web:
image: nginx:1.27-alpine
db:
image: postgres:16-alpine
networks: # optional โ custom network definitions
frontend:
backend:
volumes: # optional โ named volume declarations
db_data:version key is informational only and does not affect behavior. Compose automatically supports all Compose Specification features. You can safely omit it in new files.Top-Level Keys Reference
| Key | Required | Purpose |
|---|---|---|
version | No | Compose Specification version (informational in V2) |
services | Yes | Container definitions โ image, ports, volumes, env, etc. |
networks | No | Custom network declarations and driver options |
volumes | No | Named volume declarations; can reference external volumes |
configs | No | Config files mounted into services (Swarm-compatible) |
secrets | No | Sensitive data injected as files in /run/secrets/ |
Common Service Definitions: Web, Database, Cache
The services section is where you spend most of your time. Each service maps to one container. Here is a realistic three-tier stack covering a Node.js web server, PostgreSQL database, and Redis cache:
services:
# โโ Web application โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
web:
build:
context: .
dockerfile: Dockerfile
target: development # multi-stage build target
image: myapp:dev
container_name: myapp_web
ports:
- "3000:3000" # host:container
environment:
NODE_ENV: development
DATABASE_URL: postgres://user:pass@db:5432/mydb
REDIS_URL: redis://cache:6379
volumes:
- .:/app # bind mount for hot-reload
- /app/node_modules # anonymous volume to preserve modules
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
restart: unless-stopped
networks:
- frontend
- backend
# โโ PostgreSQL database โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
db:
image: postgres:16-alpine
container_name: myapp_db
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- db_data:/var/lib/postgresql/data # named volume
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # seed script
ports:
- "5432:5432" # expose for local DB clients
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
networks:
- backend
# โโ Redis cache โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
cache:
image: redis:7-alpine
container_name: myapp_cache
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
networks:
- backend
networks:
frontend:
backend:
volumes:
db_data:
redis_data:Key Service Properties
| Property | Description |
|---|---|
image | Docker image to use. Specify a tag for reproducibility. |
build | Build from a Dockerfile. Can be a path string or an object with context, dockerfile, args, target. |
ports | Port mappings as "HOST:CONTAINER" strings. Omit HOST to assign random port. |
environment | Environment variables as KEY=value list or KEY: value mapping. |
volumes | Mount points as "SOURCE:TARGET:OPTIONS". Source can be host path, named volume, or omitted (anonymous). |
networks | List of networks the container joins. |
depends_on | Startup dependencies with optional condition checks. |
restart | Restart policy: no, always, on-failure, unless-stopped. |
command | Override the default CMD from the image. |
entrypoint | Override the ENTRYPOINT from the image. |
Environment Variables and .env Files
Docker Compose offers four ways to supply environment variables to containers. Each has different use cases and security implications.
Method 1: Inline environment key
services:
web:
environment:
NODE_ENV: production
PORT: "3000"
# List syntax also works:
# - NODE_ENV=production
# - PORT=3000Method 2: .env file (automatic loading)
Compose automatically reads a file named .env in the same directory as your docker-compose.yml. Variables defined there are available for substitution throughout the Compose file:
# .env file (auto-loaded, NOT committed to git)
POSTGRES_USER=myuser
POSTGRES_PASSWORD=s3cur3p@ss
POSTGRES_DB=mydb
APP_PORT=3000# docker-compose.yml using .env substitution
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
web:
ports:
- "${APP_PORT}:3000"Method 3: env_file key
The env_file key loads all variables from a file directly into the container environment. Unlike the .env approach, these variables are not available for Compose file substitution โ they go straight into the container:
services:
web:
env_file:
- .env # loaded into container
- .env.local # merged on top (later files win)
- path: .env.prod # long form with required: true/false
required: falseMethod 4: Shell environment pass-through
# When you list a key with no value, Compose reads it from the shell
services:
web:
environment:
- SECRET_KEY # value comes from shell: export SECRET_KEY=...
- DATABASE_URLdocker-compose.yml if it is committed to version control. Use .env (gitignored) or Docker Secrets. Add .env to your .gitignore immediately.Networking Between Containers
By default, Compose creates a single default network for your project. All services on that network can reach each other using the service name as the hostname. No need to know IP addresses.
# Containers on the same network communicate by service name:
# From the 'web' container, connect to PostgreSQL at:
# host: db
# port: 5432 (the container port, not the published host port)
DATABASE_URL=postgres://user:pass@db:5432/mydbCustom Networks
Define multiple networks to isolate services. A common pattern is frontend(app + proxy) and backend (app + db + cache). The proxy can only reach the app; the database is completely isolated from the frontend:
services:
nginx:
image: nginx:alpine
networks:
- frontend # proxy faces public internet
web:
image: myapp:latest
networks:
- frontend # receives traffic from nginx
- backend # talks to db and cache
db:
image: postgres:16-alpine
networks:
- backend # only reachable from web
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # no external internet accessNetwork Drivers
| Driver | Use Case |
|---|---|
bridge | Default. Container-to-container on the same host. |
host | Container shares host network stack. No isolation. |
overlay | Multi-host networking (Docker Swarm). |
none | Disables networking completely. |
Connecting to an External Network
# Reference a network created outside Compose
networks:
shared_infra:
external: true # Compose will not create this network
name: my_infra_networkVolumes: Named Volumes vs Bind Mounts
Volumes provide persistent storage that survives container restarts and removals. Docker Compose supports three volume types: named volumes, bind mounts, and anonymous volumes (deprecated in favor of named volumes for persistence).
Named Volumes (Production Recommended)
Named volumes are managed by Docker, stored in /var/lib/docker/volumes/, and are the recommended approach for database data and other persistent state:
services:
db:
image: postgres:16-alpine
volumes:
- db_data:/var/lib/postgresql/data # named volume
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
db_data: # declare at top level
driver: local
redis_data:
driver: local
# Commands:
# docker volume ls List all volumes
# docker volume inspect myproject_db_data Inspect a volume
# docker volume rm myproject_db_data Remove a volume (after down)
# docker compose down -v Remove containers AND volumesBind Mounts (Development Recommended)
Bind mounts map a host directory or file into the container. Perfect for development because code changes on your host are immediately visible inside the container:
services:
web:
image: node:20-alpine
volumes:
- .:/app # entire project into /app
- /app/node_modules # anonymous vol: preserve container modules
working_dir: /app
command: npm run dev
# Mount a single config file (read-only):
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # :ro = read-onlyVolume Mount Options
# Long-form volume syntax (more explicit):
volumes:
- type: bind
source: ./src
target: /app/src
read_only: false
- type: volume
source: db_data
target: /var/lib/postgresql/data
volume:
nocopy: true # don't copy container data to volume on createHealth Checks
Health checks allow Docker to determine whether a container is actually ready to serve requests, not just running. A container can be in one of three states: starting,healthy, or unhealthy.
services:
# PostgreSQL with pg_isready health check
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: myuser
POSTGRES_DB: mydb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 10s # how often to run the check
timeout: 5s # how long to wait for a response
retries: 5 # how many consecutive failures before unhealthy
start_period: 30s # grace period before checks start counting
# Redis with redis-cli ping
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
# HTTP service health check using wget
api:
image: myapi:latest
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# HTTP service using curl
web:
image: myapp:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3Health Check test Formats
| Format | Behavior |
|---|---|
["NONE"] | Disables health check inherited from image. |
["CMD", "cmd", "arg"] | Runs command directly without a shell. |
["CMD-SHELL", "cmd arg"] | Runs command inside a shell (/bin/sh -c). |
depends_on with Conditions
The depends_on key controls startup order between services. The simple form (list of service names) only ensures containers start in order โ it does NOT wait for a service to be ready. Use the long form with conditions for true readiness waiting.
Simple depends_on (start order only)
services:
web:
depends_on:
- db
- cache
db:
image: postgres:16-alpine
cache:
image: redis:7-alpinedepends_on with condition: service_healthy
This is the recommended approach for databases and services that take time to initialize. The dependent service starts only when the dependency's healthcheck returns healthy:
services:
web:
image: myapp:latest
depends_on:
db:
condition: service_healthy # wait for db healthcheck to pass
restart: true # restart web if db restarts
cache:
condition: service_started # just wait for container to start
migrations:
condition: service_completed_successfully # wait for job to finish
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
migrations:
image: myapp:latest
command: npm run migrate
depends_on:
db:
condition: service_healthyCondition Values Reference
| Condition | Waits Until |
|---|---|
service_started | Container has started (no readiness guarantee). Default. |
service_healthy | Container healthcheck reports healthy. |
service_completed_successfully | Container exited with code 0 (for one-off jobs). |
Profiles: Dev vs Prod Services
Profiles let you define optional services in a single Compose file. Services assigned to a profile only start when that profile is explicitly activated. Services with no profile always start. This eliminates the need for separate Compose files for basic dev/prod differences.
services:
# Core services โ always start
web:
image: myapp:latest
ports:
- "3000:3000"
db:
image: postgres:16-alpine
# Development only tools โ start with --profile dev
adminer:
image: adminer:latest
profiles: [dev]
ports:
- "8080:8080"
depends_on:
- db
mailhog:
image: mailhog/mailhog:latest
profiles: [dev]
ports:
- "8025:8025" # web UI
- "1025:1025" # SMTP
# Monitoring stack โ start with --profile monitoring
prometheus:
image: prom/prometheus:latest
profiles: [monitoring]
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
profiles: [monitoring]
ports:
- "3001:3000"
# Debug tool โ start with --profile debug
redis-commander:
image: rediscommander/redis-commander:latest
profiles: [debug]
ports:
- "8081:8081"Activating Profiles
# Activate a single profile
docker compose --profile dev up -d
# Activate multiple profiles
docker compose --profile dev --profile monitoring up -d
# Via environment variable
export COMPOSE_PROFILES=dev,monitoring
docker compose up -d
# Run only a specific profiled service by name
docker compose run --rm adminerOverride Files: docker-compose.override.yml
Docker Compose automatically merges docker-compose.override.yml on top of docker-compose.yml when you rundocker compose up. This is ideal for developer-specific settings that should not be committed to version control.
Base file (docker-compose.yml) โ committed to git
# docker-compose.yml โ production-ready base
services:
web:
image: myapp:latest
ports:
- "3000:3000"
environment:
NODE_ENV: production
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- db_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
db_data:Override file (docker-compose.override.yml) โ gitignored
# docker-compose.override.yml โ developer overrides (gitignored)
services:
web:
build: . # build from source in dev
image: myapp:dev
volumes:
- .:/app # live code reload
- /app/node_modules
environment:
NODE_ENV: development
DEBUG: "myapp:*"
db:
ports:
- "5432:5432" # expose DB port for local clientsProduction override (docker-compose.prod.yml)
# docker-compose.prod.yml โ production layer
services:
web:
deploy:
resources:
limits:
cpus: '1'
memory: 512M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"# Use production override explicitly:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Merge order: later files win for scalars, arrays are concatenatedMerge Behavior
| Key Type | Merge Behavior | Example |
|---|---|---|
| Scalars (image, command) | Later file replaces earlier value | prod image tag overrides dev |
| Arrays (ports, volumes) | Arrays are concatenated | All ports from both files are added |
| Maps (environment) | Keys are merged; later wins for conflicts | Override can add or change env vars |
Common Docker Compose Commands
Here are the commands you will use daily, grouped by workflow:
Starting and Stopping
# Start all services in the background
docker compose up -d
# Start with forced rebuild
docker compose up -d --build
# Start specific services only
docker compose up -d web db
# Stop all services (keeps containers and volumes)
docker compose stop
# Stop and remove containers (keeps volumes and images)
docker compose down
# Stop, remove containers, volumes, AND networks
docker compose down -v
# Remove everything including images
docker compose down -v --rmi allInspecting Services
# List running services and their status
docker compose ps
# List all (including stopped) services
docker compose ps -a
# View logs for all services
docker compose logs
# Follow logs in real time
docker compose logs -f
# Follow logs for a specific service
docker compose logs -f web
# Tail last 100 lines
docker compose logs --tail=100 webExecuting Commands
# Open an interactive shell in a running container
docker compose exec web sh
docker compose exec web bash
# Run a one-off command (creates a new container, removes on exit)
docker compose run --rm web npm run migrate
docker compose run --rm web npx prisma db push
# Run as a specific user
docker compose exec --user node web sh
# Copy a file from container to host
docker compose cp web:/app/logs/error.log ./error.logManaging Images and Builds
# Build or rebuild images
docker compose build
# Build a specific service
docker compose build web
# Build without cache
docker compose build --no-cache web
# Pull latest images
docker compose pull
# List images used by services
docker compose imagesScaling and Restarting
# Scale a service to N replicas (no resource limits needed)
docker compose up -d --scale worker=3
# Restart all services
docker compose restart
# Restart a single service
docker compose restart web
# View resource usage
docker compose topMulti-Stage Builds with Docker Compose
Multi-stage builds reduce image sizes significantly by separating build dependencies from the final runtime image. Docker Compose works seamlessly with multi-stage Dockerfiles via the target build argument.
Multi-Stage Dockerfile
# Dockerfile
# โโ Stage 1: Install dependencies โโโโโโโโโโโโโโโโโโโโ
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# โโ Stage 2: Build the application โโโโโโโโโโโโโโโโโโโ
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# โโ Stage 3: Development server โโโโโโโโโโโโโโโโโโโโโโโ
FROM node:20-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
# โโ Stage 4: Production runner โโโโโโโโโโโโโโโโโโโโโโโโ
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY package*.json ./
EXPOSE 3000
CMD ["npm", "start"]Compose targeting specific stages
# docker-compose.yml (base)
services:
web:
build:
context: .
dockerfile: Dockerfile
target: production # use production stage
image: myapp:latest# docker-compose.override.yml (auto-merged in development)
services:
web:
build:
context: .
dockerfile: Dockerfile
target: development # overrides to dev stage
args:
NODE_ENV: development
image: myapp:dev
volumes:
- .:/app
- /app/node_modulesBuild args and secrets
services:
web:
build:
context: .
dockerfile: Dockerfile
args:
NODE_ENV: production
BUILD_DATE: ${BUILD_DATE} # from shell env
GIT_SHA: ${GIT_SHA}
# BuildKit secret (doesn't leak into image layers)
secrets:
- npm_token
secrets:
npm_token:
environment: NPM_TOKEN # reads from host shellProduction Best Practices
Docker Compose is well-suited for single-host production deployments. Follow these practices to make your Compose-based production deployment reliable, secure, and maintainable.
1. Pin Image Tags
# BAD: floating tags break reproducibility
services:
db:
image: postgres:latest # changes unexpectedly
cache:
image: redis # same as :latest
# GOOD: pin to a specific version
services:
db:
image: postgres:16.2-alpine # exact patch version
cache:
image: redis:7.2-alpine2. Restart Policies
services:
web:
restart: unless-stopped # restart unless explicitly stopped
db:
restart: unless-stopped
worker:
restart: on-failure # only restart on error exit codes
# restart: always # restart even on docker daemon restart3. Resource Limits
services:
web:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M4. Logging Configuration
services:
web:
logging:
driver: json-file
options:
max-size: "10m" # rotate when file reaches 10MB
max-file: "5" # keep 5 rotated files5. Read-Only Containers
services:
web:
image: myapp:latest
read_only: true # filesystem is read-only
tmpfs:
- /tmp # allow writes to /tmp only
- /var/run6. Security: Drop Capabilities
services:
web:
image: myapp:latest
cap_drop:
- ALL # drop all Linux capabilities
cap_add:
- NET_BIND_SERVICE # only add what you need
security_opt:
- no-new-privileges:true7. Health Checks in Production
services:
web:
image: myapp:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s # longer grace period in production8. Secrets Management
# Using Docker Secrets (Swarm mode) or env files
services:
db:
image: postgres:16-alpine
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt # not committed to git
# external: true # reference externally created secretComplete Production Example
# docker-compose.prod.yml โ production ready
services:
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- static_files:/var/www/static:ro
depends_on:
web:
condition: service_healthy
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
web:
image: myapp:1.5.2 # pinned version
env_file: .env.prod
volumes:
- static_files:/app/public
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
depends_on:
db:
condition: service_healthy
restart: unless-stopped
deploy:
resources:
limits:
cpus: '2'
memory: 1G
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
db:
image: postgres:16.2-alpine
volumes:
- db_data:/var/lib/postgresql/data
env_file: .env.prod
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
deploy:
resources:
limits:
memory: 2G
volumes:
db_data:
static_files:Generate docker-compose.yml Instantly
Select services visually, configure options, and get a production-readydocker-compose.yml in seconds. Free, no signup.
Debugging Docker Compose Issues
Validate your Compose file
# Check syntax and print the merged config
docker compose config
# Validate without printing
docker compose config -q && echo "Config is valid"Common error: port already in use
Error: Bind for 0.0.0.0:5432 failed: port is already allocated
# Find what's using the port:
lsof -i :5432
# or
ss -tulpn | grep 5432
# Kill the process or change the host port in docker-compose.yml:
ports:
- "5433:5432" # map to a different host portCommon error: container exiting immediately
# Check logs for the crashed container
docker compose logs web
# Run interactively to debug
docker compose run --rm web sh
# Check exit code
docker compose ps -aInspect the merged configuration
# See exact configuration after all overrides are merged
docker compose -f docker-compose.yml -f docker-compose.override.yml config
# Useful for debugging profile interactions
docker compose --profile dev configFrequently Asked Questions
What is the top-level structure of a docker-compose.yml file?
A docker-compose.yml file has four top-level keys: version (optional since Compose V2), services (required), networks (optional), and volumes (optional). The services section is where you define each container with its image, ports, environment, volumes, and other settings.
What is the difference between named volumes and bind mounts?
Named volumes (e.g. db_data:/var/lib/postgresql/data) are managed by Docker and stored in /var/lib/docker/volumes/. They persist across restarts and are recommended for production databases. Bind mounts (e.g. ./src:/app/src) map a host path into the container, ideal for development hot-reloading. Never use bind mounts for database data in production.
How does depends_on with condition: service_healthy work?
The long-form depends_on with condition: service_healthy makes Compose wait until the dependency's healthcheck reports healthy before starting the dependent service. You must define a healthcheck on the dependency. Withoutservice_healthy, depends_on only controls start order but does not wait for readiness.
How do I use .env files with Docker Compose?
Docker Compose automatically loads a .env file from the project directory. Variables there are available for substitution in the Compose file using${VAR_NAME} syntax. Use the env_file key to load variables directly into a container's environment. Always add .env to.gitignore to prevent committing secrets.
What are Docker Compose profiles and how do I activate them?
Profiles let you define optional services that only start when explicitly requested. Assign a profile with profiles: [dev]. Activate withdocker compose --profile dev up or setCOMPOSE_PROFILES=dev. Services with no profile always start. Common uses: debug tools, monitoring agents, mock services, test databases.
What is docker-compose.override.yml?
Compose automatically merges docker-compose.override.yml on top of docker-compose.yml. It is typically gitignored and contains developer-specific settings like bind mounts, extra ports, or debug commands. Arrays are concatenated; scalars in the override file replace the base file value.
What Docker Compose commands should I know?
Essential commands: docker compose up -d (start),docker compose down (stop and remove),docker compose logs -f (follow logs),docker compose exec service sh (shell access),docker compose ps (status),docker compose build (rebuild images),docker compose run --rm service cmd (one-off task),docker compose config (validate file).
What are production best practices for Docker Compose?
Pin image tags (never :latest), setrestart: unless-stopped, define resource limits underdeploy.resources, add health checks to all services, use env_file instead of hardcoding secrets, configurejson-file logging with size limits, use named volumes for persistent data, and separate dev/prod configs with override files.
Summary
Docker Compose is an essential tool for any developer working with containerized applications. With a single docker-compose.yml file, you can define complex multi-service stacks with databases, caches, reverse proxies, and background workers.
- Structure: Four top-level keys โ services, networks, volumes, version
- Services: Define web, db, and cache containers with image, ports, env, volumes
- Environment: Use
.envfor substitution,env_filefor container injection - Networking: Services reach each other by name; use custom networks for isolation
- Volumes: Named volumes for persistence, bind mounts for dev hot-reload
- Health checks: Use with
depends_on: condition: service_healthyfor true readiness - Profiles: Selectively start optional services with
profiles: [name] - Override files: Layer dev settings on top of a committed base config
- Production: Pin tags, set restart policies, limits, health checks, and external secrets
Ready to build your stack? Use the Docker Compose Generator to visually configure services and get a ready-to-use docker-compose.yml in seconds. Or explore related guides: the Docker Compose YAML errors guide and Docker volumes and bind mounts deep dive.