Postgres
Default store.sql image — POSTGRES_* env, PGDATA path, and when DATABASE_URL points at PgDog instead.
Postgres is the default store.sql image in dev and prod. oke docker matches
any image whose reference contains postgres or pgvector (after more specific
recipes), then derives env, healthcheck, and connection URL.
The one rule
The driver id stays postgres no matter which Postgres-wire image you pin — vendor choice lives
in images["store.sql"], not in drivers.store.sql.
Quick start
Pin the image
images: {
"store.sql": "postgres:18-alpine", // any Postgres / pgvector image
},Bring the stack up
oke devoke docker writes POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB into
.env.local as ${OKE_STORE_SQL_USER} / ${OKE_STORE_SQL_PASSWORD} /
${OKE_STORE_SQL_DB} refs — never literal secrets in compose YAML.
Required env
| Variable | Who sets it | Meaning |
|---|---|---|
OKE_STORE_SQL_USER | oke docker → compose | Injected as POSTGRES_USER |
OKE_STORE_SQL_PASSWORD | oke docker → compose | Injected as POSTGRES_PASSWORD |
OKE_STORE_SQL_DB | oke docker → compose | Injected as POSTGRES_DB |
DATABASE_URL | stack env for the app | What Bun.SQL / Drizzle actually open |
PGDATA | stack default | /var/lib/postgresql/data/pgdata |
POSTGRES_INITDB_ARGS | stack default | --data-checksums |
Data and backup
| Path | What lives there |
|---|---|
$PGDATA (/var/lib/postgresql/data/pgdata) | Cluster data directory |
Image VOLUME /var/lib/postgresql/data | Official image mount — Docker attaches a volume here |
The recipe does not declare a named compose volume. Persistence rides the image's
VOLUME (anonymous unless you add a named mount in compose.override.yml).
Backup means: pg_dump / pg_dumpall, or a Docker volume backup of
/var/lib/postgresql/data. Losing that volume loses the cluster.
Production note
For multi-replica apps, keep SQL on a shared Postgres — Clock CronStore and durable journal need one backend. See Clock.
Scale out with PgDog so N × Bun.SQL pool does not exhaust
max_connections. Do not also stack a managed provider's pooler on the same URL.
What the recipe configures
| Field | Value |
|---|---|
| Container port | 5432 |
| Healthcheck | pg_isready -U $POSTGRES_USER, every 5s, 10 retries |
| Connection URL | postgres://user:pass@host:5432/db |
| Preload | postgres -c shared_preload_libraries=pg_stat_statements |
Query performance
Preload, then create
pg_stat_statements must load at postmaster start. CREATE EXTENSION alone is not enough.
Recreate store-sql after a recipe change — the data volume can stay.
| Step | What you do |
|---|---|
| Preload | Recipe already sets shared_preload_libraries=pg_stat_statements |
| Create | CREATE EXTENSION IF NOT EXISTS pg_stat_statements |
| Console | Store → SQL band → Performance |
Default postgres:18-alpine does not ship hypopg / index_advisor. Pin the
opt-in image when you want Suggest indexes:
images: {
"store.sql": "oke-postgres-advisor:18-alpine",
},oke docker writes Dockerfile.postgres-advisor and a compose build:. Driver id
stays postgres. Suggest copies CREATE INDEX DDL — it does not create indexes.
Troubleshooting
The postgres driver fails boot when neither DATABASE_URL nor OKE_STORE_SQL_URL is
set. Re-run oke dev so the stack writes .env.local, or export
DATABASE_URL yourself when pointing at a managed host.
The Console Performance view needs the library preloaded and the extension
created. Recreate store-sql so the new command applies, then run
CREATE EXTENSION IF NOT EXISTS pg_stat_statements.
Existing clusters that started before this recipe change keep the old postmaster flags until recreate.
Wrong POSTGRES_USER or the container is still initializing. Check
docker compose … logs store-sql for database system is ready to accept connections.
Credential refs must resolve in .env.local — empty ${OKE_STORE_SQL_PASSWORD} leaves
Postgres refusing auth.
Learn more
- PgDog — transaction pooling in front of this recipe
- Store · SQL — schema push / generate / migrate
- Environment variables —
DATABASE_URLprecedence - Neon · Supabase — managed alternatives