Running the Stack Locally

One compose file runs your service with Postgres and Redis, wired by service name and gated on health checks.

Syntaxdocker compose up --build -d && docker compose logs -f api

Your service needs a database and a cache to do anything useful. Docker Compose describes all three in one YAML file and starts them with one command, so a new contributor runs docker compose up and has a working stack without installing Postgres.

Services, networks and volumes

Each entry under services: is one container. Compose puts them on a private network where each one is reachable by its service name: your code connects to host postgres, not localhost. ports: maps "host:container" so your browser can reach the service from outside. A named volume is a directory Docker keeps outside the container, so database files survive docker compose down.

Starting in the right order

A health check is a command Docker runs inside a container on a schedule; while it fails, the container is "unhealthy". depends_on with condition: service_healthy makes Compose wait for that before starting your service. Order at startup is not the whole story though: a dependency can restart at any time, so keep a retry loop in your code.

The commands

docker compose up --build builds and starts everything, -d detaches it, docker compose logs -f api follows one service's stdout (the output stream console.log writes to, which Docker collects), and docker compose down -v removes the containers and the volumes. The old top-level version: key is obsolete in Compose v2 and can be deleted.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Give a new contributor a working database and cache from a single command.
  • Run migrations against the same Postgres version production uses.
  • Reproduce a bug that only appears when the cache and the database are both present.
  • Reset a corrupted local database with docker compose down -v.

More examples

compose.yaml for a service, Postgres and Redis

The whole local stack in one file. Note that only Postgres gets a volume: losing the cache on restart is fine, losing the database is not. The credentials are deliberately trivial because nothing here is reachable from outside your machine.

Example · yaml
services:
  api:
    build: .
    ports:
      - "3000:3000"          # host:container
    environment:
      PORT: "3000"
      # The host is the service name, resolved on the compose network.
      DATABASE_URL: postgres://app:app@postgres:5432/app
      REDIS_URL: redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: app
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 2s
      timeout: 3s
      retries: 15
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

volumes:
  pgdata:

A development override with live reload

Keeps the production Dockerfile honest while still giving a fast edit-reload loop. Mount only your source directory: mounting the whole project would hide the container's node_modules behind your host's, which were built for a different operating system.

Example · yaml
# compose.override.yaml is merged automatically by `docker compose up`.
services:
  api:
    # Mount the source over the image's copy so edits are visible at once.
    volumes:
      - ./src:/app/src
    # node --watch restarts the process when a loaded file changes.
    # Added in Node 18.11.0, stable since Node 22.0.0.
    command: ["node", "--watch", "src/server.js"]
    environment:
      LOG_LEVEL: debug

Day-to-day commands

The set worth memorising. docker compose run --rm is how migrations and seed scripts are executed: it starts a throwaway container with the same image, environment and network as the service, so the database address works unchanged.

Example · bash
docker compose up --build            # build images and run in the foreground
docker compose up -d                 # run detached
docker compose ps                    # what is running, and its health state
docker compose logs -f api           # follow one service's stdout
docker compose exec api sh           # a shell inside the running service
docker compose exec postgres psql -U app -d app   # a database prompt
docker compose run --rm api node scripts/migrate.js  # one-off task, then gone
docker compose down                  # stop and remove containers, keep volumes
docker compose down -v                # also delete the volumes (wipes the data)

Discussion

  • Be the first to comment on this lesson.