Docker Compose: Multi-Container Apps
Describe an app and its Redis database in one compose.yaml file, start everything with docker compose up, and manage it with ps, logs, exec and down.
On this page 11 sections
Introduction
Real apps have several parts: a web app, a database, a cache. Starting each with a long docker run command — plus a network and volumes — quickly becomes error-prone. Docker Compose describes the whole app in one YAML file and starts or stops everything with a single command.
- How to describe services, ports, environment variables and volumes in compose.yaml.
- How to make one service wait until another is healthy.
- How to start, inspect and stop a multi-container app.
- Which Compose commands you will use every day.
The App: Quotes API with a Visit Counter
Priya adds a visit counter to her Quotes API. The count is stored in Redis, a fast in-memory database, so it is shared by every copy of the app. Her app now uses the redis Python package, and finds Redis through the REDIS_HOST variable:
import osimport socket
from flask import Flask, jsonifyfrom redis import Redis
app = Flask(__name__)redis = Redis(host=os.environ.get("REDIS_HOST", "localhost"), port=6379)
QUOTES = [ "Talk is cheap. Show me the code.", "First, solve the problem. Then, write the code.", "Simplicity is prerequisite for reliability.",]
@app.route("/")def home(): visits = redis.incr("visits") return jsonify(message="Hello from Docker Compose!", visits=visits, container=socket.gethostname())
@app.route("/quotes")def quotes(): return jsonify(QUOTES)
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)flask==3.1.3redis==8.1.0The Dockerfile and .dockerignore are the same as in lesson 6.
Writing compose.yaml
Next to the Dockerfile, Priya creates compose.yaml:
services: web: build: . ports: - "5000:5000" environment: REDIS_HOST: redis depends_on: redis: condition: service_healthy
redis: image: redis:8-alpine volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 5
volumes: redis-data:| Key | Meaning |
|---|---|
| services | Each entry is one container: web (built from this folder) and redis (an official image) |
| build: . | Build the image from the Dockerfile in this folder |
| image: redis:8-alpine | Use an existing image instead of building one |
| ports | Same as -p: publish port 5000 |
| environment | Same as -e. The value redis is the service name, which works as a host name |
| depends_on … service_healthy | Start web only after redis passes its healthcheck |
| volumes | A named volume so Redis keeps its data |
| healthcheck | redis-cli ping must succeed for Redis to count as healthy |
Compose creates a network for the project and puts every service on it, so services reach each other by service name — exactly the user-defined network setup from lesson 7, without any extra commands.
Starting the App: up
From the project folder, start everything in the background:
docker compose up -dCompose pulls redis:8-alpine, builds the web image, creates a network (quotes-api_default), the volume and both containers, and starts web once Redis reports healthy. Names are prefixed with the project name, which defaults to the folder name. Check the status:
docker compose psBoth services are listed as running, with Redis marked (healthy). Now call the API a few times — the visits count goes 1, 2, 3:
curl http://localhost:5000/curl http://localhost:5000/curl http://localhost:5000/Managing the App
docker compose logs web --tail 3 # recent output of one servicedocker compose logs -f # follow all services live (Ctrl+C to stop)docker compose exec redis redis-cli get visits # run a command in a serviceAfter a code change, rebuild and recreate only what changed with docker compose up -d --build. To restart a single service, use docker compose restart web.
Stopping: down and down -v
docker compose downThis stops and removes the containers and the network, but keeps the named volume. Run docker compose up -d again and the counter carries on from where it was, because Redis saved its data to the volume. To remove the volume too — and lose the data — add -v:
docker compose down -vThe visit counter is at 3. Priya runs docker compose down, then docker compose up -d, then curl http://localhost:5000/. What is the visits value?
Compose Command Reference
| Command | What it does |
|---|---|
| docker compose up -d | Create and start everything in the background |
| docker compose up -d --build | Rebuild images first, then start |
| docker compose ps | List the project’s containers |
| docker compose logs [-f] [service] | Show or follow logs |
| docker compose exec service cmd | Run a command in a running service |
| docker compose restart service | Restart a service |
| docker compose stop / start | Stop or start without removing containers |
| docker compose down [-v] | Remove containers and network (and volumes with -v) |
| docker compose config | Show the final configuration, with variables filled in |
| docker compose pull | Pull newer versions of the images |
Put a typical Compose development session in order.
- docker compose down when you are finished
- Test the app and read docker compose logs
- docker compose ps to check everything is running
- Write compose.yaml next to the Dockerfile
- docker compose up -d --build
Common Mistakes
- Using localhost to reach another service — use the service name (redis, db).
- Indenting YAML with tabs or inconsistent spaces — YAML needs consistent spaces.
- Running docker compose down -v out of habit and deleting the database.
- Forgetting --build after changing code, and testing the old image.
Frequently Asked Questions
docker compose (with a space) is the current Compose, a plugin of the docker command. The old standalone docker-compose program is deprecated; the file format is the same.
Both work. compose.yaml is the current preferred name; Compose still finds docker-compose.yml in older projects.
Yes, for a single server it is a simple and popular choice. For many servers, teams usually move to Kubernetes or a managed container service.
Key Takeaways
- compose.yaml describes services, ports, environment, volumes and healthchecks in one file.
- Services reach each other by service name on a network Compose creates automatically.
- depends_on with condition: service_healthy waits for a dependency to be ready.
- up -d starts everything; down removes it but keeps volumes; down -v deletes volumes too.
- ps, logs, exec and up --build are the everyday commands.
Summary
You can now run a complete multi-container app with one command. Next you will make your images dramatically smaller with multi-stage builds.