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:
-- 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.');db:
volumes:
- pgdata:/var/lib/postgresql
- ./db/seed.sql:/docker-entrypoint-initdb.d/seed.sql:roThe 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
$ cd project/docker$ docker compose up --watchNetwork notes_backend CreatedNetwork notes_frontend CreatedVolume notes_pgdata Created...⦿ Watch enabledAttaching to api-1, cache-1, db-1, proxy-1Container notes-cache-1 StartedContainer notes-db-1 StartedContainer notes-cache-1 WaitingContainer notes-db-1 Waitingcache-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:
$ 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
$ 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 length4$ docker compose logs db | grep -i initializationdb-1 | PostgreSQL Database directory appears to contain a database; Skipping initialization$ docker compose down -vVolume notes_pgdata Removed$ docker compose up -d$ curl -s localhost:8080/notes | jq length3$ docker compose logs db | grep initdbdb-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".
You edit db/seed.sql to add a fourth sample note and run docker compose up -d. The note doesn't appear. Why?
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.
- Write
compose.yaml,db/seed.sql,.env.exampleand thedevDockerfile stage (or check outdocker-step-05). - From a fresh clone,
docker compose up --watchshould give you three seeded notes throughlocalhost: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
How would you make a project's local environment reproducible for every developer?