- Docker Compose describes all services of an app in one
compose.yamlfile. - Start everything with
docker compose up -dand remove it withdocker compose down. - Services reach each other by service name, so the web app connects to
redis, not tolocalhost. depends_ononly controls start order; add a health check to wait until a service is really ready.
Running one container is easy. Running a web app, a database and a cache means three long docker run commands that you must remember in the right order. Docker Compose replaces them with one file and one command.
In this Docker Compose tutorial you will build a small web app that counts page visits in Redis. If containers are new to you, read our Docker tutorial for beginners first.
What Docker Compose does
Compose reads a YAML file that lists your services (containers), the images or Dockerfiles they use, their ports, variables and storage. It then creates a private network, so every service can reach the others by name.
Modern Docker ships Compose as a plugin, so the command is docker compose with a space. The older standalone docker-compose (with a hyphen) is the previous generation and you should use the plugin on new projects.
The sample app
Create a folder with three small files. The app is a Flask page that increments a counter in Redis.
# app.py
import os
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host=os.environ.get("REDIS_HOST", "localhost"), port=6379)
@app.route("/")
def hello():
count = cache.incr("hits")
return f"Hello! This page was viewed {count} times.\n"
# requirements.txt
flask
redis
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["flask", "--app", "app", "run", "--host", "0.0.0.0"]
Note --host 0.0.0.0. Without it Flask listens only inside the container and you cannot reach it from your browser. For a leaner image, see our Dockerfile best practices.
The compose.yaml file
Add this file next to the others. It defines two services and one named volume.
services:
web:
build: .
ports:
- "8000:5000"
environment:
REDIS_HOST: redis
depends_on:
- redis
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
volumes:
redis-data:
build: . builds the image from your Dockerfile. ports maps your computer’s port 8000 to the container’s 5000. REDIS_HOST: redis is the key line: redis is the service name and Compose resolves it on the private network.
Run and test it
docker compose up -d
docker compose ps
curl http://localhost:8000
docker compose logs -f web
Each curl call raises the counter, for example “This page was viewed 1 times.” and then 2. The -d flag runs everything in the background, ps shows the state of each service and logs -f follows the output of the web container. Stop and delete the containers and network with:
docker compose down
Volumes and environment variables
The redis-data volume keeps the counter alive when you recreate the container. docker compose down keeps volumes, so the count survives. Add -v only when you want to delete the data too.
To avoid hard-coding values, use a .env file next to the compose file. Compose reads it automatically.
# .env
WEB_PORT=8000
# compose.yaml
ports:
- "${WEB_PORT:-8000}:5000"
The part after :- is a default, used when the variable is not set. Never commit real passwords in .env; keep the file out of Git.
Start order and health checks
By default, depends_on starts Redis first, but it does not wait until Redis can accept connections. Your app may crash on its first request. A health check fixes this.
web:
depends_on:
redis:
condition: service_healthy
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
Now Compose starts web only after redis-cli ping succeeds. This is the same idea that CI pipelines use to wait for services, which we cover in our GitHub Actions CI/CD guide.
Docker Compose commands you will use daily
| Command | What it does |
|---|---|
docker compose up -d |
Create and start all services in the background |
docker compose up --build |
Rebuild images before starting |
docker compose ps |
List services and their state |
docker compose logs -f web |
Follow the logs of one service |
docker compose exec redis redis-cli get hits |
Run a command inside a running service |
docker compose stop |
Stop services without removing them |
docker compose down -v |
Remove containers, network and volumes |
Tip: after you change only the compose file, docker compose up -d recreates just the services that changed. After you change code that is copied into the image, add --build.
Common Docker Compose mistakes
- Using localhost between services. Inside a container,
localhostis the container itself. Use the service name. - Tabs in YAML. YAML allows spaces only. Use two spaces per level.
- Expecting depends_on to wait. Add a health check and
condition: service_healthy. - Forgetting the port mapping. A service without
portsis reachable only from other services. - Deleting data by accident.
down -vremoves volumes. Use plaindownday to day.
Final thoughts
Docker Compose turns a pile of commands into a file you can review, share and commit. Start with two services, add a volume, then a health check, and you will cover most real projects.
Frequently asked questions
What is Docker Compose used for?
It runs multi-container applications from one YAML file. You describe services, networks and volumes once and start the whole stack with a single command.
What is the difference between docker-compose and docker compose?
docker-compose is the older standalone tool. docker compose is the newer plugin built into Docker and is the one to use for new projects.
How do containers talk to each other in Compose?
Compose creates a private network where each service is reachable by its service name, for example redis, on that service’s internal port.
Does docker compose down delete my data?
No. It removes containers and networks but keeps named volumes. Add the -v flag only if you also want to delete the volumes.
Is Docker Compose good for production?
It is excellent for local development, testing and small single-host deployments. Large production systems usually use an orchestrator such as Kubernetes.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.