Module 5: Docker Compose — Multi-Container Stacks

7. Essential Compose Commands

Description

Docker Compose has a set of commands that cover your stack's entire lifecycle: bringing it up, monitoring it, debugging it, rebuilding it, and shutting it down. Mastering these commands is the difference between "I know docker compose up exists" and "I can operate a multi-service stack with confidence." These are the same commands you'll use every day as an AI Engineer working with development stacks.

Why it matters: In your day-to-day, you don't just bring the stack up once — you bring it up, watch logs, run commands inside a container, rebuild after changing the Dockerfile, restart a service that failed, and you do all of that several times a day. Every minute you save with the right command adds up over weeks and months.

Connection with the module: Capsules 02-06 taught you what to put in the docker-compose.yml. This capsule teaches you how to operate that file — the commands you run in your terminal to interact with your stack.


docker compose up

The most important command

docker compose up

It does everything in one step:

  1. Creates the default network (if it doesn't exist)
  2. Creates the declared volumes (if they don't exist)
  3. Builds the images that have build: (if they don't exist)
  4. Creates and starts the containers
  5. Shows logs in the foreground (attached mode)

Essential flags

# Detached: runs in the background, frees up your terminal
docker compose up -d
[+] Running 3/3
 ✔ Container stack-redis-1     Started
 ✔ Container stack-chromadb-1  Started
 ✔ Container stack-api-1       Started
# Build: rebuilds the images before bringing them up
docker compose up --build

It rebuilds every image that has build:. Without this flag, Compose uses the cached image — if you changed your Dockerfile or your code, you won't see the changes.

# The most common combination in development
docker compose up -d --build

Rebuilds and brings it up in the background. The command you'll use the most.

# Bring up a single service (and its dependencies)
docker compose up -d redis

Brings up only Redis. If another service depends on Redis and isn't running, it does NOT bring it up — only what you asked for.

# Force recreate: recreates containers even if they haven't changed
docker compose up -d --force-recreate

Useful when you change environment variables or other configs that don't trigger an automatic recreate.

When to use each variant

SituationCommand
First timedocker compose up -d --build
I changed Python codedocker compose up -d --build
I changed docker-compose.ymldocker compose up -d
I changed .envdocker compose up -d --force-recreate
I just want to see logsdocker compose up (without -d)
I only need Redisdocker compose up -d redis

docker compose down

Shut down and clean up

docker compose down
[+] Running 4/4
 ✔ Container stack-api-1       Removed
 ✔ Container stack-chromadb-1  Removed
 ✔ Container stack-redis-1     Removed
 ✔ Network stack_default       Removed

It works in reverse order:

  1. Stops the containers (reverse dependency order)
  2. Removes the containers
  3. Removes the default network

It does not remove: volumes or the images you built.

Important flags

# Remove volumes (Redis data, ChromaDB data, etc.)
docker compose down -v

⚠️ Careful: This wipes out ALL persisted data. Use it only for a complete reset.

# Remove locally built images
docker compose down --rmi local

Removes the images that were built with build:. The images pulled from Docker Hub (Redis, ChromaDB) stay.

# Remove ALL images (including the Docker Hub ones)
docker compose down --rmi all
# Nuclear: remove everything (containers, networks, volumes, images)
docker compose down -v --rmi all

When to use each variant

SituationCommand
Wrapping up for the daydocker compose down
Corrupted data, I want a resetdocker compose down -v
I want a clean rebuilddocker compose down --rmi local
Switched projects, freeing spacedocker compose down -v --rmi all

docker compose logs

See the logs from every service

docker compose logs
api-1       | INFO:     Uvicorn running on http://0.0.0.0:8000
api-1       | INFO:     Application startup complete.
redis-1     | Ready to accept connections tcp
chromadb-1  | Running Chroma

Essential flags

# Follow: real-time logs (Ctrl+C to exit)
docker compose logs -f
# Logs from a specific service
docker compose logs api
# Follow a specific service
docker compose logs -f api
# The last N lines
docker compose logs --tail 50 api
# With timestamps
docker compose logs -t api
api-1  | 2026-03-08T15:30:01.234Z INFO:     Application startup complete.
api-1  | 2026-03-08T15:30:05.678Z INFO:     GET /health 200 OK
# Combine: the last 20 lines + follow + timestamps
docker compose logs -f -t --tail 20 api

Filtering logs from several services

# Only api and redis (no chromadb)
docker compose logs -f api redis

The debugging pattern

When something breaks, the typical sequence is:

# 1. Check the state of the services
docker compose ps

# 2. If a service failed, look at its logs
docker compose logs failing-service

# 3. Watch the logs in real time while you reproduce the bug
docker compose logs -f api
# In another terminal: curl http://localhost:8000/broken-endpoint

docker compose ps

Check the state of the services

docker compose ps
NAME                IMAGE                    COMMAND                  SERVICE    STATUS          PORTS
stack-api-1         stack-api                "uvicorn main:app ..."   api        Up 5 minutes    0.0.0.0:8000->8000/tcp
stack-chromadb-1    chromadb/chroma:latest   "/docker_entrypoin..."   chromadb   Up 5 minutes    8000/tcp
stack-redis-1       redis:7-alpine           "docker-entrypoint..."   redis      Up 5 minutes    6379/tcp

With health status

docker compose ps
NAME                STATUS                    PORTS
stack-api-1         Up 5 minutes (healthy)    0.0.0.0:8000->8000/tcp
stack-chromadb-1    Up 5 minutes (healthy)    8000/tcp
stack-redis-1       Up 5 minutes (healthy)    6379/tcp

Filtering by state

# Only running services
docker compose ps --status running

# Only stopped services
docker compose ps --status exited

Compact format

docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
NAME                STATUS                    PORTS
stack-api-1         Up 5 minutes (healthy)    0.0.0.0:8000->8000/tcp
stack-chromadb-1    Up 5 minutes (healthy)    8000/tcp
stack-redis-1       Up 5 minutes (healthy)    6379/tcp

docker compose exec

Run commands inside a running container

# An interactive shell in the API
docker compose exec api bash
# Run a specific command
docker compose exec api python3 -c "print('hello from container')"
# The Redis CLI
docker compose exec redis redis-cli
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> KEYS *
1) "cache:sentiment:hello"
2) "model:gpt-4o-mini"
127.0.0.1:6379> exit
# Verify the connection between services
docker compose exec api python3 -c "
import redis
r = redis.Redis(host='redis', port=6379, decode_responses=True)
print(f'PING: {r.ping()}')
print(f'Keys: {r.dbsize()}')
"

Useful flags

# Without a TTY (for scripts)
docker compose exec -T api python3 script.py

# As a specific user
docker compose exec --user root api bash

# With extra environment variables
docker compose exec -e DEBUG=true api python3 app.py

exec vs run

CommandPurpose
docker compose execRuns in a container that is already running
docker compose runCreates a new container to run the command
# exec: step into the api container that's already running
docker compose exec api bash

# run: create a new container based on the api service
docker compose run --rm api bash

Use exec for debugging running containers. Use run for one-off commands (migrations, tests) in a clean container.


docker compose build

Build/rebuild images

# Build every image that has build:
docker compose build
# Build a single service
docker compose build api
# Without cache (a full rebuild)
docker compose build --no-cache
# With build args
docker compose build --build-arg ENV=production api

When to use build vs up --build

# Build only (doesn't bring anything up)
docker compose build

# Build AND bring up
docker compose up --build -d

docker compose build is useful when you want to verify that the build works without bringing up the whole stack. docker compose up --build is the integrated command for daily development.


docker compose restart

Restart services

# Restart every service
docker compose restart
# Restart a specific service
docker compose restart api

restart vs up --build

CommandWhat it does
docker compose restart apiStops and restarts the existing container (same image)
docker compose up -d --build apiRebuilds the image and recreates the container

restart is faster but doesn't apply changes in code or the Dockerfile. If you changed something, you need up --build.


docker compose stop / start

Stop without removing

# Stop the services (the containers stay, they can be restarted)
docker compose stop
# Restart the stopped services
docker compose start

stop vs down

CommandContainersNetworkVolumes
docker compose stopStopped (they exist)ExistsThey exist
docker compose downRemovedRemovedThey exist (without -v)

stop is like hitting pause. down is closing everything.


docker compose config

Validate and see the effective configuration

docker compose config

It shows the "resolved" docker-compose.yml — with every environment variable interpolated, defaults applied, and paths resolved.

name: ai-stack
services:
  api:
    build:
      context: /Users/dev/ai-stack
      dockerfile: Dockerfile
    depends_on:
      redis:
        condition: service_healthy
    environment:
      OPENAI_API_KEY: sk-proj-abc123
      REDIS_HOST: redis
    ports:
      - mode: ingress
        target: 8000
        published: "8000"
        protocol: tcp
  redis:
    healthcheck:
      test:
        - CMD
        - redis-cli
        - ping
      interval: 5s
      timeout: 3s
      retries: 5
    image: redis:7-alpine

Validate before bringing it up

docker compose config --quiet
# No output = valid YAML
# Error output = there are problems
# Just see the defined services
docker compose config --services
api
redis
chromadb
# Just see the volumes
docker compose config --volumes
redis-data
chromadb-data

docker compose pull

Download/update images

# Pull the latest images for services with image:
docker compose pull
# Just one service
docker compose pull redis

Useful when you want to make sure you have the latest version of redis:7-alpine or chromadb/chroma:latest.


The Development Workflow

The daily cycle

                ┌──────────────────┐
                │  Edit code       │
                └────────┬─────────┘
                         │
                         ▼
                ┌──────────────────┐
                │  up -d --build   │
                └────────┬─────────┘
                         │
                         ▼
                ┌──────────────────┐
                │  logs -f api     │
                └────────┬─────────┘
                         │
                         ▼
                ┌──────────────────┐
                │  Test (curl/     │
                │  browser)        │
                └────────┬─────────┘
                         │
               ┌─────────┴────────┐
               │                  │
            Works?          Doesn't work?
               │                  │
               ▼                  ▼
          ┌─────────┐      ┌──────────┐
          │ Commit  │      │ exec /   │
          │ & push  │      │ logs /   │
          └─────────┘      │ debug    │
                           └────┬─────┘
                                │
                                └──► Back to editing

The typical sequence: a morning of work

# 1. Bring the stack up
docker compose up -d --build

# 2. Check that everything is running
docker compose ps

# 3. Watch the logs in a separate terminal
docker compose logs -f api

# 4. Make changes in the code...
# (you edit Python files)

# 5. Rebuild and relaunch
docker compose up -d --build

# 6. Test
curl http://localhost:8000/health

# 7. If something fails, debug
docker compose logs api
docker compose exec api bash

# 8. At the end of the day
docker compose down

The debugging sequence

# Something broke. Which services are running?
docker compose ps

# Look at the logs from the service that failed
docker compose logs chromadb

# Is it a configuration error? Validate the YAML
docker compose config --quiet

# Do I need to step into the container?
docker compose exec api bash

# Do I need to restart just one service?
docker compose restart api

# Do I need a clean rebuild?
docker compose down
docker compose up -d --build

Quick Reference

Every command in one table

CommandPurposeCommon flags
docker compose upBring the stack up-d, --build, --force-recreate
docker compose downShut down and clean up-v, --rmi local/all
docker compose logsSee the logs-f, --tail N, -t, service
docker compose psState of the services--status running/exited
docker compose execRun in a running container-T, --user, -e
docker compose runRun in a new container--rm, -e
docker compose buildBuild images--no-cache, service
docker compose restartRestart servicesservice
docker compose stopStop without removingservice
docker compose startStart the stopped onesservice
docker compose configValidate the YAML--quiet, --services, --volumes
docker compose pullUpdate imagesservice

Cheatsheet: the 5 commands you'll use the most

docker compose up -d --build     # Bring up/rebuild
docker compose logs -f api       # Watch logs in real time
docker compose ps                # State of the services
docker compose exec api bash     # Step into a container
docker compose down              # Shut everything down

Troubleshooting

"no configuration file provided: not found"

There's no docker-compose.yml in the current directory:

ls docker-compose.yml compose.yml 2>/dev/null
# If neither exists, you're in the wrong directory
pwd

"service 'api' failed to build"

An error in your Dockerfile. Try the manual build to see the complete error:

docker build -t test-build .

"Bind for 0.0.0.0:8000 failed: port is already allocated"

Another process is using port 8000:

lsof -i :8000
# Identify the process and kill it, or change the port in docker-compose.yml

docker compose up hangs

Possible causes:

  • A service has a health check that never passes
  • A service crashes and Compose keeps waiting
# Ctrl+C to cancel, then check the state
docker compose ps
docker compose logs problem-service

Code changes aren't showing up

You forgot --build:

# ❌ Doesn't rebuild
docker compose up -d

# ✅ Rebuilds the images
docker compose up -d --build

Exercises

Exercise 1: The complete cycle

Create a stack with Redis, bring it up, check it with ps, write data with exec, look at the logs, and shut it down with down.

See solution
# docker-compose.yml
services:
  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3
# Bring it up
docker compose up -d
# Check it
docker compose ps
# Write data
docker compose exec redis redis-cli SET greeting "Hello Docker Compose"
# Read data
docker compose exec redis redis-cli GET greeting
# Look at the logs
docker compose logs redis
# Shut it down
docker compose down

Exercise 2: Rebuild after a change

Create a stack with a build: service. Change the code, and show the difference between up -d (no rebuild) and up -d --build (with rebuild).

See solution
# app.py
print("VERSION 1")
FROM python:3.11-slim
COPY app.py .
CMD ["python", "app.py"]
services:
  app:
    build: .
docker compose up --build
# Output: VERSION 1

# Change app.py to "VERSION 2"
echo 'print("VERSION 2")' > app.py

docker compose up
# Output: VERSION 1 (it didn't rebuild!)

docker compose up --build
# Output: VERSION 2 (it rebuilt!)

docker compose down

Exercise 3: Debugging with exec and logs

Bring up a stack with Redis. Use exec to create data, logs to see the activity, and inspect to check health.

See solution
services:
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3
docker compose up -d
sleep 10

# Exec: create data
docker compose exec redis redis-cli SET key1 "value1"
docker compose exec redis redis-cli SET key2 "value2"
docker compose exec redis redis-cli DBSIZE
# (integer) 2

# Logs: see the activity
docker compose logs redis

# PS: see the status
docker compose ps
# redis-1  Up X seconds (healthy)

docker compose down

Exercise 4: Bring up a single service

Create a stack with 3 services. Bring up only one and verify that the others aren't running.

See solution
services:
  app:
    image: python:3.11-slim
    command: sleep 3600
  redis:
    image: redis:7-alpine
  worker:
    image: python:3.11-slim
    command: sleep 3600
docker compose up -d redis

docker compose ps
# Only redis is Up
# app and worker don't show up (they were never created)

docker compose up -d
# Now all three are Up

docker compose ps
# app-1, redis-1, worker-1 all Up

docker compose down

Exercise 5: down with different flags

Bring up a stack with volumes. Show the difference between down, down -v, and down --rmi local.

See solution
services:
  app:
    build: .
    volumes:
      - app-data:/data
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
volumes:
  app-data:
  redis-data:
# Bring it up
docker compose up -d --build

# Basic down: containers and network removed, volumes and images stay
docker compose down
docker volume ls | grep data   # ✅ The volumes exist
docker images | grep app       # ✅ The image exists

# Bring it up again
docker compose up -d --build

# down -v: it also removes the volumes
docker compose down -v
docker volume ls | grep data   # ❌ Volumes removed

# Bring it up again
docker compose up -d --build

# down --rmi local: it also removes the built images
docker compose down -v --rmi local
docker images | grep app       # ❌ Image removed

Summary

  • docker compose up -d --build is the most common command — it rebuilds and brings the stack up in the background.
  • docker compose down shuts everything down cleanly. Add -v to remove volumes (data).
  • docker compose logs -f service shows logs in real time — essential for debugging.
  • docker compose ps shows the state and health of every service.
  • docker compose exec service command runs commands inside running containers.
  • docker compose build rebuilds images without bringing the stack up.
  • docker compose restart restarts without rebuilding (it doesn't apply code changes).
  • docker compose config validates your YAML before you bring the stack up — it prevents syntax errors.
  • The development flow is: edit → up --build → logs → test → repeat.
  • You only need to memorize 5 commands: up, down, logs, ps, exec.

Additional Resources

  1. Docker Compose CLI Reference — Complete reference for every command
  2. docker compose up — Detailed documentation for up
  3. docker compose down — Detailed documentation for down
  4. docker compose logs — Logging options
  5. docker compose exec — Running commands in containers
  6. docker compose build — Build options
  7. Docker Compose Cheat Sheet (Docker Blog) — Quick-reference PDF
  8. Docker Compose Watch (File Sync) — Auto-rebuild with file watching