Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

KeycloakRealm

Identifier field: Set the realm name in the spec.realmName field. It is required and immutable once set. A realm key inside spec.definition is tolerated only when it matches spec.realmName; a conflicting value is rejected.

spec.realmName is immutable once set. A realm rename in Keycloak is destructive and would orphan the realm, so the value is locked after the first time it is set (the one-time migration from unset to a value is permitted).

A KeycloakRealm represents a realm within a Keycloak instance.

Specification

apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
  name: my-realm
spec:
  # One of instanceRef or clusterInstanceRef must be specified
  
  # Option 1: Reference to a namespaced KeycloakInstance
  instanceRef:
    name: my-keycloak
  
  # Option 2: Reference to a ClusterKeycloakInstance
  # clusterInstanceRef:
  #   name: my-cluster-instance
  
  # Required: Realm name in Keycloak (do NOT set realm in definition)
  realmName: my-realm
  
  # Required: Realm definition (Keycloak RealmRepresentation)
  definition:
    displayName: My Realm
    enabled: true
    # ... any other Keycloak realm properties

Status

status:
  ready: true
  status: "Ready"
  message: "Realm synchronized successfully"
  resourcePath: "/admin/realms/my-realm"
  instance:
    instanceRef: my-keycloak
  conditions:
    - type: Ready
      status: "True"
      reason: Synchronized

Example

Basic Realm

apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
  name: my-app-realm
spec:
  instanceRef:
    name: production-keycloak
  realmName: my-app
  definition:
    displayName: My Application
    enabled: true

With ClusterKeycloakInstance

apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
  name: my-app-realm
spec:
  clusterInstanceRef:
    name: central-keycloak
  realmName: my-app
  definition:
    displayName: My Application
    enabled: true

Full Configuration

apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
  name: production-realm
spec:
  instanceRef:
    name: production-keycloak
  realmName: production
  definition:
    displayName: Production Realm
    enabled: true
    
    # Login settings
    registrationAllowed: false
    registrationEmailAsUsername: true
    loginWithEmailAllowed: true
    duplicateEmailsAllowed: false
    resetPasswordAllowed: true
    rememberMe: true
    
    # Session settings
    ssoSessionIdleTimeout: 1800
    ssoSessionMaxLifespan: 36000
    accessTokenLifespan: 300
    
    # Security settings
    bruteForceProtected: true
    permanentLockout: false
    maxFailureWaitSeconds: 900
    minimumQuickLoginWaitSeconds: 60
    waitIncrementSeconds: 60
    quickLoginCheckMilliSeconds: 1000
    maxDeltaTimeSeconds: 43200
    failureFactor: 5
    
    # Themes
    loginTheme: keycloak
    accountTheme: keycloak
    adminTheme: keycloak
    emailTheme: keycloak
    
    # SMTP settings (non-sensitive parts in definition)
    smtpServer:
      host: smtp.example.com
      port: "587"
      fromDisplayName: My App
      from: noreply@example.com
      starttls: "true"
      auth: "true"
  
  # SMTP credentials from a Kubernetes Secret (recommended over plaintext in definition)
  smtpSecretRef:
    name: my-smtp-credentials
    userKey: user         # optional, defaults to "user"
    passwordKey: password # optional, defaults to "password"

SMTP Credentials from Secret

To avoid storing SMTP credentials in plaintext in the CR, use smtpSecretRef to reference a Kubernetes Secret:

kubectl create secret generic smtp-credentials \
  --from-literal=user=smtp-user@example.com \
  --from-literal=password=my-smtp-password
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
  name: my-realm
spec:
  instanceRef:
    name: my-keycloak
  smtpSecretRef:
    name: smtp-credentials
    # userKey: user         # default
    # passwordKey: password # default
  realmName: my-realm
  definition:
    enabled: true
    smtpServer:
      host: smtp.example.com
      port: "587"
      from: noreply@example.com
      starttls: "true"
      auth: "true"

The operator reads the user and password values from the referenced secret and injects them into smtpServer before sending the realm configuration to Keycloak. Always set from: Keycloak 26.8 and later reject a realm whose smtpServer has no sender address (Invalid sender address 'null'), which surfaces as CreateFailed or UpdateFailed on the realm. Also set auth: "true": Keycloak 26.8 keeps user and password on realm creation but drops them on every update when auth is not enabled, so the injected credentials would disappear on the operator’s next reconcile. The secret must exist in the same namespace as the KeycloakRealm.

For ClusterKeycloakRealm, the secret namespace must be specified explicitly:

apiVersion: keycloak.hostzero.com/v1beta1
kind: ClusterKeycloakRealm
metadata:
  name: my-realm
spec:
  clusterInstanceRef:
    name: central-keycloak
  smtpSecretRef:
    name: smtp-credentials
    namespace: keycloak-system
  realmName: my-realm
  definition:
    enabled: true
    smtpServer:
      host: smtp.example.com
      port: "587"
      from: noreply@example.com
      starttls: "true"
      auth: "true"

When the referenced secret changes, the operator automatically re-reconciles the realm to pick up the new credentials.

Definition Properties

The definition field accepts any property from the Keycloak RealmRepresentation.

Common properties:

PropertyTypeDescription
realmstringRealm name (required)
displayNamestringDisplay name for the realm
enabledbooleanWhether the realm is enabled
registrationAllowedbooleanAllow user registration
loginWithEmailAllowedbooleanAllow login with email
ssoSessionIdleTimeoutintegerSSO session idle timeout (seconds)
accessTokenLifespanintegerAccess token lifespan (seconds)

Nested Resources

definition holds the realm’s own attributes only. The collections Keycloak nests inside a RealmRepresentation each have a CRD of their own, and the realm rejects them with Ready=False and reason UnsupportedDefinitionField:

definition keyUse instead
clientsKeycloakClient
clientScopesKeycloakClientScope
usersKeycloakUser
groupsKeycloakGroup
rolesKeycloakRole
componentsKeycloakComponent
authenticationFlowsKeycloakAuthenticationFlow
identityProvidersKeycloakIdentityProvider
identityProviderMappersKeycloakIdentityProviderMapper
requiredActionsKeycloakRequiredAction
organizationsKeycloakOrganization

Keycloak only imports these when a realm is created; GET /admin/realms/{realm} leaves them out and PUT ignores them. A realm declaring them would therefore never settle in drift detection and would re-send the realm on every reconcile while the nested entries never changed in Keycloak. See Spec Layout for the rule behind this. Flow bindings such as browserFlow are realm attributes and stay in definition; see Binding Custom Authentication Flows.

Partial Updates

definition is sent to Keycloak as-is. For an existing realm, Keycloak applies only the fields present in the representation and leaves everything else unchanged, so a minimal definition can adopt a pre-existing realm without rewriting it.

Keycloak makes an exception for the OAuth 2.0 device flow (oauth2DeviceCodeLifespan, oauth2DevicePollingInterval), CIBA and PAR settings (attributes.ciba*, attributes.parRequestUriLifespan) and browserSecurityHeaders: it resets these to defaults when they are omitted from an update. The operator compensates by copying the realm’s current values for these fields into the request when the definition does not set them.

Because of that, attributes behaves as a whole: when the definition sets attributes, Keycloak replaces the realm’s attribute map with it, dropping any attribute not listed.

Binding Custom Authentication Flows

A realm definition may bind built-in authentication points to custom flows via browserFlow, registrationFlow, directGrantFlow, resetCredentialsFlow, clientAuthenticationFlow, or dockerAuthenticationFlow. Keycloak rejects realm imports that reference a flow alias which does not yet exist (see keycloak/keycloak#23980), which would otherwise prevent declaratively creating the realm and the flow at the same time.

The operator works around this with deferred bindings:

  1. On the first CreateRealm call, any flow-binding fields whose target alias does not yet exist in Keycloak are stripped before the request is sent. The realm is created and marked Ready so that the flows can be created against it, but with status.status (and the Ready condition reason) set to FlowBindingsDeferred instead of Ready.
  2. The realm controller watches KeycloakAuthenticationFlow resources and requeues the realm immediately when a referenced flow becomes ready, instead of waiting for the next periodic resync.
  3. On the next reconcile (either triggered by the watch or by the periodic resync) the operator updates the realm with the original bindings now that the referenced flows exist, and the status reason returns to Ready.

Practically this means you can apply a KeycloakRealm and its KeycloakAuthenticationFlow resources together — in any order — and convergence happens within seconds. To wait for the bindings to be applied rather than just for the realm to exist, wait on the status reason:

kubectl wait keycloakrealm/my-realm --for=jsonpath='{.status.status}'=Ready
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
  name: my-realm
spec:
  instanceRef:
    name: my-keycloak
  realmName: my-realm
  definition:
    enabled: true
    browserFlow: my-custom-browser           # may be created later
    registrationFlow: my-custom-registration # may be created later
---
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakAuthenticationFlow
metadata:
  name: my-custom-browser
spec:
  realmRef:
    name: my-realm
  alias: my-custom-browser
  providerId: basic-flow
  executions:
    - authenticator: auth-cookie
      requirement: ALTERNATIVE

See KeycloakAuthenticationFlow for the flow CRD reference.

Identity Providers and Mappers

Identity providers themselves can be embedded in definition.identityProviders for initial realm creation, but Keycloak’s PUT /admin/realms/{realm} endpoint does not consume the identityProviders or identityProviderMappers fields, so they are silently dropped on realm updates. To declaratively manage identity providers and their mappers on existing realms (including the master realm), use the dedicated CRDs:

Preserving Realm on Deletion

To keep the realm in Keycloak when deleting the CR:

apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
  name: my-realm
  annotations:
    keycloak.hostzero.com/preserve-resource: "true"
spec:
  instanceRef:
    name: my-keycloak
  realmName: my-realm
  definition:
    enabled: true

See Common Patterns for more details.

Short Names

AliasFull Name
kcrmkeycloakrealms
kubectl get kcrm