sehaj/learn
Docker/06 Compose

06.06 · build · 25 min

Project: one-command dev environment

Replace nine docker commands with one: the whole notes stack, healthy in order, seeded with data, and reloading as you edit.

The brief

A new contributor clones the repo and runs one command. Done means:

Seeding with init scripts

The official Postgres image runs any .sql or .sh file it finds in /docker-entrypoint-initdb.d/, once, when it creates a new database in an empty data directory. Mount a seed file there:

db/seed.sql
-- Runs once, when Postgres initialises an empty data volume (see 06.06).
-- The API also creates this table at startup; IF NOT EXISTS keeps the two in agreement.
CREATE TABLE IF NOT EXISTS notes (
id uuid PRIMARY KEY,
text text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);

INSERT INTO notes (id, text) VALUES
(gen_random_uuid(), 'Welcome to the notes API. This note came from db/seed.sql.'),
(gen_random_uuid(), 'Edit api/src/server.ts while docker compose watch runs, and the API restarts.'),
(gen_random_uuid(), 'docker compose down keeps these notes. docker compose down -v deletes them.');
compose.yaml
db:
  volumes:
    - pgdata:/var/lib/postgresql
    - ./db/seed.sql:/docker-entrypoint-initdb.d/seed.sql:ro

The whole file

project/docker/compose.yaml puts together everything from this module: the four services, both networks, the volume, healthchecks with service_healthy ordering (06.03 Startup order and healthchecks), variables with defaults (06.04 Env files and profiles), the dev build target with watch rules (06.05 A dev loop with compose watch), and Adminer behind the tools profile. One change from step 4: the API is now the service api, so the Nginx config proxies to http://api:3000.

Run it

❯terminal
$ cd project/docker
$ docker compose up --watch
Network notes_backend Created
Network notes_frontend Created
Volume notes_pgdata Created
...
⦿ Watch enabled
Attaching to api-1, cache-1, db-1, proxy-1
Container notes-cache-1 Started
Container notes-db-1 Started
Container notes-cache-1 Waiting
Container notes-db-1 Waiting
cache-1 | 1:C 02 Nov 2026 20:53:47.202 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
...

Output trimmed. Watch is enabled straight away, then every service's logs stream into this terminal, prefixed with its name, while Compose works through the health waits. Give it a few seconds, then in another terminal:

❯terminal
$ curl -s localhost:8080/notes | jq -r '.[].text'
Welcome to the notes API. This note came from db/seed.sql.
Edit api/src/server.ts while docker compose watch runs, and the API restarts.
docker compose down keeps these notes. docker compose down -v deletes them.

(Without jq, plain curl localhost:8080/notes shows the same notes as JSON.)

What survives what

❯terminal
$ curl -X POST localhost:8080/notes -H 'content-type: application/json' -d '{"text":"added after seeding"}'
$ docker compose down
$ docker compose up -d
$ curl -s localhost:8080/notes | jq length
4
$ docker compose logs db | grep -i initialization
db-1 | PostgreSQL Database directory appears to contain a database; Skipping initialization
$ docker compose down -v
Volume notes_pgdata Removed
$ docker compose up -d
$ curl -s localhost:8080/notes | jq length
3
$ docker compose logs db | grep initdb
db-1 | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/seed.sql

down keeps the volume, so the fourth note survives and the seed doesn't run again. down -v deletes it; the next up initialises a fresh database and runs the seed, giving you back exactly three notes. That makes docker compose down -v && docker compose up a one-line "reset my dev data".

Predict first

You edit db/seed.sql to add a fourth sample note and run docker compose up -d. The note doesn't appear. Why?

Commit to an answer before you read on.

Nine commands, now one

Step 4 needed docker build, two network create, a volume create, four docker run and a network connect, typed in the right order with names repeated across them. Step 5 is docker compose up --watch, and the order, names and health waits live in a file that's reviewed and versioned with the code.

CheckpointProject step 5: one-command dev environment
  • Write compose.yaml, db/seed.sql, .env.example and the dev Dockerfile stage (or check out docker-step-05).
  • From a fresh clone, docker compose up --watch should give you three seeded notes through localhost:8080.
  • Edit the API and see it reload; reset the data with down -v; open Adminer with --profile tools.
STUCK? ▸

Compare with the reference: git diff docker-step-05 -- project/docker. Port 8080 in use: set PROXY_PORT in .env. API never becomes healthy: docker compose logs api, usually a DATABASE_URL typo.

What's next

This is a development setup: the dev target, no resource limits, passwords in .env. Module 07 makes the image production-ready, ships it from CI, and deploys it to a server.

Interview questions

MidfreqGood to knowDesign

How would you make a project's local environment reproducible for every developer?

Report a mistakeEdit this page
☰ Compose06.06 · 37/44next →