LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 1015 min read

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.

What You Will Learn
  • 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:

app.py
import os
import socket
from flask import Flask, jsonify
from 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)
requirements.txt
flask==3.1.3
redis==8.1.0

The Dockerfile and .dockerignore are the same as in lesson 6.

Writing compose.yaml

Next to the Dockerfile, Priya creates compose.yaml:

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:
KeyMeaning
servicesEach 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-alpineUse an existing image instead of building one
portsSame as -p: publish port 5000
environmentSame as -e. The value redis is the service name, which works as a host name
depends_on … service_healthyStart web only after redis passes its healthcheck
volumesA named volume so Redis keeps its data
healthcheckredis-cli ping must succeed for Redis to count as healthy
Networking Is Automatic

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 -d

Compose 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 ps

Both 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 service
docker compose logs -f # follow all services live (Ctrl+C to stop)
docker compose exec redis redis-cli get visits # run a command in a service

After 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 down

This 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 -v
Mini Challenge · Predict the Output

The 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?

Choose one answer

Compose Command Reference

CommandWhat it does
docker compose up -dCreate and start everything in the background
docker compose up -d --buildRebuild images first, then start
docker compose psList the project’s containers
docker compose logs [-f] [service]Show or follow logs
docker compose exec service cmdRun a command in a running service
docker compose restart serviceRestart a service
docker compose stop / startStop or start without removing containers
docker compose down [-v]Remove containers and network (and volumes with -v)
docker compose configShow the final configuration, with variables filled in
docker compose pullPull newer versions of the images
Mini Challenge · Put It in Order

Put a typical Compose development session in order.

  1. docker compose down when you are finished
  2. Test the app and read docker compose logs
  3. docker compose ps to check everything is running
  4. Write compose.yaml next to the Dockerfile
  5. docker compose up -d --build

Common Mistakes

Avoid These 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.

Next Lesson →

Multi-Stage Builds: Small, Fast, Secure Images