Partial Imports
When managing Keycloak configurations, you often need to update only specific parts of a realm without affecting other components. Understanding how to perform partial imports allows you to maintain modular, focused configuration files and avoid unintended changes to unrelated realm settings.
Related issues: #1161
The Problem
Users often encounter challenges when trying to update specific realm components because: - Running a full realm import can affect unrelated configurations - It's unclear how to update only clients without touching users or groups - Separate teams managing different components need isolated configuration files - Fear of accidentally deleting resources not included in the import file - Uncertainty about which resources will be managed vs. left untouched - Complex realms require modular configuration management
What are Partial Imports?
Partial imports allow you to import only specific parts of a realm configuration without affecting other components. Instead of managing the entire realm in one large file, you can:
- Update only clients without touching users
- Add authentication flows without modifying existing ones
- Manage groups separately from roles
- Update specific client scopes without affecting others
- Split configuration across multiple files by component type or team ownership
How Partial Imports Work
With import.remote-state.enabled=true (default), keycloak-config-cli tracks which resources it manages. This enables true partial imports where:
- Only specified resources are managed - Resources in your config file are created/updated
- Unspecified resources are untouched - Resources not in your config remain unchanged
- Removal is explicit - Only resources previously managed by keycloak-config-cli can be deleted
- Multiple files can coexist - Different files can manage different parts of the realm
Usage
Importing Only Clients
Scenario: You want to add or update clients without affecting users, groups, or other realm settings.
File: clients-config.yaml
realm: "myrealm"
clients:
- clientId: "app-frontend"
enabled: true
publicClient: true
redirectUris:
- "https://app.example.com/*"
webOrigins:
- "https://app.example.com"
- clientId: "app-backend"
enabled: true
publicClient: false
serviceAccountsEnabled: true
clientAuthenticatorType: "client-secret"
Result: - Only the two clients are created/updated - Existing users remain unchanged - Existing groups remain unchanged - Other clients not in this file remain untouched - No realm settings are modified
Import command:
java -jar keycloak-config-cli.jar \
--keycloak.url=http://localhost:8080 \
--keycloak.user=admin \
--keycloak.password=admin \
--import.files.locations=path/to/clients-config
Importing Only Users
Scenario: HR team manages users in a separate file from the infrastructure team's client configurations.
Important: Users reference realm roles which must exist. Either import roles first or include them in the same file.
File: users-config.yaml
realm: "myrealm"
roles:
realm:
- name: "user"
- name: "manager"
users:
- username: "john.doe"
email: "john.doe@example.com"
firstName: "John"
lastName: "Doe"
enabled: true
realmRoles:
- "user"
credentials:
- type: "password"
value: "temp123"
temporary: true
- username: "jane.smith"
email: "jane.smith@example.com"
firstName: "Jane"
lastName: "Smith"
enabled: true
realmRoles:
- "user"
- "manager"
Result: - Users and roles are managed - Clients remain unchanged - Groups remain unchanged - Authentication flows remain unchanged
Note: If you only want to import users without roles, remove the realmRoles field from user definitions.
Importing Only Groups
Scenario: Manage organizational structure separately from user accounts.
File: groups-config.yaml
realm: "myrealm"
groups:
- name: "Engineering"
path: "/Engineering"
attributes:
department: ["engineering"]
subGroups:
- name: "Backend"
path: "/Engineering/Backend"
- name: "Frontend"
path: "/Engineering/Frontend"
- name: "Sales"
path: "/Sales"
attributes:
department: ["sales"]
Result: - Only groups are managed - Users remain unchanged - User group memberships added via this file are managed - Other realm components remain unchanged
Importing Only Authentication Flows
Scenario: Security team manages authentication flows independently.
File: auth-flows-config.yaml
realm: "myrealm"
authenticationFlows:
- alias: "browser-with-mfa"
description: "Browser flow with mandatory MFA"
providerId: "basic-flow"
topLevel: true
builtIn: false
authenticationExecutions:
- authenticator: "auth-cookie"
requirement: "ALTERNATIVE"
priority: 10
- authenticator: "identity-provider-redirector"
requirement: "ALTERNATIVE"
priority: 20
- authenticator: "auth-username-password-form"
requirement: "REQUIRED"
priority: 30
- authenticator: "auth-otp-form"
requirement: "REQUIRED"
priority: 40
Result: - Only authentication flows are managed - Browser binding not changed unless explicitly set - Clients remain unchanged - Users remain unchanged
Importing Client Scopes Only
Scenario: Create reusable scopes without affecting existing clients.
File: client-scopes-config.yaml
realm: "myrealm"
clientScopes:
- name: "custom-claims"
description: "Custom application claims"
protocol: "openid-connect"
attributes:
include.in.token.scope: "true"
display.on.consent.screen: "true"
protocolMappers:
- name: "department-mapper"
protocol: "openid-connect"
protocolMapper: "oidc-usermodel-attribute-mapper"
config:
user.attribute: "department"
claim.name: "department"
jsonType.label: "String"
id.token.claim: "true"
access.token.claim: "true"
Result: - Only client scopes are created/updated - Existing clients are not modified - Client scope assignments to clients remain unchanged unless explicitly set
Multiple File Strategy
Organizing by Component Type
Structure:
config/
├── realm-settings.yaml # Basic realm settings
├── clients.yaml # All clients
├── users.yaml # All users
├── groups.yaml # Group structure
├── roles.yaml # Realm and client roles
├── auth-flows.yaml # Authentication flows
└── client-scopes.yaml # Reusable scopes
Import all files:
java -jar keycloak-config-cli.jar \
--keycloak.url=http://localhost:8080 \
--keycloak.user=admin \
--keycloak.password=admin \
--import.files.locations='config/*.yaml'
Import specific component:
java -jar keycloak-config-cli.jar \
--keycloak.url=http://localhost:8080 \
--keycloak.user=admin \
--keycloak.password=admin \
--import.files.locations=config/clients.yaml
Organizing by Environment
Structure:
config/
├── base/
│ ├── clients.yaml # Common clients
│ └── roles.yaml # Common roles
├── dev/
│ ├── realm-settings.yaml # Dev-specific settings
│ └── users.yaml # Dev test users
├── staging/
│ └── realm-settings.yaml # Staging settings
└── production/
└── realm-settings.yaml # Production settings
Import base + environment:
# Import base configuration
java -jar keycloak-config-cli.jar \
--import.files.locations='config/base/*.yaml'
# Then import environment-specific
java -jar keycloak-config-cli.jar \
--import.files.locations='config/production/*.yaml'
Organizing by Team
Structure:
config/
├── infrastructure/
│ ├── clients.yaml # Infrastructure team manages clients
│ └── auth-flows.yaml # Authentication configuration
├── hr/
│ └── users.yaml # HR team manages users
└── security/
├── roles.yaml # Security team manages roles
└── groups.yaml # Permission groups
Each team imports their files:
# Infrastructure team
java -jar keycloak-config-cli.jar \
--import.files.locations='config/infrastructure/*.yaml'
# HR team
java -jar keycloak-config-cli.jar \
--import.files.locations='config/hr/*.yaml'
Behavior with Remote State
With Remote State Enabled (Default)
Behavior:
1. Import clients.yaml → Creates/updates app-1 client
2. Import users.yaml → Creates/updates user-1 user
3. app-1 client remains unchanged by user import
4. user-1 user remains unchanged by client import
5. Both files can be maintained independently
This is true partial import!
Without Remote State
Behavior: - Each import attempts to sync the entire realm - Resources not in the file may be removed - Partial imports are risky without remote state - Not recommended for partial import strategy
Important: Always use import.remote-state.enabled=true (default) for partial imports.
Common Pitfalls
1. Expecting Unmanaged Resources to Update
Problem:
Later, manually creating app-2 via Admin Console, then expecting:
# Second import
realm: "myrealm"
clients:
- clientId: "app-1"
enabled: true
- clientId: "app-2" # Manually created
enabled: false # Trying to disable it
Result: If app-2 was created manually (not via keycloak-config-cli), changes to it may not be applied consistently.
Solution: Once you start managing a resource with keycloak-config-cli, always manage it through config files, not the Admin Console.
2. Conflicting Imports from Multiple Files
Problem:
# File 2: team-b.yaml
realm: "myrealm"
clients:
- clientId: "shared-client"
enabled: false # Conflicts with File 1
Result: Last import wins. Client state depends on import order.
Solution: Establish ownership rules:
- One file per resource
- Use naming conventions (e.g., team-a-* clients)
- Document which team owns which resources
3. Incomplete Partial Configuration
Problem:
# Trying to update only one field of a client
realm: "myrealm"
clients:
- clientId: "existing-client"
enabled: false # Only this field
Result: Other fields of the client may be reset to defaults or remain unchanged unpredictably.
Solution: Include all relevant fields when updating a resource:
realm: "myrealm"
clients:
- clientId: "existing-client"
enabled: false
publicClient: true
redirectUris:
- "https://app.example.com/*"
# Include all fields you want to maintain
4. Removing Resources Across Files
Problem: Resource managed by File A, trying to remove it via File B.
File A (initial):
File B (attempting removal):
Result: app-1 is not deleted because File B didn't create it.
Solution: Remove resources from the original file that created them: - Delete from File A, or - Use the same file consistently for a resource
5. Forgetting Realm Name
Problem:
Result: Import fails or applies to wrong realm.
Solution: Always specify the realm:
6. Partial Import of Realm-Level Settings
Problem: Trying to update only one realm setting without including other required realm configuration:
Result:
Realm-level settings like ssoSessionMaxLifespan, displayName, themes, and security defenses often require the full realm context. A partial import may:
- Reset unspecified settings to defaults
- Fail validation if required fields are missing
- Not work as expected for realm properties
Solution: For realm-level settings, include the full realm configuration or manage realm settings separately:
# realm-settings.yaml - dedicated file for realm configuration
realm: "myrealm"
enabled: true
displayName: "My Realm"
ssoSessionMaxLifespan: 3600
accessTokenLifespan: 300
# Include other realm-level settings you want to maintain
loginTheme: "keycloak"
accountTheme: "keycloak"
Key distinction: - Resource-level partial imports (clients, users, groups, roles) work excellently - Realm-level partial imports (realm settings, security defenses) may require full context
7. Missing Dependencies Between Resources
Problem:
realm: "myrealm"
users:
- username: "john.doe"
enabled: true
realmRoles:
- "user" # This role doesn't exist yet!
Result:
Cause:
Users with realmRoles reference roles that must already exist in the realm. The partial import fails because it tries to assign a non-existent role.
Solution: Option 1 - Include roles in the same file:
realm: "myrealm"
roles:
realm:
- name: "user"
- name: "manager"
users:
- username: "john.doe"
enabled: true
realmRoles:
- "user"
Option 2 - Import dependencies first:
# 1. Import roles first
java -jar keycloak-config-cli.jar \
--import.files.locations=path/to/roles-config
# 2. Then import users that reference those roles
java -jar keycloak-config-cli.jar \
--import.files.locations=path/to/users-config
Option 3 - Import users without roles initially, then add roles later.
Other dependency chains to watch for: - Users referencing groups (groups must exist first) - Clients referencing client scopes (scopes must exist first) - Authentication flows referencing sub-flows (parent must exist first) - Groups with subGroups (parent group must exist first)
Best Practices
- Always Use Remote State: Keep
import.remote-state.enabled=truefor partial imports - One Resource, One Owner: Assign each resource to a single configuration file
- Organize Logically: Group related resources (e.g., all clients together, all users together)
- Use Naming Conventions: Prefix resources by team/purpose (e.g.,
frontend-*,backend-*) - Document Ownership: Maintain a README showing which file manages which resources
- Version Control Everything: Store all configuration files in Git
- Small, Focused Files: Better than one large file for maintainability
- Validate Before Import: Use
--import.validate=trueto catch errors - Test in Development: Apply partial imports to dev environment first
- Automate in CI/CD: Use separate jobs for different component imports
Configuration Options
# Enable remote state for partial imports (default)
--import.remote-state.enabled=true
# Validate configuration before import
--import.validate=true
# Import multiple files with pattern
--import.files.locations='config/*.yaml'
# Import specific files (comma-separated)
--import.files.locations='config/clients.yaml,config/users.yaml'
# Enable parallel import for performance
--import.parallel=true
Import Strategies
Strategy 1: Component-Based
Best for: Large teams with specialized roles
config/
├── 01-realm-settings.yaml
├── 02-roles.yaml
├── 03-groups.yaml
├── 04-clients.yaml
├── 05-users.yaml
└── 06-auth-flows.yaml
Benefits: - Clear separation of concerns - Different teams own different files - Easy to understand structure
Strategy 2: Application-Based
Best for: Microservices architecture
config/
├── app-frontend/
│ ├── client.yaml
│ ├── roles.yaml
│ └── scopes.yaml
├── app-backend/
│ ├── client.yaml
│ └── service-account.yaml
└── shared/
├── common-roles.yaml
└── common-groups.yaml
Benefits: - Application-centric organization - Easy to deploy/remove entire applications - Clear dependencies
Strategy 3: Environment-Based
Best for: Multiple environments (dev, staging, prod)
config/
├── base/ # Common to all environments
│ ├── clients.yaml
│ └── roles.yaml
├── overlays/
│ ├── dev/
│ │ └── realm-settings.yaml
│ ├── staging/
│ │ └── realm-settings.yaml
│ └── production/
│ └── realm-settings.yaml
Benefits: - Reuse common configuration - Environment-specific overrides - Consistent base across environments
Real-World Example
Scenario: E-commerce Platform
Teams: - Infrastructure: Manages clients and authentication - Backend: Manages service accounts - Frontend: Manages public clients - HR: Manages users
Configuration Structure:
config/
├── infrastructure/
│ ├── realm-settings.yaml
│ │ realm: "ecommerce"
│ │ displayName: "E-commerce Platform"
│ │
│ └── auth-flows.yaml
│ realm: "ecommerce"
│ authenticationFlows: [...]
│
├── backend/
│ └── service-clients.yaml
│ realm: "ecommerce"
│ clients:
│ - clientId: "order-service"
│ - clientId: "payment-service"
│ - clientId: "inventory-service"
│
├── frontend/
│ └── public-clients.yaml
│ realm: "ecommerce"
│ clients:
│ - clientId: "web-shop"
│ - clientId: "mobile-app"
│
└── hr/
└── employees.yaml
realm: "ecommerce"
users:
- username: "john.doe"
- username: "jane.smith"
Import Process:
# Infrastructure team (rarely changes)
java -jar keycloak-config-cli.jar \
--import.files.locations='config/infrastructure/*.yaml'
# Backend team (updates service configs)
java -jar keycloak-config-cli.jar \
--import.files.locations='config/backend/*.yaml'
# Frontend team (updates client apps)
java -jar keycloak-config-cli.jar \
--import.files.locations='config/frontend/*.yaml'
# HR team (manages employees)
java -jar keycloak-config-cli.jar \
--import.files.locations='config/hr/*.yaml'
Benefits: - Each team works independently - No conflicts between teams - Clear ownership - Easy to track changes in Git
Partial Management Mode
In addition to using import.remote-state.enabled, you can control import behavior using the import.managed setting.
Management Modes
| Mode | Behavior | Use Case |
|---|---|---|
full (default) |
Create, update, AND delete resources not in the config | Full synchronization |
partial |
Create and update only - never delete | Safe partial updates |
create-only |
Only create new resources | Initial seeding |
update-only |
Only update existing resources | Patching existing |
delete-only |
Only remove resources | Cleanup |
Using Partial Mode
For the safest partial imports that never delete anything:
java -jar keycloak-config-cli.jar \
--keycloak.url=http://localhost:8080 \
--keycloak.user=admin \
--keycloak.password=admin \
--import.managed=partial \
--import.remote-state.enabled=true \
--import.files.locations=path/to/config-file
When to use partial mode:
- When you're unsure if the config file contains all resources
- When doing incremental updates from multiple sources
- When you want to ensure nothing gets accidentally deleted
Relationship with Remote State:
- import.remote-state.enabled=true + import.managed=full: Only deletes resources previously managed by the CLI
- import.remote-state.enabled=true + import.managed=partial: Never deletes, only creates/updates
- import.remote-state.enabled=false: May delete unmanaged resources (dangerous!)
Advanced: Selective Import with Cache Keys
Cache keys provide independent tracking for different import sets. While simple partial imports work for most cases, cache keys are useful when:
- Different teams run imports on different schedules
- You want independent checksum tracking for each component
- You need to force reimport of one component without affecting others
When to Use Cache Keys vs Simple Partial Imports
| Scenario | Approach |
|---|---|
| Same file updated regularly | Simple partial import (no cache key needed) |
| Multiple files imported together | Simple partial import |
| Different files imported on different schedules | Use cache keys |
| Need to force reimport of one component only | Use cache keys |
Using Cache Keys
# Import and track clients separately
java -jar keycloak-config-cli.jar \
--import.cache.key=clients \
--import.files.locations=path/to/clients-config
# Import and track users separately
java -jar keycloak-config-cli.jar \
--import.cache.key=users \
--import.files.locations=path/to/users-config
Benefits: - Independent checksum tracking - Can reimport one without affecting the other - Useful for scheduled imports of different components
Troubleshooting
Resources Not Being Updated
Symptom: Changes in configuration file don't appear in Keycloak
Possible causes: 1. Resource created manually (not managed by keycloak-config-cli) 2. Different cache key used 3. Remote state disabled
Diagnosis:
# Check remote state
--import.remote-state.enabled=true
# Force reimport
--import.cache.enabled=false
Resources Being Deleted Unexpectedly
Symptom: Resources disappear after import
Possible causes: 1. Remote state disabled 2. Resource removed from config file that created it 3. Full realm export used instead of partial import
Solution:
- Always use import.remote-state.enabled=true
- Only manage resources you intend to control
- Keep resource definitions in their original files
Import Order Matters
Symptom: Import fails due to missing dependencies
Cause: Trying to assign roles/groups that don't exist yet
Solution: Import in dependency order:
# 1. Roles first
--import.files.locations=path/to/roles-config
# 2. Groups next
--import.files.locations=path/to/groups-config
# 3. Users last (can reference roles and groups)
--import.files.locations=path/to/users-config
Consequences
When using partial imports:
- Independent Updates: Different teams can update their components without conflicts
- Reduced Risk: Only specified resources are affected by each import
- Modular Configuration: Easier to maintain small, focused files
- Requires Discipline: Teams must follow ownership conventions
- Remote State Essential: Partial imports require
import.remote-state.enabled=true - Git-Friendly: Smaller files mean clearer Git diffs and easier reviews
Migration Guide
From Full Realm Exports to Partial Imports
Step 1: Export current realm
Step 2: Split into components
# Create separate files
config/
├── clients.yaml # Extract clients section
├── users.yaml # Extract users section
├── roles.yaml # Extract roles section
└── groups.yaml # Extract groups section
Step 3: Clean up each file - Remove UUIDs - Remove default values - Keep only managed resources
Step 4: Import incrementally
# Import one component at a time
java -jar keycloak-config-cli.jar \
--import.files.locations=path/to/roles-config
# Verify, then import next
java -jar keycloak-config-cli.jar \
--import.files.locations=path/to/clients-config
Step 5: Establish ownership - Document which team owns which file - Set up CI/CD pipelines per component - Train teams on their responsibilities
Related Issues
Additional Resources
- Managed Resources - Resource management guidelines
- Import Settings Documentation
- Remote State Management