Set up PostgreSQL in Docker
Start a local PostgreSQL database, scaffold a Drizzle service, validate its environment, and apply the first migration.
Connect an existing RELKIT app to PostgreSQL running in local Docker. This is the default database setup for a new module. The Orders tutorial remains a separate SQLite walkthrough; keep its database and migration history when continuing that app.
Pre-setup
You need a working minimal app, Bun, and Docker with Compose. Run these commands in the backend root. This walkthrough adds a database only when the app has no database service. For an existing service, retain its dialect, tables, credentials, and applied migrations.
Start PostgreSQL
Check the daemon, then create one local password:
docker version
openssl rand -hex 24Add the following values to your untracked .env.local, replacing both placeholders with
the same generated password. Preserve other entries. Port 5433 must be free; if you change
it, change both the connection URL and Compose port mapping.
RELKIT_LOCAL_DATABASE_PASSWORD=PASTE_GENERATED_HEX
DATABASE_URL=postgres://relkit:PASTE_GENERATED_HEX@127.0.0.1:5433/relkitCreate compose.local.yaml, or merge this service and volume into your existing Compose file:
services:
database:
image: postgres:16-alpine@sha256:cf78e76683b9ca8c5733cbbdce6c9262b45b6767934dd0a95e671f9a0fc20685
environment:
POSTGRES_DB: relkit
POSTGRES_USER: relkit
POSTGRES_PASSWORD: ${RELKIT_LOCAL_DATABASE_PASSWORD:?Set a local database password}
ports:
- "127.0.0.1:5433:5432"
volumes:
- database:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U relkit -d relkit"]
interval: 2s
timeout: 3s
retries: 30
volumes:
database:This pins the same PostgreSQL image used by RELKIT’s local job infrastructure. The named volume keeps data across stops; the published port accepts only loopback connections. Start it:
docker compose --env-file .env.local -f compose.local.yaml up -d --wait database
docker compose --env-file .env.local -f compose.local.yaml exec database pg_isready -U relkit -d relkitExpect accepting connections. RELKIT has no docker(postgresql()) integration; Compose owns
this database lifecycle. Changing POSTGRES_PASSWORD does not rotate an initialized volume’s
password. Keep the original value across restarts.
Scaffold the database service
bun run relkit add database --orm drizzle --dialect postgresqlThe command installs the selected Drizzle packages and declaration repair, creates
src/services/database/service.ts, adds an items table in its schema directory, and creates
drizzle.config.ts. It also merges the database environment declaration. It does not start
PostgreSQL or apply migrations. See the command reference.
Check the environment contract
Keep DATABASE_URL: env.secret() in the generated src/platform/env.ts and pass that
environment descriptor to defineApp({ env }) in relkit.config.ts. The same configuration
can be expressed inline as this complete minimal example:
/** Validate the database connection before activating the Drizzle service. */
import { defineApp, defineEnv, env } from "@relkit/app/config";
export default defineApp({
env: defineEnv({ DATABASE_URL: env.secret() }),
});Merge the field into your app’s existing contract; retain its ID and other settings. The
generated Drizzle client reads context.env.DATABASE_URL through its client({ env }) factory.
There is no database profile to add to defineApp: the specialized service registers it.
RELKIT_LOCAL_DATABASE_PASSWORD belongs to Compose and need not be exposed to application functions.
bun run relkit env checkBun loads .env.local; .env.example is only a template. Drizzle Kit and the app must use
the same DATABASE_URL. Keep production credentials out of this local file and browser bundles.
Generate and apply a migration
Review the scaffolded table, then generate the initial migration:
bun --bun drizzle-kit generate --name=initialReview the SQL and snapshots in src/services/database/migrations. Apply the reviewed history
to the local database and start the app:
bun run relkit db migrate
bun run check
bun run typecheck
bun run devExpect successful migration, checks, and server readiness. In another terminal, verify that the scaffolded table exists:
docker compose --env-file .env.local -f compose.local.yaml exec database psql -U relkit -d relkit -c '\dt'Expect items in the table list. If you renamed the schema table, check its new name instead.
check, build, and dev never apply migrations for you.
Before running application tests, update existing HTTP fixtures to import
createTestApplication from @relkit/testing/drizzle and supply an explicit test environment.
The harness does not inherit .env.local. Use a separate test database and apply its migrations;
follow database testing before changing the fixtures.
Stop and resume without resetting data
Stop the application with Ctrl+C, then stop its database:
docker compose --env-file .env.local -f compose.local.yaml stop database
Run the earlier up -d --wait command to resume with the same volume and password. Keep
your schema, migration history, and volume; do not remove volumes to resolve a migration error.
Continue with Drizzle services, schema definitions, or authentication setup.