Recipes

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

oke.config.ts
images: {
  "store.sql": "postgres:18-alpine", // any Postgres / pgvector image
},

Bring the stack up

oke dev

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

Connect

# App reads this (also accepts OKE_STORE_SQL_URL)
echo "$DATABASE_URL"
# postgres://oke:…@127.0.0.1:5432/oke

When PgDog is pinned, DATABASE_URL points at :6432 instead — see PgDog.

Required env

VariableWho sets itMeaning
OKE_STORE_SQL_USERoke docker → composeInjected as POSTGRES_USER
OKE_STORE_SQL_PASSWORDoke docker → composeInjected as POSTGRES_PASSWORD
OKE_STORE_SQL_DBoke docker → composeInjected as POSTGRES_DB
DATABASE_URLstack env for the appWhat Bun.SQL / Drizzle actually open
PGDATAstack default/var/lib/postgresql/data/pgdata
POSTGRES_INITDB_ARGSstack default--data-checksums

Data and backup

PathWhat lives there
$PGDATA (/var/lib/postgresql/data/pgdata)Cluster data directory
Image VOLUME /var/lib/postgresql/dataOfficial 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

FieldValue
Container port5432
Healthcheckpg_isready -U $POSTGRES_USER, every 5s, 10 retries
Connection URLpostgres://user:pass@host:5432/db
Preloadpostgres -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.

StepWhat you do
PreloadRecipe already sets shared_preload_libraries=pg_stat_statements
CreateCREATE EXTENSION IF NOT EXISTS pg_stat_statements
ConsoleStore → SQL band → Performance

Default postgres:18-alpine does not ship hypopg / index_advisor. Pin the opt-in image when you want Suggest indexes:

oke.config.ts
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

Learn more

Next

On this page