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

Exporting Keycloak Resources

The Keycloak Operator includes an export command that extracts resources from an existing Keycloak instance and generates Kubernetes CRD manifests. This is useful for:

  • Migration: Moving from manual Keycloak configuration to operator-managed resources
  • Discovery: Generating manifests from an existing Keycloak setup
  • Backup: Creating declarative representations of your Keycloak configuration

Quick Start

Export a realm to stdout:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm

Running the Export Command

The export command is part of the operator binary. Either download a prebuilt binary or use the container image.

Release Binary

Prebuilt binaries for Linux, macOS and Windows (amd64/arm64) are attached to every GitHub release, together with a checksums.txt:

VERSION=0.14.0
curl -fsSLO "https://github.com/Hostzero-GmbH/keycloak-operator/releases/download/v${VERSION}/keycloak-operator_${VERSION}_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz"
tar -xzf keycloak-operator_${VERSION}_*.tar.gz
./keycloak-operator_${VERSION}_*/keycloak-operator export --help

The binary reads ~/.kube/config directly, so --from-instance works without volume mounts. The examples below use Docker; replace docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator with the binary path to run them locally.

Container Image

The export command is also included in the operator container image. Run it via Docker:

Direct Connection Mode

Connect directly to a Keycloak instance using URL and credentials:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --target-namespace production \
  --instance-ref keycloak-prod

From Existing KeycloakInstance CR

If you already have the operator deployed with a KeycloakInstance configured, you can reuse those connection details:

docker run --rm -v ~/.kube:/root/.kube ghcr.io/hostzero-gmbh/keycloak-operator export \
  --from-instance my-keycloak \
  --namespace keycloak-operator \
  --realm my-realm

This reads the connection URL and credentials from the existing KeycloakInstance CR and its associated Secret.

For cluster-scoped instances:

docker run --rm -v ~/.kube:/root/.kube ghcr.io/hostzero-gmbh/keycloak-operator export \
  --from-cluster-instance my-cluster-keycloak \
  --realm my-realm

Output Options

Stdout (Default)

Output all manifests as multi-document YAML, suitable for piping:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  > manifests.yaml

Or apply directly:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  | kubectl apply -f -

Single File

Write all manifests to a single file:

docker run --rm -v $(pwd):/output ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --output /output/manifests.yaml

Directory Structure

Create an organized directory hierarchy:

docker run --rm -v $(pwd)/manifests:/output ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --output-dir /output

This creates:

manifests/
  realm.yaml
  clients/
    my-app.yaml
    another-client.yaml
  users/
    john-doe.yaml
  groups/
    admin-group.yaml
  roles/
    custom-role.yaml
  ...

Filtering Resources

Include Specific Types

Export only certain resource types:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --include clients,users,groups

Exclude Types

Skip certain resource types:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --exclude role-mappings,protocol-mappers

Resource Types

Available resource types for filtering:

TypeDescription
realmThe realm itself
clientsOAuth2/OIDC clients
client-scopesClient scopes
usersUser accounts
groupsUser groups
rolesRealm and client roles
role-mappingsRole assignments to users/groups
identity-providersExternal identity providers (SAML, OIDC, etc.). Org-linked IdPs emit spec.organizationRef instead of definition.organizationId.
componentsLDAP federation, key providers, etc.
protocol-mappersToken claim mappers
organizationsOrganizations (Keycloak 26+)

A user’s group memberships are not part of the Keycloak user representation. When both users and groups are exported, each KeycloakUser gets spec.groups with the memberships as absolute paths (/developers, /engineering/team-alpha), which is how KeycloakUser reconciles them; a user without memberships leaves spec.groups unset. Apply the exported groups before the users, otherwise the operator skips the unknown groups until its next sync.

Sub-components, such as the mappers of an LDAP provider, are exported with parentComponentRef pointing at their parent’s resource, and their resource name is prefixed with the parent’s. A sub-component whose parent is not exported (for example a key provider skipped by --skip-defaults) is left out rather than exported with a dangling reference.

The exported KeycloakRealm carries the realm’s own attributes only. Nested collections such as clients, identityProviders or authenticationFlows become resources of their own kinds and are stripped from the realm definition, which would reject them (see Nested Resources).

Identity providers linked to an organization are exported with spec.organizationRef pointing at the generated KeycloakOrganization (named from the organization name). The links are read from each organization’s identity-provider list (GET /admin/realms/{realm}/organizations/{id}/identity-providers), which is the only place Keycloak 26.8 reports them; the organizationId field older versions return on the identity provider itself, and the organizationLinks list Keycloak 26.8 returns, are ignored and stripped from definition so the exported manifest applies without being rejected. An identity provider that Keycloak 26.8 links to several organizations is exported with the first one in organization-name order, since organizationRef holds a single reference.

Skip Built-in Resources

By default, Keycloak’s built-in resources are skipped (--skip-defaults=true). These include:

  • Default clients: account, account-console, admin-cli, broker, realm-management, security-admin-console
  • Default client scopes: address, email, offline_access, phone, profile, roles, web-origins, etc.
  • Default roles: offline_access, uma_authorization, default-roles-{realm}
  • Service account users (prefixed with service-account-)

To include built-in resources:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --skip-defaults=false

Manifest Generation Options

Target Namespace

Set the namespace for generated manifests:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --target-namespace production

Instance Reference

Set the KeycloakInstance reference for all generated resources:

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --instance-ref keycloak-prod

Realm Reference

Override the realm reference name (defaults to the sanitized realm name):

docker run --rm ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm \
  --realm-ref production-realm

Security Considerations

Secrets Are Never Exported

The export command never exports secrets. This includes:

  • Client secrets
  • User passwords
  • Identity provider client secrets
  • LDAP bind credentials (config.bindCredential)

You must create these secrets separately and set the matching reference on the CR (clientSecretRef, configSecretRef, userSecret). Export does not emit configSecretRef; wire it after creating the Secret. See Secret references.

Password Handling

Pass passwords via environment variable instead of command line:

export KEYCLOAK_PASSWORD="your-password"
docker run --rm -e KEYCLOAK_PASSWORD ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm my-realm

Example Workflow: Migrating to the Operator

  1. Export existing configuration:
docker run --rm -v $(pwd)/manifests:/output ghcr.io/hostzero-gmbh/keycloak-operator export \
  --url https://keycloak.example.com \
  --username admin \
  --password "$KEYCLOAK_PASSWORD" \
  --realm production \
  --target-namespace keycloak \
  --instance-ref keycloak-prod \
  --output-dir /output
  1. Review and customize manifests:
# Review generated files
ls -la manifests/

# Edit as needed (add client secrets, customize settings)
vim manifests/clients/my-app.yaml
  1. Create required secrets:
# Create client secrets referenced by the manifests
kubectl create secret generic my-app-credentials \
  --namespace keycloak \
  --from-literal=client-secret=your-client-secret

# LDAP bind credentials (then set spec.configSecretRef on the exported Component)
kubectl create secret generic ldap-credentials \
  --namespace keycloak \
  --from-literal=bindCredential=your-ldap-password
  1. Deploy the operator (if not already installed):
helm install keycloak-operator oci://ghcr.io/hostzero-gmbh/charts/keycloak-operator \
  --namespace keycloak-operator \
  --create-namespace
  1. Create the KeycloakInstance:
# Create credentials secret
kubectl create secret generic keycloak-admin \
  --namespace keycloak \
  --from-literal=username=admin \
  --from-literal=password=your-admin-password

# Apply instance configuration
kubectl apply -f - <<EOF
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakInstance
metadata:
  name: keycloak-prod
  namespace: keycloak
spec:
  baseUrl: https://keycloak.example.com
  auth:
    passwordGrant:
      secretRef:
        name: keycloak-admin
EOF
  1. Apply the exported manifests:
kubectl apply -f manifests/

Command Reference

Usage: keycloak-operator export [options]

Connection Options (choose one mode):
  --url           Keycloak server URL
  --username      Admin username
  --password      Admin password (or use KEYCLOAK_PASSWORD env var)

  --from-instance          Name of KeycloakInstance CR
  --from-cluster-instance  Name of ClusterKeycloakInstance CR
  --namespace              Namespace of KeycloakInstance

Export Options:
  --realm         Realm to export (required)

Output Options:
  --output        Output file path (default: stdout)
  --output-dir    Output directory (creates file structure)

Manifest Options:
  --target-namespace  Namespace for generated manifests (default: "default")
  --instance-ref      KeycloakInstance name to reference
  --realm-ref         KeycloakRealm name to reference

Filtering Options:
  --include       Resource types to include (comma-separated)
  --exclude       Resource types to exclude (comma-separated)
  --skip-defaults Skip built-in Keycloak resources (default: true)

General Options:
  --verbose       Enable verbose output