- Jinja 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitea/workflows | ||
| defaults | ||
| meta | ||
| tasks | ||
| templates | ||
| .gitignore | ||
| CHANGELOG.md | ||
| LICENSE | ||
| README.md | ||
Keycloak
Deploys Keycloak — an open source identity and access management solution — via Docker Compose, with PostgreSQL included. Realms (groups, roles, clients, users, Active Directory/LDAP, branding) are applied declaratively with keycloak-config-cli, custom themes are installed from the controller.
Requirements
- Docker
community.dockerAnsible collection (>=5.2.0)
Variables
| Variable | Default | Description |
|---|---|---|
keycloak_instance |
keycloak |
Unique instance name. Used as Compose project name and container names. |
keycloak_data_path |
/data/keycloak |
Host path for persistent data (PostgreSQL data). |
keycloak_docker_path |
/docker/keycloak |
Host path for the Compose file. |
keycloak_version |
26.7.4 |
Keycloak image tag (quay.io/keycloak/keycloak). Pin to a specific release in production. |
keycloak_db_version |
pg17 |
PostgreSQL image tag (pgvector/pgvector). Major version upgrades need a manual dump and restore. |
keycloak_domains |
[] |
List of public domains, routed by Traefik. Required, at least one. The first one is the Keycloak hostname. |
keycloak_traefik_certresolver |
default |
Traefik certificate resolver to use for keycloak_domains. |
keycloak_root_redirects |
{} |
domain: path redirects for / (e.g. to a realm's account page) instead of Keycloak's admin console redirect. |
keycloak_username |
admin |
Username of the initial admin account. Only applied on first start. Also used to apply keycloak_realms. |
keycloak_pass |
admin |
Password of the initial admin account. Only applied on first start. Change the default. |
keycloak_realms |
[] |
Realms to create and keep up to date, in Keycloak's realm export format. Each entry needs a realm key. |
keycloak_oidc_clients |
[] |
OpenID Connect clients of web apps, generated into their realm. See OIDC clients. |
keycloak_config_cli_version |
6.5.1-26.5.5 |
adorsys/keycloak-config-cli image tag. The part after the dash is the Keycloak version it is built for. |
keycloak_themes |
[] |
Theme directories on the controller. Each becomes a theme named after the directory. A change recreates the Keycloak container. |
keycloak_db_name |
keycloak |
PostgreSQL database name. |
keycloak_db_user |
keycloak_usr |
PostgreSQL user. |
keycloak_db_pass |
~ |
PostgreSQL password. Required. Generate with: openssl rand -hex 32 |
keycloak_uninstall |
false |
Stops and removes containers and the compose directory. |
keycloak_purge |
false |
Also removes the data directory (including the database). Requires keycloak_uninstall: true. Irreversible. |
Usage
Minimal setup:
keycloak_pass: "{{ vault_keycloak_pass }}"
keycloak_db_pass: "{{ vault_keycloak_db_pass }}"
keycloak_domains:
- "auth.example.com"
Multiple domains (all are routed by Traefik, the first one is the Keycloak hostname):
keycloak_domains:
- "auth.example.com"
- "sso.example.com"
Realm with Active Directory users, a group and custom branding:
keycloak_themes:
- "{{ playbook_dir }}/files/keycloak/themes/tni"
keycloak_realms:
- realm: tni
enabled: true
displayName: TNI
loginTheme: tni
emailTheme: tni
attributes:
frontendUrl: https://auth.tni.dev
groups:
- name: admins
components:
org.keycloak.storage.UserStorageProvider:
- name: ad
providerId: ldap
config:
vendor: [ad]
connectionUrl: ["ldaps://dc01.tni.local"]
usersDn: ["OU=Users,DC=tni,DC=local"]
bindDn: ["CN=keycloak,OU=Service,DC=tni,DC=local"]
bindCredential: ["{{ vault_tni_ad_bind_pass }}"]
editMode: [READ_ONLY]
usernameLDAPAttribute: [sAMAccountName]
rdnLDAPAttribute: [cn]
uuidLDAPAttribute: [objectGUID]
userObjectClasses: ["person, organizationalPerson, user"]
subComponents:
org.keycloak.storage.ldap.mappers.LDAPStorageMapper:
- name: groups
providerId: group-ldap-mapper
config:
groups.dn: ["OU=Groups,DC=tni,DC=local"]
mode: [READ_ONLY]
The easiest way to write a realm is to set it up in the admin console once, export it (Realm settings → Action → Partial export), convert it to YAML and remove all id fields and values that equal Keycloak's defaults.
Uninstall
To stop and remove all containers and the compose directory:
keycloak_uninstall: true
Add keycloak_purge: true to also delete the database.
OIDC clients
keycloak_oidc_clients is a short form for the usual web app client, so the realm doesn't need a full client representation. Each entry becomes a confidential client with only the authorization code flow enabled (no direct access grants, no implicit flow, no service account) and is appended to the clients of its realm, which must be listed in keycloak_realms.
| Key | Required | Default | Description |
|---|---|---|---|
realm |
yes | — | Realm of the client. Must be in keycloak_realms. |
client_id |
yes | — | Client ID. |
secret |
yes | — | Client secret. Generate with openssl rand -hex 32 and keep it in the vault. |
redirect_uris |
yes | — | List of valid redirect URIs. |
name |
no | client_id | Display name. |
url |
no | unset | Root and home URL of the app. |
post_logout_redirect_uris |
no | ["+"] |
List of valid post logout redirect URIs (+ = same as the redirect URIs). |
pkce |
no | true |
Require PKCE (S256). |
roles |
no | [] |
Client roles to create. Assign them to groups with clientRoles in the realm. |
roles_claim |
no | roles |
Token claim (ID token, access token, userinfo) the user's roles of this client are written to. Only added when roles is set. |
attributes |
no | {} |
Merged into the client attributes, e.g. access.token.lifespan. |
extra |
no | {} |
Merged into the generated client representation, for anything not covered above. |
Example: a Nextcloud client whose admin role is granted by the group nextcloud admin:
keycloak_realms:
- realm: example
enabled: true
groups:
- name: nextcloud admin
clientRoles:
nextcloud:
- admin
keycloak_oidc_clients:
- realm: example
client_id: nextcloud
secret: "{{ vault_nextcloud_oidc_secret }}"
url: https://cloud.example.com
redirect_uris:
- https://cloud.example.com/apps/user_oidc/code
roles:
- admin
The secret has to reach the app's configuration too. Keep it in one vault variable that both playbooks read (e.g. through hostvars of the app host), not in two places.
Notes
- Upgrades. Bump
keycloak_versionand read the upgrading guide first. Keycloak migrates its database schema on the first start of a new version, so take a database backup before. Downgrades are not supported. - Hostname. The first entry of
keycloak_domainsis set asKC_HOSTNAME(https://<domain>). Keycloak builds all issuer and redirect URLs from it, so changing it breaks existing client configurations and issued tokens. The other domains are routed by Traefik but redirect to the first one, unless a realm sets its ownfrontendUrl(see below). - Initial admin.
keycloak_usernameandkeycloak_passcreate a temporary bootstrap admin on the first start only, when the database is empty. Later changes have no effect; manage admins in the Keycloak admin console. The same credentials are used to applykeycloak_realms, so when you replace the bootstrap admin with a permanent one, update both variables to the new account. - Realms. Each entry of
keycloak_realmsis rendered to<keycloak_docker_path>/realms/<realm>.jsonand applied by a one-shot keycloak-config-cli container on the internal network after the deploy. It only changes what is in the file; resources created in the admin console stay untouched. Resources that keycloak-config-cli created itself (groups, roles, clients, components, …) are deleted again when removed from the file. Users are never deleted. Removing a realm from the list only stops managing it; the realm stays in Keycloak. - Import cache. keycloak-config-cli skips a realm file whose checksum it already imported, so a run without changes does nothing. Changes made in the admin console to managed resources are therefore only reverted with the next change to that realm's file.
- keycloak-config-cli version. The newest image is built for Keycloak 26.5.5, the default
keycloak_versionis newer. The admin API is stable within 26.x, but test a Keycloak upgrade against your realms before rolling it out. - Placeholders. Variable substitution of keycloak-config-cli is disabled, so
${...}strings (e.g.${role_admin}in role descriptions) are passed to Keycloak unchanged. Secrets in realms (bind passwords, client secrets) are rendered into the realm files (mode0600, root-only), likecompose.yml. - Users. Only list local accounts and service users in a realm. Users from Active Directory come in through the LDAP user federation and must not be listed.
- Themes. Each directory in
keycloak_themesis copied to<keycloak_data_path>/themes/<name>, which is mounted read-only at/opt/keycloak/themes. Select it per realm withloginTheme,accountThemeandemailTheme. A changed theme recreates the Keycloak container before the realms are applied (a restart would keep serving Keycloak's gzipped copies of the old files). Themes removed from the list are not deleted from the host. Themes packaged as a JAR (e.g. Keycloakify) are not supported. - Per-realm hostname. Set the realm attribute
frontendUrl(see the example above) to give a realm its own domain fromkeycloak_domains. Without it, the realm uses the first domain. - Reverse proxy. Keycloak runs with HTTP enabled on port 8080 and trusts
X-Forwarded-*headers (KC_PROXY_HEADERS=xforwarded). Only expose it through Traefik, never directly: a client that reaches Keycloak directly can spoof these headers. - Healthcheck. Keycloak exposes
/health/readyon its management port 9000, which is only reachable inside the container. The deploy waits until both Keycloak and PostgreSQL report healthy. - Postgres is internal. PostgreSQL does not publish a host port; it is only reachable on the instance's internal Docker network. The data directory is owned by
999:999. - Passwords are inlined. Secrets are rendered directly into
compose.yml(mode0600, root-only), like the other Docker roles. Avoid$in passwords, as Compose would interpret it. - Traefik is required. Keycloak is always proxied by Traefik, so the host must already have a
traefik_webnetwork and a matchingcertresolver(see thetraefikrole).