Docker Compose: Running Multi-Container Applications
Introduction
Real applications are more than one container: a web app, a database, maybe a cache. Docker Compose describes all of them in one file and starts them together with a single command. This lesson builds a web and database setup with health-based startup ordering, persistent storage, and safe handling of secrets.
Prerequisites
- Docker installed with the Compose plugin
- A Dockerfile for a web application
Step-by-Step Guide
Step 1: Create a compose.yaml
The preferred file name is compose.yaml. The old top-level version key is obsolete and should be left out, since Compose always uses the latest schema. This file defines a web service built from your Dockerfile and a PostgreSQL database with a named volume and a health check.
services:
web:
build: .
ports:
- "8000:3000"
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 5
volumes:
db-data:Step 2: Keep secrets out of the file
Compose substitutes variables from your shell or from a .env file next to compose.yaml. Put the password in .env, and add .env to .gitignore and .dockerignore so it is never committed or baked into an image. For real secrets in production, prefer Compose secrets or your platform's secret manager over environment variables.
# .env (never commit this file)
DB_PASSWORD=change-me-locallyStep 3: Start, inspect, and stop the stack
Services on the same Compose network reach each other by service name, so the web app connects to the database at the host name db. Use down to stop and remove containers, and add -v only when you want to delete the named volumes and their data too.
docker compose up -d
docker compose ps
docker compose logs -f web
docker compose exec db psql -U app -d app
docker compose down # keeps the db-data volume
docker compose down -v # also deletes the dataStep 4: Understand startup order and health
depends_on with condition service_healthy makes the web service wait until the database passes its health check, which avoids connection errors during startup. A plain depends_on only waits for the container to start, not to be ready. Check the resolved configuration with docker compose config.
docker compose configStep 5: Use profiles for optional services
A service with a profiles list only starts when that profile is enabled. This is useful for tools you do not always want, such as a database admin UI. Services without profiles always start.
services:
adminer:
image: adminer
ports:
- "8080:8080"
profiles: [debug]
# docker compose --profile debug up -dFAQs
- What is the difference between docker-compose and docker compose?
- docker-compose is the older standalone tool. docker compose is the current plugin built into the Docker CLI and is what the docs use. Prefer the plugin.
- Is Compose suitable for production?
- It is great for development, testing, and small single-host deployments. For multi-node scaling and self-healing across many machines, teams usually move to an orchestrator such as Kubernetes.
Summary
Describe services, volumes, and health checks in compose.yaml, keep passwords in an ignored .env file, rely on service names for networking, and use health-based depends_on and profiles for reliable startup and optional tools.
Comments
Log in or create a free account to join the discussion.