Skip to content

fix(deploy): run the bundled Keycloak on Postgres instead of dev-mode H2 - #1204

Open
njbrake wants to merge 1 commit into
thunderbird:mainfrom
njbrake:pr5-keycloak-on-postgres
Open

fix(deploy): run the bundled Keycloak on Postgres instead of dev-mode H2#1204
njbrake wants to merge 1 commit into
thunderbird:mainfrom
njbrake:pr5-keycloak-on-postgres

Conversation

@njbrake

@njbrake njbrake commented Aug 6, 2026

Copy link
Copy Markdown

Note: this PR description was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

Runs the bundled Keycloak in production mode against Postgres instead of start-dev with file-backed H2.

start-dev implies H2, which Keycloak documents as development only. Combined with an orchestrator that mounts no volume for the service, the realm becomes ephemeral: every redeploy re-imports the realm JSON and discards everything created since. Observed on a live deployment:

  • users added in the admin console vanish
  • master-realm settings such as brute-force protection reset to off
  • there is no login or admin event history to inspect
  • KC_BOOTSTRAP_ADMIN_* becomes load-bearing on every boot, when it exists for the first boot only

Two-stage build so the image can run start --optimized: the database vendor is baked in by kc.sh build, while hostname, DB URL and credentials stay runtime options.

Keycloak gets its own database rather than a schema in the app's. It owns roughly 95 tables managed by Liquibase, which has no business sharing a namespace with the app's Drizzle migrations. postgres-init creates it alongside powersync_storage, so fresh stacks need nothing extra.

One behaviour change worth calling out: --import-realm only applies to an empty database, so realm JSON edits no longer take effect on an existing stack. That is the point, since console changes now survive redeploys, but it moves realm configuration from the file to the console.

_Note: this PR description was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's._

Runs the bundled Keycloak in production mode against Postgres instead of `start-dev` with file-backed H2.

`start-dev` implies H2, which Keycloak documents as development only. Combined with an orchestrator that mounts no volume for the service, the realm becomes ephemeral: every redeploy re-imports the realm JSON and discards everything created since. Observed on a live deployment:

- users added in the admin console vanish
- master-realm settings such as brute-force protection reset to off
- there is no login or admin event history to inspect
- `KC_BOOTSTRAP_ADMIN_*` becomes load-bearing on every boot, when it exists for the first boot only

Two-stage build so the image can run `start --optimized`: the database vendor is baked in by `kc.sh build`, while hostname, DB URL and credentials stay runtime options.

Keycloak gets its own database rather than a schema in the app's. It owns roughly 95 tables managed by Liquibase, which has no business sharing a namespace with the app's Drizzle migrations. `postgres-init` creates it alongside `powersync_storage`, so fresh stacks need nothing extra.

One behaviour change worth calling out: `--import-realm` only applies to an empty database, so realm JSON edits no longer take effect on an existing stack. That is the point, since console changes now survive redeploys, but it moves realm configuration from the file to the console.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant