Skip to content

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:

https://<instance-name>.<stack's public domain>

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_host are 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_type record at dns_target for the hostname to actually resolve. dns_state tells 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.state is Pending while a platform-managed certificate is still issuing, Issued once it's live, Failed if issuance failed (reason/message carry the underlying reason verbatim), or Provided if you supplied the certificate yourself — see Certificate source below. Provided is a steady state, not something waiting to become Issued.

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:

GET /apps/{id}/admin-exposure-preview?exposure=Internal
Authorization: Bearer <token>
{
  "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 failed instance'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.