Spin up Postgres locally — in five minutes
A reproducible local environment so the rest of the course has somewhere to run. Plus the only `docker compose` debug trick you actually need.
Prerequisite: Docker Desktop installed and running (docker.com/products/docker-desktop). The first install on Windows or macOS takes ~10 minutes. If Docker isn't ready when you start this lesson, pause here and come back — the rest of this lesson assumes a working docker compose.
Why a local stack matters
If the only place your code runs is production, you have no playground for performance experiments and no way to reproduce a bug. Worse, you start "testing in prod" — every senior engineer has at least one war story that starts with "I just wanted to check…".
The junior version of me once truncated a customer table on prod because the prod and staging shells were both on the same monitor and I clicked the wrong tab. We had backups. We did not have my self-respect.
A throwaway local stack pays for itself the first time you almost did that.
# Save as docker-compose.yml in a folder you don't care about.
services:
postgres:
image: postgres:16.4 # pin minor; "latest" is a future bug report
environment:
POSTGRES_USER: codabra
POSTGRES_PASSWORD: codabra
POSTGRES_DB: codabra
ports: ["5432:5432"]
volumes: ["pgdata:/var/lib/postgresql/data"]
healthcheck:
test: ["CMD", "pg_isready", "-U", "codabra"]
interval: 5s
retries: 10
# Object storage stand-in for the lake parts of the course.
# Any recent MinIO RELEASE.* tag works; we pin for reproducibility.
minio:
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: minio12345
ports: ["9000:9000", "9001:9001"]
volumes: ["miniodata:/data"]
volumes:
pgdata:
miniodata:Run docker compose up -d then docker compose ps. Both services should say healthy (or at least running). Connect:
psql postgresql://codabra:codabra@localhost:5432/codabra -c 'SELECT 1;'
If you see 1, you have a real database to talk to. The whole rest of this course assumes this works.
A word on :latest
The Postgres image above is pinned to 16.4. There is exactly one acceptable use of :latest in production: never.
A team I knew pinned postgres:14 (major-version only) and expected stability. Six months later a minor patch in the 14.x line shipped a planner behavior change that made one of their queries several times slower. Production paged at 3 AM. Fix: pin the minor version, not just the major. Better yet, pin the digest.
The rule: pin to the version you have actually tested. Updates are a deliberate act, not a side effect of a rebuild.
The one docker compose trick that pays for itself: when something is wrong, docker compose logs --tail=200 -f <service> is the first command, not the tenth. Don't docker exec in to poke at processes — read the logs. 80% of "why doesn't it start" is right there.
-- inside psql:
\? -- list every backslash command
\h SELECT -- SQL syntax help (here for SELECT)
\dt -- list tables in current schema
\d customers -- describe one table (columns, types, indexes)
\timing on -- print query duration after every statement
\x auto -- pretty-print wide rows
\q -- quitYour first real query: count how many customers we have. Return a single row with one column `total` containing the count of rows in `customers`.
You add `image: postgres:16` to your compose file. Six months later a teammate's CI builds suddenly behave differently. What's the most likely cause?
Checkpoint — before moving on, confirm:
docker compose up -dstarts cleanly.psql postgresql://codabra:codabra@localhost:5432/codabra -c 'SELECT version();'prints a Postgres version string.- You know how to read logs (
docker compose logs -f postgres).
If any of those fail, fix it now — every later module assumes this works.