Postgres (after Django + uv)
Prerequisite: django-uv.md is done. Strongly prefer finishing django-environment.md first so the database URL lives in .env, not in settings.py.
End state: Django talks to a local Postgres database via DATABASE_URL. Migrations apply; /api/health still works.
Suggested order: after environment settings, before you care about CORS/auth/second app. CORS and auth do not require Postgres. A second app with real models will be happier on Postgres once you leave SQLite behind, but SQLite is still fine for early feature work.
How to use this: get Postgres running, wire Django, migrate, verify. Do not tune connection pools or replicas yet.
0. Why you are here
SQLite is a file. It is excellent for the first day. You switch to Postgres when you need concurrent writes, closer-to-production behavior, or multiple processes talking to one database.
1. Have Postgres available
Pick one. You only need a running server and one empty database.
Option A — Docker (common):
docker run --name myapi-postgres \
-e POSTGRES_USER=myapi \
-e POSTGRES_PASSWORD=myapi \
-e POSTGRES_DB=myapi \
-p 5432:5432 \
-d postgres:16
Option B — local install: use Homebrew/apt/Postgres.app; create a user and database yourself.
- [ ] You can connect somehow (Docker running, or
psqlworks).
Connection URL shape (adjust user/password/host/db):
postgres://myapi:myapi@127.0.0.1:5432/myapi
2. Add the driver
uv add "psycopg[binary]"
- [ ] Run that.
Why psycopg: current Postgres adapter for Django. The [binary] extra avoids a local C build on most machines.
3. Put the URL in .env
If you have not done the environment runbook, do that first — or at minimum load a .env the same way.
Add:
DATABASE_URL=postgres://myapi:myapi@127.0.0.1:5432/myapi
Update .env.example with an empty or placeholder DATABASE_URL= line (no real password if this were production-like).
- [ ]
.envhasDATABASE_URL; it is not committed.
4. Point Django at it
In config/settings.py, replace the SQLite DATABASES block with:
DATABASES = {
"default": env.db("DATABASE_URL"),
}
That requires the environ.Env setup from django-environment.md. env.db parses the URL into Django’s DATABASES dict.
If you are temporarily skipping django-environ, the equivalent manual shape is:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "myapi",
"USER": "myapi",
"PASSWORD": "myapi",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}
Prefer the URL form so credentials stay out of source.
- [ ] Save.
About old db.sqlite3: leave it alone or delete it. Django will not use it once DATABASES points at Postgres. Do not commit either database file.
5. Migrate against Postgres
uv run python manage.py migrate
- [ ] Command succeeds (creates Django’s tables in Postgres).
Sanity check:
uv run python manage.py dbshell
If psql is on your PATH, you drop into the DB. Type \dt to list tables, then \q to quit.
- [ ] Server still runs:
uv run python manage.py runserverand/api/healthworks.
Checkpoint — you are done when
- [ ]
DATABASE_URL(or equivalent) selects Postgres - [ ]
migrateapplied cleanly on that database - [ ] API still responds
Stop. Do not add PgBouncer, read replicas, or multi-db routing now.
If you get stuck
| Symptom | Likely cause |
|---|---|
connection refused |
Postgres not running, wrong port, Docker not started |
| password authentication failed | User/password in URL do not match the server |
database "…" does not exist |
DB name in URL never created |
No module named 'psycopg' |
Forgot uv add / not using uv run |
Still writing to db.sqlite3 |
DATABASES not saved / still the SQLite block |
Next
- Browser frontend on another origin →
django-cors.md - Protect endpoints →
django-ninja-auth.md - New feature module →
django-second-app.md