DevLogTechnical writing
Tutorials

Getting Started with Docker Compose for Multi-Container Apps

October 11, 2026

Building a real application rarely means running a single process. A typical stack pairs a web service with a database, a cache, and often a message queue. Starting each one by hand with docker run, juggling ports, networks, and restart flags, works for a demo and turns painful the moment something else needs adding. Docker Compose solves this by describing your whole stack in one declarative file and managing it as a single unit.

Note: this guide covers Compose v2, the current plugin that ships with Docker and runs as docker compose (with a space). Early tutorials show docker-compose (with a hyphen), the legacy standalone binary — Docker deprecated v1 after June 2023 and has since dropped it from Desktop and CI images. Treat the hyphenated form as deprecated — you want docker compose.

The compose file

Create a docker-compose.yml (Compose also accepts compose.yaml) alongside your project. It uses the modern format, which opens directly with services: — no version: key needed (Compose v2 ignores it anyway):

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Two services, one command. web publishes port 80 inside the container to 8080 on your host, while db keeps its credentials simple for local development and stores its data in a named volume so it survives containers being recreated.

The daily workflow

Start the stack in the background — detached, so it keeps running after your terminal closes:

docker compose up -d

This pulls what's needed, creates the network and volumes, and starts both services. When you change the file later, running it again applies only the changes to affected containers.

Check what's running:

docker compose ps

This lists each service's container, its state, and the ports it's mapped to. To watch what the app and database are actually doing:

docker compose logs -f

The -f flag follows the output live, streaming logs from all services into one terminal — the easiest way to see the whole stack at once. Add a service name to target one, e.g. docker compose logs -f web.

While things run, you can reach inside a container. To open a shell in the web container:

docker compose exec web sh

Use exec when the service is already running and you need to inspect state or run a quick command — it behaves like a normal process, not a new container. To talk directly to the database:

docker compose exec db psql -U app -d app

Tearing it down

When you're done, stop everything cleanly:

docker compose down

This stops the containers and removes the network Compose created, returning your machine to its pre-stack state. Your named volume (the data) is kept, so the next up finds your database exactly where you left it. If you ever want to start over and wipe the data too, docker compose down -v removes the volumes as well — use that one carefully.

Where to go next

Compose handles far more than two services: watch mode for live-reloading code, health checks to control startup order, and profiles to run different subsets of your stack. Master the five commands above first — they carry you through ninety percent of day-to-day work with a Compose project, whether it's a two-service prototype or the foundation of a larger production deployment.

← More Tutorials