Lesson 8 of 12 · Build the app
Run a Postgres database with Docker Compose
Write one small file, run one command, and have a real Postgres database running on your laptop. Learn where the password lives and why it never goes in Git.
Your app needs somewhere to remember things. That is a database, and the one we use is Postgres. Instead of installing Postgres on your laptop, which is messy and hard to undo, you will run it inside Docker: a sealed box that starts with one command, stops with one command, and can be thrown away without a trace.
Everything running (Lesson 6, “Stopping and coming back”), plus Docker Desktop open and running. Check the whale icon.
What Docker Compose is, in one paragraph
Docker runs programs in boxes called containers. Docker Compose reads a small file called docker-compose.yml that describes which boxes you want and how they are set up, and starts them all with one command. For this course the file describes one box: Postgres, with a username, a password, a database name, and a folder where it keeps its data so that stopping the box does not lose anything.
Step 1: Ask Claude Code to create the file
In Claude Code, plan mode on:
Add a docker-compose.yml at the root of the project that runs a single Postgres 16 database for local development.
- Service name: db
- Username: app
- Password: localdev
- Database name: client_tracker
- Expose port 5432 on my laptop
- Keep the data in a named volume so it survives restarts
- Restart the container automatically unless I stop it
Then create a .env file with DATABASE_URL pointing at that database, and make sure .env is listed in .gitignore so it is never committed. If a .env.example file is a good idea, create one with a placeholder password.
Do not install any packages and do not change any app code. Tell me the plan first.
Read the plan. It should be: one new docker-compose.yml, one new .env, possibly a .env.example, and a line in .gitignore. Nothing else. Approve it with manually approve edits.
The compose file it writes should look roughly like this. You do not have to type it; you are reading it so that it is not a mystery.
services:
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: localdev
POSTGRES_DB: client_tracker
ports:
- "5432:5432"
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
And the .env file should contain one line:
DATABASE_URL="postgresql://app:localdev@localhost:5432/client_tracker"
That line is the address of your database, with the username and password inside it. Anything that knows this line can read and write your data. On your laptop, with a made-up password, that is fine. It is still the habit that matters: this line never goes into Git.
Step 2: Start the database
In a third terminal window (the first two are busy), go to the project and run:
cd ~/projects/client-tracker
docker compose up -d
The first time, Docker downloads Postgres (a minute or so). Then it prints a line saying the container started. -d means “in the background,” so you get your terminal back.
Prove it is running:
docker compose ps
You should see a row for db with a status of “running” or “Up”. Open Docker Desktop: under Containers there is a client-tracker group with a green db inside it.
Step 3: Prove the app can reach it
You have not connected the app yet; that is Lesson 9. But you can prove the database is answering. Ask Claude Code:
Without installing anything or changing app code, check that the Postgres database from docker-compose.yml is reachable using the DATABASE_URL in .env. Use a docker command if that is the simplest way. Tell me what you ran and what it means.
It will most likely run something like docker compose exec db psql -U app -d client_tracker -c "select 1" and show you a result with a 1 in it. That 1 means: the database is up, the username and password work, and the database called client_tracker exists.
Step 4: Learn the three commands
You will use these for the rest of the project. Say them to Claude Code or type them yourself; both work.
| What you want | Command |
|---|---|
| Start the database | docker compose up -d |
| Stop it (data is kept) | docker compose down |
| Stop it and delete all its data | docker compose down -v |
The third one is your reset button. When the database gets into a state you do not understand, and it will, down -v then up -d gives you a clean, empty one. Ten years ago that meant reinstalling Postgres. Now it is one command. Just know that it is a delete.
Step 5: Check what Git can see
This is the important one. Ask:
Show me which files are new or changed and would be included in a commit. Confirm that .env is NOT in that list.
If .env shows up as a file to be committed, stop and say: “Add .env to .gitignore and make sure it is not staged.” Do not commit until it is gone from the list.
Then:
Commit with the message "Local Postgres via Docker Compose" and push.
Go to github.com, open the repository, and confirm there is a docker-compose.yml and no .env file. Also confirm .env.example is there; that is what it is for. If it is missing, the generated .gitignore is ignoring every file that starts with .env. Ask: “Make .gitignore keep ignoring .env but allow .env.example, then commit and push.” Check github.com again.
Why this matters more than it seems. Leaked secrets are a large share of how AI-built apps get breached: Escape found more than 400 leaked secrets across 5,600 live vibe-coded apps (Escape, October 2025). Not clever hacking: a
.envfile committed to a public repository and found by a scanner within minutes. The habit you just practiced, checking the list before every commit, is the whole defense. You will do it again in Lesson 11, and the exposed API keys guide covers what to do if it has already happened.
Check your work
docker compose psshowsdbrunning.- Docker Desktop shows a green container.
- The “select 1” style check returned a
1. .envexists on your laptop and is not on github.com.docker-compose.ymlis on github.com.
If something went wrong
“Cannot connect to the Docker daemon.” Docker Desktop is not running. Open it, wait for the whale to settle, try again.
“port is already allocated” or “address already in use” for 5432. Something else on your laptop is using the Postgres port, usually an old Postgres install. Simplest fix: ask Claude Code to “change the compose file to expose port 5433 on my laptop instead of 5432, and update DATABASE_URL to match.” Then docker compose up -d again.
docker compose up downloads forever or fails. Internet. Try again. If it keeps failing on a Mac, Docker Desktop → Settings → Resources and check it has at least 2 GB of memory. On Windows that limit is controlled by WSL, not Docker Desktop; ask a person.
The database starts but the reachability check fails with “password authentication failed.” The password in .env and the one in docker-compose.yml do not match, or the container was created earlier with a different password and kept it in the volume. docker compose down -v then docker compose up -d resets it.
Windows: the volume or restart gives permission errors. Make sure Docker Desktop has “Use the WSL 2 based engine” ticked (Settings → General). If you changed it, restart Docker Desktop.
Next
Lesson 9 adds Prisma, the translator, and creates the actual clients table in that database.
Did this lesson not go to plan?
A CTO can help you starting today.
Screenshot the error, note which step you were on, and request a call. Every member starts with a call: bring what you built and where it stopped, and that is where we work out whether we can help and what to do first. No card. No sales deck.
Request a call with your CTO