When I mapped out the Mosburn Lab stack, the list looked like this: Forgejo for code, Redmine for project tracking, Docmost for documentation, TheHive for security research, ELK for log aggregation. Every single one has a login page. Every login page means credentials. And credentials at scale mean password managers, shared secrets, access drift, and eventually the realization that someone’s dev account still has admin rights to something they stopped using a year ago.

Better password hygiene doesn’t fix this. Centralizing authentication before you deploy anything else does.

Why Keycloak

Keycloak is Red Hat’s open source identity and access management platform. OAuth2, OpenID Connect, SAML 2.0. Every major application framework can talk to it. Runs fine on modest hardware. Has an admin interface that doesn’t require you to write LDAP queries.

The alternatives I actually considered:

Authentik is excellent and has a better UI, honestly. I went with Keycloak because the enterprise support story is stronger and the third-party integration docs are more thorough. Either works.

Authelia is lighter and simpler, but it’s primarily a forward-auth proxy rather than a full identity provider. Fine for protecting static services. Not enough for apps that need to issue tokens to their own APIs.

Kanidm is interesting — Rust implementation, strong consistency guarantees — but ecosystem support is still catching up.

Keycloak wins on coverage. If a self-hostable application supports OIDC, Keycloak has a documented path for it.

The architecture

In the Mosburn Lab, Keycloak sits at the center of the auth graph:

                        ┌──────────────────┐
                        │    Keycloak      │
                        │  (mosburn realm) │
                        └────────┬─────────┘
              ┌──────────────────┼──────────────────┐
              │                  │                  │
         ┌────▼───┐        ┌─────▼────┐      ┌─────▼────┐
         │Forgejo │        │ Redmine  │      │ Docmost  │
         └────────┘        └──────────┘      └──────────┘

Each application registers as an OIDC client in the mosburn realm: unique client ID and secret, scoped redirect URIs, claims mapped to what the application actually needs. Users log in once. Keycloak issues a session. Everything else inherits it.

What this changes operationally

Onboarding: One account in Keycloak. Access to every service that role covers, no per-application user creation required.

Offboarding: Disable the account. Access revoked everywhere immediately. No checklist of applications to hunt down.

Audit trail: Authentication events centralized. You can see when anyone last logged into any service from a single view.

Access policy: Role-based access defined once. Apps trust the claims in the Keycloak token instead of maintaining their own permission models.

The Ansible approach

The mosburn.keycloak role deploys via Docker Compose with a PostgreSQL backend and a systemd unit managing the container lifecycle. Structured to support native installation on Fedora and Ubuntu too — useful when you don’t want Docker overhead on a given host.

Configuration in defaults/main.yml. Realm name, admin credentials, database passwords, hostname — all variables. Nothing hardcoded. Deploy to any inventory host with the right credentials via -e or Ansible Vault.

Deployment order is not optional

Keycloak has to be running before you configure the services authenticating against it. This is why lab.yml deploys it first:

- name: Deploy Keycloak
  hosts: keycloak
  become: true
  roles:
    - mosburn.docker_host
    - mosburn.keycloak

- name: Deploy Forgejo
  hosts: forgejo
  ...

The Forgejo role registers Keycloak as an OAuth2 provider via the Forgejo admin API at the end of its run. Keycloak not up? Registration fails. Order is enforced by the playbook, not by hope.

One thing I’d do differently

Realm and client configuration is currently manual after the initial deploy. The keycloak_* modules in community.general can automate client creation, role mapping, and identity provider config — but they need the Keycloak admin REST API available during the Ansible run. Next iteration of the role handles that idempotently, the same way the Forgejo role handles its OAuth2 provider registration. For now, the changeme client secrets in defaults/main.yml make it obvious that post-deploy config is still required.

Identity first. Everything else depends on it.