Skip to content

All lessons

Area 07 // DeploymentLesson 110 / 14013 min read

Docker Compose

Docker Compose describes all the containers an app needs (the API, a database, a cache, a local model server) in one compose.yaml file, so a single command starts the whole stack with the right ports, settings, volumes and health checks, on a laptop, in CI or on a small server.

Think of it like this

Analogy

A band's stage plan: one sheet that says where the drums, guitars, microphones and monitors go, how they're wired together, and in what order to switch them on. The crew can set up the whole show in any venue from that one sheet. compose.yaml is the stage plan for your app's containers.

The problem it solves

A real AI app is rarely one container. The API needs Postgres, Redis, maybe a vector database and a local model server for development. Starting each with long docker run commands, remembering ports, networks, environment variables and the right start-up order, is fragile, and every teammate does it slightly differently. You want the whole stack described once and started with one command.

How it works, step by step

  1. Write compose.yaml with a services: section; each service is a container built from your Dockerfile (build:) or pulled from a registry (image:).
  2. Ports: "8000:8000" maps a port on your machine to the container.
  3. Environment: settings per service, with ${VAR:-default} reading from your shell or a git-ignored .env file (environments and config).
  4. Networking: services reach each other by name (http://ollama:11434, postgres:5432) on a private network.
  5. Volumes keep data across restarts: databases, Redis snapshots, downloaded models (Ollama).
  6. Health checks and order: healthcheck: plus depends_on: { db: { condition: service_healthy } }; docker compose up --wait waits until every service is healthy (health checks).
  7. Profiles make optional services opt-in (--profile local-llm).
  8. Commands: docker compose up -d --wait, ps, logs -f app, exec app sh, down (add -v to delete volumes).
  9. Where it fits: local development, CI smoke tests (as in the capstone) and small single-server deployments; clusters use Kubernetes or managed platforms.

See it in code

Needs
Docker with the Compose plugin. The file is examples/rag-app/compose.yaml in the course repository.

The capstone's Compose file (the app, plus an optional local model server), then the real smoke test from the course's CI run.

compose.yaml
services:
  app:
    build:
      context: .
      args: { APP_VERSION: "${APP_VERSION:-compose}" }
    ports: ["8000:8000"]
    environment:
      LLM_PROVIDER: ${LLM_PROVIDER:-mock}
      LLM_MODEL: ${LLM_MODEL:-llama3.2}
      OLLAMA_HOST: http://ollama:11434          # reach the other service by name
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} # from your shell or a git-ignored .env
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/ready')"]
      interval: 5s
      retries: 5
    restart: unless-stopped
 
  ollama:                                       # only with: --profile local-llm
    image: ollama/ollama
    profiles: ["local-llm"]
    volumes: ["ollama-models:/root/.ollama"]    # downloaded models survive restarts
    ports: ["11434:11434"]
 
volumes:
  ollama-models:
Output
$ APP_VERSION=31fab5b docker compose up -d --wait
 Image rag-app-app Built
 Network rag-app_default Created
 Container rag-app-app-1 Started
 Container rag-app-app-1 Waiting
 Container rag-app-app-1 Healthy
$ docker compose ps
NAME            IMAGE         SERVICE   STATUS                   PORTS
rag-app-app-1   rag-app-app   app       Up 5 seconds (healthy)   0.0.0.0:8000->8000/tcp
$ curl -s localhost:8000/ready
{"ready":true,"checks":{"index_loaded":true,"provider_configured":true},"version":"31fab5b"}
$ curl -s -X POST localhost:8000/ask -d '{"question": "How many days of annual leave do I get?"}'
{"answer":"Full-time employees get 25 days of annual leave per year, plus public holidays. [1]", ...}
$ docker compose down
 Container rag-app-app-1 Removed
 Network rag-app_default Removed

This ran inside the capstone's GitHub Actions build job. --wait returned only after the container's health check (which calls /ready) passed, so the curl calls never raced the start-up. The Ollama service didn't start, because its profile wasn't enabled.

A real example

Example

A one-command dev environment

A team's onboarding doc for their RAG product was two pages of install steps for Postgres with pgvector, Redis, the API and a local model. New joiners took a day to get running. They replaced it with a compose.yaml: Postgres (with a volume and a health check), Redis, the API built from the Dockerfile with depends_on conditions, and Ollama behind a local-llm profile so laptops without much memory could skip it. Onboarding became cp .env.example .env && docker compose up --wait, and the same file runs the integration tests in CI.

The easy way

Start here

Put your app and each dependency it needs (database, cache) as services in one compose.yaml, add a health check to each, and use docker compose up -d --wait locally and in CI. Keep secrets in a git-ignored .env.

When you grow: the production setup

Teams keep a base compose.yaml plus override files per purpose (compose.dev.yaml with hot reload and mounted source, compose.ci.yaml for tests), pin image versions, add resource limits, use profiles for heavy optional services, run integration and smoke tests against the Compose stack in CI, and move to Kubernetes or a managed platform when they need multiple machines.

Trade-offs

What you gain

  • Whole stack described in one reviewed file.
  • One command to start, stop and reset.
  • Same setup for laptops and CI.

What it costs you

  • Single machine only; no built-in scaling across servers.
  • Limited self-healing compared with an orchestrator.
  • Heavy services (local models) can exhaust laptop memory.

When not to use it

  • Production systems that need high availability across several machines: use Kubernetes or a managed platform.

Common mistakes

  • Secrets written directly in compose.yaml.
  • No health checks, so tests start before the app is ready.
  • Using localhost to reach another service instead of its service name.
  • Forgetting volumes, then losing the database on down.
  • Running down -v and deleting data by accident.

Interview questions

Try answering out loud first, then open the model answer.

What is Docker Compose used for?

Model answer

Defining and running multi-container applications from one YAML file: services with images or builds, ports, environment, volumes, networks, health checks and dependencies. It's ideal for local development environments, integration and smoke tests in CI, and simple single-host deployments.

How do containers in the same Compose project talk to each other?

Model answer

Compose creates a project network and registers each service's name in DNS, so a container reaches another at http://service-name:port. Ports only need publishing to the host for access from outside the project.

Docker Compose vs Kubernetes?

Model answer

Compose runs containers on one machine with simple declarative config: great for development, CI and small deployments. Kubernetes schedules containers across a cluster with self-healing, rolling updates, autoscaling, service discovery and richer networking and storage: suited to production at scale, at the cost of much more complexity.

Quiz

Pick an answer to see whether it’s right and why.

  1. 01What does docker compose up -d --wait do?
  2. 02How does the app container reach an Ollama container defined in the same compose.yaml?
  3. 03Why put a database's data in a volume?
  4. 04Where should secrets in a Compose setup come from?
  5. 05When is Compose not enough?

0 of 5 answered

Key terms

Docker Compose
A tool that starts several containers together from one compose.yaml file, with their ports, settings, volumes, health checks and network.
Container
A lightweight, isolated package of an app and everything it needs to run, sharing the host's kernel. Docker is the most common tool. Also used as a sandbox.
Volume
Storage managed by Docker that lives outside a container, so data such as databases or downloaded models survives when the container is replaced.
Health check
A small endpoint (like /health or /ready) that the platform calls to decide whether an instance is alive and should get traffic.
Service (Compose)
One named container definition in a compose.yaml file, such as app, db or ollama. Other services reach it by that name.

Before and after

Progress is saved in this browser only.