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 psql works).

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).

  • [ ] .env has DATABASE_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 runserver and /api/health works.

Checkpoint — you are done when

  • [ ] DATABASE_URL (or equivalent) selects Postgres
  • [ ] migrate applied 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