Managed Keycloak¶
Managed Keycloak runs Keycloak — identity and access management, SSO, and OpenID Connect / SAML — as a fully operated Kubernetes workload. It's built on the same managed-apps framework as Managed Nextcloud and Managed GitLab: placed onto a Stack, driven through the same five-phase lifecycle, with an operator handling installation, upgrades, and ongoing reconciliation. This page covers what's specific to the Keycloak kind; for the shared create flow, Stack concepts, and day-2 mechanics, see Deploying a Managed App and Apps / Service Catalog.
The kind value is Apps::Managed::Keycloak::V1.
Known limitations at this release
- Rotating admin credentials isn't available yet; you can reveal the current credential (see Admin credentials) but not generate a new one from the dashboard or API. Talk to your account manager if you need one rotated in the meantime.
- Realm import and custom identity providers are not available in this release.
Plans¶
| Plan | Keycloak nodes | PostgreSQL | PgBouncer | Price |
|---|---|---|---|---|
| Starter | 2 | standalone | — | €199 / month |
| Professional | 2 | highly available (3 nodes) | — | €349 / month |
| Enterprise | 3 | highly available (3 nodes) | yes | €699 / month |
Prices excl. VAT; business customers only. The plan is chosen when the instance is created (plan on POST /apps, keys starter, professional, enterprise). Changing the plan of a running instance is done by b'nerd on request (support ticket); PostgreSQL scales in place and the instance stays available during the change.
Creating an instance¶
Keycloak follows the same 4-step create flow as every managed kind (Kind → Stack → Configure → Confirm — see Deploying a Managed App for the mechanics). The Configure step's Keycloak-specific fields are:
| Field | spec key | Notes |
|---|---|---|
| Name | — | Required. Also drives the default host (below). |
| Version | version | Dropdown when the Stack's operator publishes a version list; otherwise "managed automatically". |
| Root redirect (Advanced) | root_redirect | Optional path Keycloak redirects / to, e.g. /realms/myrealm/account. |
Instance URL (default domain)¶
Every instance gets a host derived automatically from its name and the Stack it runs on — there's nothing to configure:
For example, an instance named sso on a Stack whose public domain is apps.example-stack.bnerd.cloud comes up at https://sso.apps.example-stack.bnerd.cloud. This is the default for both the public entry point and the admin console — see Custom domains and Admin console exposure below for bringing your own hostname for either.
Custom domains¶
You can serve an instance on your own hostname instead of the platform-assigned default — for the public entry point (public_host), and, independently, for the admin console (admin_host — see Admin console exposure).
Requirements¶
- The domain must be verified.
public_host/admin_hostare only accepted when they are (or are a subdomain of) a domain your organization has verified — see Domain Verification. Verification stays valid for 90 days; if it lapses, the instance doesn't drop the custom hostname immediately — there's a 7-day grace period before it falls back to the default host, so a delayed re-verification doesn't cause certificate churn. - Your Stack's Keycloak operator must be new enough (0.3.4 or later) to carry the per-app listener a custom hostname needs. On an older Stack the request is refused outright, rather than accepted and left not actually serving.
Set it with a PATCH:
PATCH /apps/{id}
Authorization: Bearer <token>
Content-Type: application/json
{ "app": { "spec": { "public_host": "sso.example.com" } } }
Leave public_host null (or omit it) to keep the derived default host — that's always accepted, regardless of any of the requirements above.
A request that doesn't meet the requirements is refused with 422 validation_failed and one of these codes:
code | Meaning |
|---|---|
domain_not_verified | The hostname isn't a verified domain of your organization. The error's meta.detail.domains_url points at Domain Verification. |
unsupported_by_stack_operator_version | Your Stack's Keycloak operator predates 0.3.4. |
internal_gateway_cannot_admit_listeners | The hostname targets an internal (VPN-only) entry point and that Stack's internal gateway doesn't support per-app listeners yet. This is the case an internal admin console hostname can hit. |
Checking status¶
GET /apps/{id} includes a custom_domains array — one entry per field currently in effect (public, and admin if you've set one):
{
"field": "public",
"host": "sso.example.com",
"dns_target": "203.0.113.10",
"dns_record_type": "A",
"dns_state": "public",
"certificate": {
"state": "Issued",
"reason": null,
"message": null,
"observed_at": "2026-08-28T09:00:00Z"
}
}
- Point a
dns_record_typerecord atdns_targetfor the hostname to actually resolve.dns_statetells you which network that address belongs to:"public"(the internet-facing load balancer) or"internal"(the VPN-only one) — check it before publishing a public DNS record, since an internal target is never reachable from the public internet. certificate.stateisPendingwhile a platform-managed certificate is still issuing,Issuedonce it's live,Failedif issuance failed (reason/messagecarry the underlying reason verbatim), orProvidedif you supplied the certificate yourself — see Certificate source below.Providedis a steady state, not something waiting to becomeIssued.
Certificate source¶
By default the platform obtains and renews the certificate for your custom hostname automatically (Let's Encrypt) — there's nothing to do. If your hostname isn't reachable from the public internet (an internal admin-console hostname, most commonly), automatic issuance can't work, and you supply your own certificate instead. The platform's Artifact Store is the supported way to do this: upload a tls_cert artifact to your organization and attach it to this app. A supplied certificate has no renewal loop on the platform side — replacing it before it expires is on you.
Admin console exposure¶
By default the admin console shares the instance's public host, reachable at the same URL as everything else — protected by login, not by network placement. You can instead isolate it:
| Exposure | Behavior |
|---|---|
Public (default) | The admin console is served on the same entry point as the rest of the instance. |
Internal | The admin console is served from a separate, private entry point (typically reachable only over VPN) — never the public one. |
Switch it with a PATCH:
PATCH /apps/{id}
Authorization: Bearer <token>
Content-Type: application/json
{ "app": { "spec": { "admin_exposure": "Internal" } } }
You can preview the resulting admin hostname for either value before committing, without changing anything:
{
"metadata": { "organization_id": "<uuid>", "project_id": "<uuid>", "timestamp": "..." },
"data": {
"exposure": "Internal",
"current_admin_host": "sso.apps.example-stack.bnerd.cloud",
"new_admin_host": "admin-sso.internal.example-stack.bnerd.cloud"
}
}
The preview is read-only and available to anyone who can read the app. Switching exposure itself, and setting an admin custom domain (below), are both organization-admin-only — a project admin cannot do either, even one with full apps:update permission on the project. A request from anyone else is refused with 403 forbidden.
Admin custom domain¶
Independently of exposure, you can also give the admin console its own custom hostname (admin_host) — the same verification and operator-version requirements from Custom domains apply, field-scoped to admin_host, and setting it is under the same organization-admin-only gate as switching exposure:
PATCH /apps/{id}
Authorization: Bearer <token>
Content-Type: application/json
{ "app": { "spec": { "admin_host": "admin.example.com" } } }
Its status shows up in the same custom_domains array, as the entry with "field": "admin".
Admin credentials¶
Keycloak's built-in admin account is handled the same write-only way as any managed kind's credential (see Credentials are write-only), with one addition specific to Keycloak: you can reveal it again later, on demand, from the instance's detail page.
- The credential is shown once at create time (generate or set your own), the same as any managed-app credential.
- After that, the value is masked. Project admins of the app's project, and platform admins, can click Reveal to see it again — a confirmation dialog makes clear that doing so is recorded.
- Every reveal is written to the account's audit log with the actor, so there's a record of who looked at the admin credential and when. This is an absolute, non-delegatable permission: only actual project admins of the app's project and platform admins can reveal it — a project admin cannot extend this to another member the way other project permissions can be granted.
- Reveal is rate-limited per instance to discourage scripting around the audit trail.
Rotating the credential (generating a fresh password) is not yet available — see the limitations note above.
Status & health¶
Keycloak instances use the same five-phase lifecycle as every managed app (see The instance lifecycle). The instance detail page additionally shows, specific to Keycloak:
- Requested vs. running version — the version you asked for alongside the version the operator has actually observed running, so a mismatch during an upgrade is visible rather than silent.
- Conditions — a list of the operator's own health signals (type, status, reason, and how long each has held), shown newest-affecting-first. This is where a
failedinstance's underlying reason shows up in more detail than the top-level failure message.
Day-2 changes¶
From the instance's Configuration tab you can change version and root redirect. Each change is submitted the same way as at create time and re-enters the provisioning phase while the operator reconciles, the same as changing an instance's version for any managed kind.
The same tab is also where you manage custom domains and admin console exposure — both day-2 changes, not create-time settings.
Deletion protection & delete¶
Keycloak instances use the platform's standard two-step deletion flow — see Day-2: delete an instance. Deletion protection is on by default; there is no separate Keycloak-specific behavior here.
API reference¶
The full request/response shape for Keycloak's spec fields is documented in the interactive API reference alongside every other app kind, generated directly from the platform's OpenAPI spec. If a field described on this page isn't showing there yet, the reference hasn't been regenerated for this release — check back shortly.