Running the Stack Locally
One compose file runs your service with Postgres and Redis, wired by service name and gated on health checks.
docker compose up --build -d && docker compose logs -f apiYour 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
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.
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.
# 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: debugDay-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.
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