No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-28 09:39:20 +00:00
.gitea/workflows feat: initial keycloak deployment 2026-09-26 00:52:08 +02:00
defaults feat: added optional redirect for the accounts page 2026-09-28 11:39:10 +02:00
meta feat: added realm config support 2026-09-26 01:19:33 +02:00
tasks feat: added optional redirect for the accounts page 2026-09-28 11:39:10 +02:00
templates feat: added optional redirect for the accounts page 2026-09-28 11:39:10 +02:00
.gitignore feat: initial keycloak deployment 2026-09-26 00:52:08 +02:00
CHANGELOG.md feat: added optional redirect for the accounts page 2026-09-28 11:39:10 +02:00
LICENSE feat: initial keycloak deployment 2026-09-26 00:52:08 +02:00
README.md Prettified Code! 2026-09-28 09:39:20 +00:00

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.docker Ansible 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_version and 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_domains is set as KC_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 own frontendUrl (see below).
  • Initial admin. keycloak_username and keycloak_pass create 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 apply keycloak_realms, so when you replace the bootstrap admin with a permanent one, update both variables to the new account.
  • Realms. Each entry of keycloak_realms is rendered to <keycloak_docker_path>/realms/<realm>.json and 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_version is 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 (mode 0600, root-only), like compose.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_themes is copied to <keycloak_data_path>/themes/<name>, which is mounted read-only at /opt/keycloak/themes. Select it per realm with loginTheme, accountTheme and emailTheme. 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 from keycloak_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/ready on 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 (mode 0600, 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_web network and a matching certresolver (see the traefik role).