Beginner · Docker · topic 18 of 53 · 35 min
Docker Compose: multi-container apps
Explanation
Why Compose?
Running a web app, Postgres and Redis by hand means three long docker run commands, a network, two volumes and the right order, typed the same way on every laptop. Docker Compose puts all of this in one YAML file in the repository. Anyone can clone the project and run one command to get the whole stack.
Compose v2 is a plugin of the Docker CLI, so the command is docker compose, with a space. The old standalone docker-compose (with a hyphen) is the retired v1. The preferred file name is compose.yaml (docker-compose.yml still works), and the old top-level version: key is obsolete: Compose ignores it, so leave it out.
A real example: a small e-commerce store
A small Jaipur startup sells block-printed fabrics online. Its stack is a Node.js storefront, PostgreSQL for orders and Redis for sessions and the cart.
services:
web:
build: . # build from the Dockerfile in this folder
ports:
- "8080:3000"
env_file: .env # app settings, never committed
environment:
DATABASE_URL: postgres://shop:${POSTGRES_PASSWORD}@db:5432/shop
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy # wait until Postgres answers
cache:
condition: service_started
restart: unless-stopped
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: shop
POSTGRES_DB: shop
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shop -d shop"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
cache:
image: redis:7.4-alpine
volumes:
- redisdata:/data
restart: unless-stopped
volumes:
pgdata:
redisdata:Reading the file
- services: each entry becomes one or more containers. The key (web, db, cache) is also the host name other services use.
- image or build: use a ready image, or build one from a Dockerfile.
- ports: publish to the host, host first. Only web is published; db and cache are reachable only inside the Compose network, which is safer.
- environment and env_file: set variables. Compose also reads a file called .env in the project folder to fill in ${VARIABLES} in compose.yaml.
- volumes: the top-level section declares named volumes; the service section mounts them.
- networks: Compose creates a default network for the project automatically. Add your own only when you need to separate groups of services.
- depends_on: start order. The plain list form only waits for the container to START. With condition: service_healthy, Compose waits until that service's healthcheck passes.
- healthcheck: the command Docker runs to decide whether the service is healthy.
- restart: the same restart policies as docker run.
The commands
docker compose up -d # create network, volumes and containers; start in the background
docker compose up -d --build # rebuild images first (after code or Dockerfile changes)
docker compose ps # status and health of each service
docker compose logs -f web # follow one service's logs
docker compose exec db psql -U shop shop # run a command in a running service
docker compose build # just build the images
docker compose down # stop and remove containers and the network (volumes stay)
docker compose down -v # ALSO delete the named volumes: your database is goneProfiles and override files
Profiles let you include optional services only when asked. Services without a profile always start; a service with profiles starts only when you enable that profile.
adminer: # a small web UI for the database
image: adminer
ports:
- "8081:8080"
profiles: ["debug"]Override files change settings per environment. If a compose.override.yaml file exists, Compose merges it on top of compose.yaml automatically, which is handy for development extras such as bind-mounting the code. For other setups, list files explicitly: docker compose -f compose.yaml -f compose.prod.yaml up -d. Later files override earlier ones.
Common mistakes
- Using depends_on without a healthcheck and expecting the database to be ready: the container may have started, but Postgres needs a few seconds before it accepts connections. Use condition: service_healthy, and still make the app retry its connection.
- Connecting to localhost from the web container. Inside a container, localhost is the container itself; use the service name (db, cache).
- Committing .env with real passwords. Commit a .env.example with dummy values and add .env to .gitignore.
- Treating Compose as a production cluster. Compose is great for development, CI and small single-server deployments; for many servers and automatic failover, teams move to Kubernetes or a managed container service.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Docker Compose: multi-container apps · 1/7
Docker Compose describes a multi-container app in one file and starts it with one command.
Prefer a video by a teacher? Find free videos on “docker compose tutorial postgres redis healthcheck depends_on” on YouTube ↗
In real life: One command to start a whole store
At the Jaipur fabric startup, the two developers and a freelance designer all run the store locally with the same compose.yaml. The designer, who never installed Postgres, runs docker compose up -d and opens localhost:8080. The CI pipeline uses the same file to start Postgres and Redis for integration tests, then runs docker compose down -v to throw the test data away. In production they currently run the same stack on one cloud VM with a compose.prod.yaml override that removes the bind mounts and adds memory limits.
cp .env.example .env # then set POSTGRES_PASSWORD
docker compose up -d
docker compose ps
docker compose logs -f webPractice (0/3 done)
In a compose.yaml, the database service is called db. What host name should the web service use to connect to it?
Compose must start services after the services they depend on. Write start_order(deps), where deps maps each service to the list of services it depends on. Work in rounds: in each round, start ALL services whose dependencies have already started, in alphabetical order. Return the full list. Raise ValueError if a dependency is not a known service, or if there is a cycle (no service can start but some are left).
Write a compose.yaml for a Python (FastAPI) attendance app for a coaching institute: an api service built from the local Dockerfile on port 8000, a PostgreSQL database and a Redis cache. The api must wait until Postgres is healthy. Data must survive docker compose down. Only the api should be reachable from the host. Add an optional pgadmin or adminer service under a "debug" profile.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Image and container securityDocker · same module
- Docker: packaging your appBackend Development · Deployment and scaling
- Docker images for Go servicesGo Programming (backend and cloud) · CLI tools and Go in cloud and DevOps
- YAML in depthConfiguration management with Ansible, and YAML
- Containers with Podman: images, run, ports and logsLinux and Red Hat (RHCSA to RHCE) · Boot, recovery and containers
- Debugging and performanceKotlin and Android App Development · Testing Android apps
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is the correct way to start a Compose v2 project in the background?
Answer: docker compose up -d
Compose v2 is the docker compose plugin (with a space); up -d creates and starts everything detached.
What does depends_on: [db] (the short list form) guarantee?
Answer: db is started before the dependent service is started
The short form only orders container start-up. Use condition: service_healthy with a healthcheck to wait for readiness.
Which command removes the containers AND deletes the project's named volumes?
Answer: docker compose down -v
The -v flag removes named volumes declared in the file and anonymous volumes. Plain down keeps them.
You find version: "3.8" at the top of an old compose file. What should you know?
Answer: It is obsolete and ignored by current Compose; you can remove it
The Compose Specification no longer uses the version key; current Compose ignores it (and warns).
A service has profiles: ["debug"]. When does it start?
Answer: Only when that profile is enabled, for example with --profile debug
Services with profiles start only when one of their profiles is active.
What does -p 8080:3000 mean in docker run?Related
Answer: Port 8080 on your machine goes to port 3000 inside the container
The format is host:container.
What happens to files a container wrote into its own file system when you run docker rm on it?Related
Answer: They are deleted with the container
The writable layer belongs to the container. Use a volume for data that must survive.
From Running containers: ports, volumes, networks and limits
What is the full reference for the image you get with docker pull python?Related
Answer: docker.io/library/python:latest
The default registry is docker.io, official images live under library/, and the default tag is latest.
Why run the container as a non-root user?Related
Answer: It limits damage if the process is compromised
Least privilege is a basic security practice.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Docker” before its exam: Image and container security.
- Module 4 exam: Docker · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Kubernetes fundamentals. 53 topics left in the course before the final exam.
← Previous: Running containers: ports, volumes, networks and limits