CRD Reference — bnerd-keycloak-operator¶
API group: k8s.bnerd.com/v1alpha1
KeycloakInstance¶
Namespaced | Short name: kci
One CR = one Keycloak deployment (StatefulSet + Services + optional Ingresses).
Spec fields¶
| Field | Type | Required | Description |
|---|---|---|---|
spec.version |
string | no | Keycloak version to deploy ("26.6.4", "stable", "latest", "26"). Resolved via KeycloakVersionMap/default. Defaults to the latest alias when empty. Ignored when spec.image is set. |
spec.replicas |
int32 | no | StatefulSet replica count. Defaults to 2 (the reference default); must be >= 1 when set. |
spec.hosts.host |
string | yes | Public account/auth hostname. Drives KC_HOSTNAME=https://<host> and the public Ingress rule. Structurally required even when spec.ingress.enabled: false. |
spec.hosts.adminHost |
string | no | Admin console hostname. Empty = no admin Ingress and KC_HOSTNAME_ADMIN is left unset. |
spec.ingress.enabled |
bool | no | Defaults to true. false = render Services only (dev/kind); no Ingress objects are created. |
spec.ingress.class |
string | no | Public Ingress class. Defaults to "nginx". |
spec.ingress.adminClass |
string | no | Admin Ingress class. Defaults to "nginx-internal". |
spec.ingress.clusterIssuer |
string | no | cert-manager.io/cluster-issuer annotation value. Empty = no TLS annotation/secret on either Ingress (plain HTTP, dev/kind). |
spec.ingress.annotations |
map[string]string | no | Extra annotations merged onto both Ingresses; the operator's own security-snippet and (if set) cluster-issuer annotations always take precedence. |
spec.postgres.managed |
bool | no | true = operator applies a PerconaPGCluster named <name>-pg (capability-gated on the pgv2.percona.com CRD). false = BYO via credentialsSecret. |
spec.postgres.topology |
"" | ha | standalone |
no | Managed-only. ha provisions 3 Postgres instance replicas; anything else (including empty) provisions 1. Rejected at validation if set to any other value. |
spec.postgres.pgbouncer |
bool | no | Managed-only. Enables the PgBouncer proxy sidecar (2 replicas) in the PerconaPGCluster; when false the proxy block is still emitted (Percona 2.8.2 requires it) with 0 replicas. |
spec.postgres.nodes |
int | no | Managed-only. Overrides the Postgres instance replica count derived from topology: 0 (default) uses the topology default (ha → 3, standalone → 1); any value > 0 wins over it. Must be >= 0; setting it with managed: false is rejected at validation. |
spec.postgres.credentialsSecret |
string | required when managed: false |
BYO Secret name. Keys: host, dbname, user, password (+ optional port). A Secret missing a required key permanently fails the instance (CredentialsSecretInvalid). |
spec.adminSecret |
string | no | Name of a pre-existing bootstrap admin Secret (keys username, password). Empty (default) = the operator generates <name>-admin-credentials once (random 32-char password, never rotated) and tracks it in status.secrets for finalizer GC. |
spec.providers[].name |
string | yes, per entry | Unique, DNS-label-safe suffix; the initContainer is named provider-<name>. |
spec.providers[].image |
string | yes, per entry | Image carrying the JARs to inject (theme, SPI provider, ...). Must run as a numeric non-root user (e.g. USER 1000:1000) — the pod's runAsNonRoot: true policy makes the kubelet reject root (or unresolvable-username) provider images, and the pod never starts. Its initContainer copies path/*.jar into the shared providers volume mounted internally at /bnerd-providers (distinct from path so the empty volume never shadows the image's JARs); Keycloak reads them at /opt/keycloak/providers. |
spec.providers[].path |
string | no | Directory inside the image holding *.jar files. Defaults to /providers (the .ref theme image instead uses /theme — set explicitly for images that don't use the default layout). |
spec.providers[].pullPolicy |
corev1.PullPolicy | no | initContainer image pull policy. Defaults to IfNotPresent. |
spec.imagePullSecrets |
[]LocalObjectReference | no | Pod-level pull secrets; cover the Keycloak image and every provider image. |
spec.realmImport.secretName |
string | no | Secret containing the realm JSON to import. Mounted read-only at /opt/keycloak/data/import; presence adds --import-realm to the start command. |
spec.image |
string | no | Full image override (registry/repo:tag); bypasses KeycloakVersionMap resolution entirely. status.observedVersion becomes the tag portion of this value. |
spec.resources |
corev1.ResourceRequirements | no | Compute resources for the Keycloak container. Defaults to the reference envelope: 500m/1700Mi request, 2000m/2000Mi limit. |
spec.placement.nodeSelector |
map[string]string | no | Node selector for the Keycloak pod. |
spec.placement.tolerations |
[]corev1.Toleration | no | Tolerations for tainted nodes. |
spec.env |
[]corev1.EnvVar | no | Raw extra environment variables, merged last onto the container env (escape hatch — can override any operator-set variable by name). |
spec.metrics.serviceMonitor |
bool | no | true = create a Prometheus Operator ServiceMonitor (<name>) scraping the client Service's management port at /metrics every 30s. Capability-gated on the monitoring.coreos.com ServiceMonitor CRD: if absent the operator sets MetricsExporterReady=False/ServiceMonitorCRDMissing and continues (it does not fail the instance). Toggling back to false removes the ServiceMonitor. |
Status fields¶
| Field | Type | Description |
|---|---|---|
status.phase |
string | Lifecycle phase: Pending | Provisioning | Deploying | Ready | Failed. |
status.host |
string | Set to spec.hosts.host once the instance reaches Ready. |
status.observedVersion |
string | Keycloak version (or image tag, when spec.image is set) last observed as resolved/running. |
status.observedGeneration |
int64 | The metadata.generation this status reflects — lets a consumer (e.g. kubectl wait, an hq status sync) distinguish a status that reflects the current spec from a stale one. |
status.conditions |
[]metav1.Condition | Validated, BackendsReady, Progressing, Ready, UpgradeInProgress, MetricsExporterReady. See reason vocabulary below. |
status.secrets |
[]string | Names of operator-owned Secrets tracked for finalizer GC (currently: the generated admin-credentials Secret, when the operator created it). |
Condition reason vocabulary¶
| Condition | Reason | Meaning |
|---|---|---|
Validated |
SpecValid |
Spec passed ValidateSpec. |
Validated |
ValidationFailed |
Permanent — bad spec (missing BYO secret name, invalid topology, missing host with ingress enabled, invalid replica count, negative or BYO-only postgres.nodes, invalid provider name). |
Ready |
VersionResolutionPending |
Transient — KeycloakVersionMap/default absent, or the requested version/alias not found in it. Requeues after 30s; phase stays Pending. |
BackendsReady |
WaitingForCredentials |
Transient — BYO credentialsSecret not found yet. Requeues after 30s. |
BackendsReady |
CredentialsSecretInvalid |
BYO Secret found but missing a required key. Phase Failed, but bounded-retried (~60s requeue) because the fix — correcting the Secret — is user-side and neither owned nor watched by the operator; a corrected Secret recovers without editing the CR. |
BackendsReady |
PerconaCRDMissing |
Permanent — spec.postgres.managed: true but pgv2.percona.com/PerconaPGCluster is not installed. Never silently falls back. |
BackendsReady |
PostgresProvisioning |
Transient — managed PerconaPGCluster applied, waiting for its <name>-pg-db-credentials Secret. |
BackendsReady |
PostgresReady |
Database credentials are available. |
Ready / Progressing |
WorkloadNotReady / Deploying |
StatefulSet applied, waiting for it to become ready and for the rollout to converge (updatedReplicas >= spec.replicas, updateRevision == currentRevision, observedGeneration caught up) so no old-revision pods still serve. |
Ready |
WorkloadReady |
StatefulSet is ready and the rollout has fully converged onto the current revision; phase is Ready, Progressing is cleared, observedVersion is the running version. |
MetricsExporterReady |
ServiceMonitorApplied |
spec.metrics.serviceMonitor: true and the ServiceMonitor was applied. |
MetricsExporterReady |
ServiceMonitorCRDMissing |
Warning (not Failed) — spec.metrics.serviceMonitor: true but the monitoring.coreos.com ServiceMonitor CRD is absent; deployment continues without metrics scraping. |
Print columns (kubectl get kci)¶
Phase, Host, Version, Age
HA / production hardening¶
These are applied automatically — there is no spec surface to disable them:
- PodDisruptionBudget — an owned PDB named
<name>(maxUnavailable: 1) is created when the desired replica count is>= 2. It is removed when the instance is scaled to a single replica and while a cross-minor upgrade is draining (so the drain-to-zero recreate is never blocked). A single-replica instance never gets one. - Pod anti-affinity — soft (
preferredDuringScheduling, weight 100,kubernetes.io/hostname) so replicas spread across nodes where possible, while a single-node dev cluster still schedules them. - Security context — pod-level
runAsNonRoot: true+seccompProfile: RuntimeDefault; the Keycloak container and every provider initContainer getallowPrivilegeEscalation: falseandcapabilities.drop: [ALL].runAsUseris deliberately left unset — the Keycloak/provider images declare their own non-root user, and pinning a UID breaks provider init containers. Consequence for provider images: becauserunAsUseris unset, every provider image must itself declare a numeric non-rootUSER(e.g.USER 1000:1000); the kubelet rejects a root or unresolvable-username image underrunAsNonRoot, and the pod never starts. - Update strategy — the StatefulSet uses an explicit
RollingUpdatestrategy (one-by-one), protecting Infinispan in-memory session state. - Events — a Kubernetes Event (
reason: Phase<NewPhase>) is emitted each timestatus.phasechanges, alongside the upgrade-orchestration Events (UpgradeRecreateStarted,UpgradeCompleted,DowngradeBlocked, ...).
KeycloakVersionMap¶
Cluster-scoped | Short name: kcvm
Maps Keycloak version strings/aliases to container images. The operator always
looks up the KeycloakVersionMap named default.
Spec fields¶
| Field | Type | Required | Description |
|---|---|---|---|
spec.versions |
map[string]VersionEntry | yes | Maps a Keycloak version string (e.g. "26.6.4") to a VersionEntry. |
spec.versions[*].image |
string | yes | Full container image reference (including tag) for this version. |
spec.aliases |
map[string]string | no | Maps symbolic names (e.g. "latest", "stable", "26") to a key present in spec.versions. Resolution is a single alias hop followed by an exact-key match — there is no prefix/fuzzy matching (unlike bnerd-gitlab-operator's resolver). An alias pointing at a version not present in spec.versions is an error, listing the known versions. |
Print columns¶
Latest (from .spec.aliases.latest), Age