Import / Export & Migration¶
Move a realm's entire configuration between environments as a single file — export it, commit it, and apply it elsewhere — and migrate in from an existing identity provider.
What it is¶
Helix IAM treats a realm's configuration as portable, version-controllable data. You can export a realm as one self-contained document and import it into another — to promote config from staging to production, seed a new tenant, or keep configuration in git. Secrets are never written to the file as literals: they appear as ${ENV_VAR} placeholders and are resolved from the environment on import. The same document can be dropped into a folder that the server reads on startup (config-as-code).
| Endpoint / mechanism | Purpose |
|---|---|
GET /admin/realms/{realm}/export |
Export the realm as a JSON document, secrets as ${ENV_VAR} placeholders |
POST /admin/realms/{realm}/import?onConflict=… |
Apply a realm document — overwrite, skip, or fail on conflicts |
drop-in folder (/realms) |
Apply *.json / *.yaml realm files on startup, idempotently |
| migration import | Translate a realm-export from a previous provider — see Migrate |
The file format (v2)¶
A single self-describing document per realm. The realm id comes from the path or file name, never the document — so an export re-homes into any realm. Unset slices may be omitted; unknown fields are ignored (forward-compatible).
{
"formatVersion": 2,
"realm": { "displayName": "Gov", "captchaSecretKey": "${HELIX_GOV_CAPTCHA_SECRET}" },
"roles": [ { "name": "ledger-writer" } ],
"clientScopes": [ { "name": "billing", "claims": [ { "key": "dept", "mandatory": false } ] } ],
"clients": [ { "clientId": "billing", "grantTypes": ["authorization_code","client_credentials"],
"redirectUris": ["https://billing.example.com/callback"], "scopes": ["openid","billing"] } ],
"identityProviders": [ { "alias": "google", "protocol": "oidc",
"config": { "clientId": "…", "clientSecret": "${HELIX_GOV_IDP_GOOGLE_CLIENTSECRET}" } } ],
"flows": [ { "alias": "browser", "executions": [ /* … */ ] } ],
"samlClients": [ /* … */ ], "organizations": [ /* … */ ],
"requiredEnv": [ "HELIX_GOV_CAPTCHA_SECRET", "HELIX_GOV_IDP_GOOGLE_CLIENTSECRET" ]
}
requiredEnvlists every environment variable the file references — set these before importing elsewhere.- Excluded by design: realm signing keys (each environment mints its own) and user password / MFA secrets.
- Both JSON and YAML are accepted by the drop-in folder loader; the export endpoint returns JSON.
Export a realm¶
curl https://auth.example.com/admin/realms/acme/export \
-H "Authorization: Bearer $ADMIN_TOKEN" > acme-realm.json
The document captures the realm's configuration. Every secret is emitted as a ${ENV_VAR} placeholder — never the literal value — so the export is safe to review, commit, and share. The requiredEnv array tells you exactly which variables to provide on the target.
What's included¶
A realm export bundles every configurable slice the admin console manages:
- Realm settings — login/token/session policy, branding, CAPTCHA, password policy
- Applications, their OIDC clients, and SAML relying parties
- Per-client OIDC detail — protocol mappers, client roles, service-account role grants, resource-indicator allow-lists (RFC 8707), and authorization services (UMA resource server: resources, scopes, policies, permissions)
- Roles, client scopes (with claim mappings), and groups (hierarchy + role mappings)
- Identity providers / federation brokers and authentication flows
- Organizations and admin-RBAC role grants
- Webhooks, SCIM targets, messaging providers + message templates, and workload-identity credentials
- Users — profiles and their realm-role / group assignments
Per-client slices carry the OAuth client id as their key, so they re-home into any realm: on import each is matched to the target realm's client and applied (a slice whose client isn't present in the target is skipped, never orphaned).
Never exported — regenerate or provision these on the target: realm signing keys, user passwords and MFA secrets, GDPR consent ledgers and DSAR (data-subject access request) records, and all runtime state (sessions, tokens, audit log). These are per-user runtime data, not realm configuration. Every secret-bearing config field above is emitted as a ${ENV_VAR} placeholder, not a literal.
Import a realm¶
# overwrite (default): upsert — existing resources are updated in place
curl -X POST "https://auth.example.com/admin/realms/acme/import?onConflict=overwrite" \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
--data @acme-realm.json
Import is an idempotent upsert by natural key (client clientId, role name, scope name, IdP alias, …): re-importing converges the target realm to the document's state without duplicating. The response summarises, per slice, how many entries were created, updated, or skipped.
Conflict modes¶
onConflict controls how an entry whose key already exists is treated:
onConflict |
Behaviour |
|---|---|
overwrite (default) |
Update existing resources in place. |
skip |
Block — leave existing resources untouched; only create new ones. |
fail |
Like skip, but each collision is listed in the response's conflicts array so you can reject the apply. |
Secret resolution¶
A ${VAR} placeholder is resolved from the server's environment. Set the variables listed in requiredEnv before importing:
export HELIX_GOV_CAPTCHA_SECRET=… # value from your secrets manager
export HELIX_GOV_IDP_GOOGLE_CLIENTSECRET=…
If a referenced variable is missing the secret is left unset (the rest of the import still applies). Strict mode — failing when any required variable is missing — is available for the folder loader below.
Config-as-code: the drop-in folder¶
Mount a directory of realm-config files and Helix applies them on startup — one file per realm, the file name is the realm id:
The bundled docker-compose.yml already mounts ./deploy/realms into helix-iam-server. Configure it with:
| Variable | Default | Meaning |
|---|---|---|
HELIX_REALMS_LOCATION |
/realms |
Folder scanned on startup |
HELIX_REALMS_ON_CONFLICT |
skip |
skip (never clobber a live realm) / overwrite / fail |
HELIX_REALMS_REQUIRE_ENV |
false |
true = fail a file whose required secret env var is missing |
The loader is idempotent and non-fatal: a restart re-applies cleanly (default skip makes it a no-op against an unchanged realm), and a malformed or partially-failing file is logged and skipped without stopping startup. This is the recommended way to ship a realm's configuration alongside your deployment — see deploy/realms/example-realm.json.sample.
On Kubernetes (Helm)¶
The Helm chart renders your realm files into a ConfigMap and mounts it at /realms on helix-iam-server. Enable it in values.yaml — each key under realms.files becomes one file (the key is the realm id + extension), and the ${ENV_VAR} secrets are supplied from Kubernetes Secret(s) listed in realms.secretRefs (loaded with envFrom):
realms:
enabled: true
onConflict: skip # skip | overwrite | fail
requireEnv: false
secretRefs: [ helix-realm-secrets ] # Secret keys = the ${ENV_VAR} names the files reference
files:
gov.json: |
{
"formatVersion": 2,
"realm": { "realmId": "gov", "displayName": "Government" },
"webhooks": [ { "name": "audit", "url": "https://…", "secret": "${HELIX_GOV_AUDIT_SECRET}" } ],
"requiredEnv": [ "HELIX_GOV_AUDIT_SECRET" ]
}
partner.yaml: |
formatVersion: 2
realm: { realmId: partner, displayName: Partner }
kubectl create secret generic helix-realm-secrets --from-literal=HELIX_GOV_AUDIT_SECRET=…
helm upgrade --install helix ./deploy/helm/helix-iam -f my-values.yaml
The same HELIX_REALMS_LOCATION / HELIX_REALMS_ON_CONFLICT / HELIX_REALMS_REQUIRE_ENV knobs apply; the chart sets them from the realms.* values.
Promote config like code
Keep the exported file in git, store the requiredEnv secrets in your secrets manager, and either POST /import from your pipeline or drop the file in deploy/realms. Because every path is idempotent, the same document converges any target realm to the same state.
Migrate from another provider¶
Moving to Helix from an existing identity provider is a paste-and-go step. In the console's Import / export screen, choose the migration import and upload (or paste) a standard realm-export JSON from your current system — Helix translates the portable slices and upserts them into the target realm: OIDC clients (grant types derived from the source), realm roles, and identity providers. The exact request shape is in the live API reference.
Users in a migration come across separately
Password hashes from another provider are not portable, so user credentials are not brought in by the migration import. Provision users via SCIM, LDAP federation, or bulk user import — then decommission the old system. (A native Helix realm export/import does carry user profiles and role/group assignments — never passwords or MFA secrets.)
Imported users have no password yet
Because exports never carry credentials, a user brought in by a realm import (or the drop-in folder) is created without a password and flagged with the UPDATE_PASSWORD required action. The account exists with all its roles and group memberships, but the person must set a password — via a reset link, the account console, an admin, or a passwordless / federated login — before their first sign-in. This is standard config-as-code behaviour: profiles ship in the file, secrets are established per environment.