What is Keycloak#
If you enjoyed this article, you can support the author by becoming a sponsor on Boosty (link in the contacts section).
Keycloak is an open-source identity and access management (IAM) solution from Red Hat, with support for SSO, OAuth2, OpenID Connect, SAML 2.0, and a good dozen smaller standards on top. It’s probably the “heaviest” and, at the same time, the most feature-rich player among all the IAM solutions I’ve covered in this series - it’s the de facto standard in enterprise environments, and if you’ve ever logged into a corporate portal through a page with the “Keycloak” logo at the bottom, that’s exactly what it was.
In a home lab, Keycloak isn’t always justified - it’s noticeably heavier than Authentik or Authelia, and requires a Java stack under the hood. But if you want to get hands-on with the exact tool used in production at work, or you need specific enterprise features like realms and federation - this is the one you need.
Why Keycloak is needed#
Keycloak’s centralized user-authentication management is extensive:
- single sign-on (SSO) into all connected applications at once;
- login via external providers - Google, GitHub, and others through OAuth2/OIDC;
- connecting LDAP and Active Directory, if you already have a corporate user directory;
- a full web admin console, not just config files;
- native support for OpenID Connect and OAuth 2.0 with no workarounds.
What you’ll need#
- Docker and Docker Compose;
- a reverse proxy - in this article I use Traefik;
- a domain with DNS configured (mine is
keycloak.stilicho.ru); - an SSL certificate, most easily via Let’s Encrypt through Traefik itself.
Keycloak’s capabilities#
Keycloak really does have a lot of features, so let me break them down into logical groups so it’s clear what you actually get when installing it.
Authentication and authorization. Single Sign-On across all connected applications, login via social providers (Google, Facebook, GitHub, and others), support for OAuth2/OIDC/SAML 2.0, and multi-factor authentication via TOTP (Google Authenticator and similar).
User and role management. Full user, group, and role creation and management, delegated administration - you can define which admins manage which users - plus import and export of users via LDAP, CSV, or REST API.
Integration with corporate infrastructure. Direct integration with LDAP and Active Directory, SCIM-like capabilities via REST API, a full CLI client and an Admin REST API for automation.
Multi-tenancy and realms. Probably Keycloak’s main architectural highlight - realms, isolated authentication domains. Each realm has its own settings, users, clients, and policies - you can run a single Keycloak for several independent projects or clients, without worrying about them “seeing” each other’s users.
Customization. You can customize the login and registration screen UI to match your brand, extend functionality via Java SPI and plugins, and localize the interface into the language you need.
Auditing and security. Auditing login and user-action logs, flexibly configurable password policies, IP-based restrictions, and built-in brute-force protection.
The Docker Compose file used in the video#
Below is the configuration I used to deploy Keycloak in the video. It consists of two services: a PostgreSQL database and Keycloak itself, behind Traefik.
services:
postgres:
image: postgres:16-alpine
container_name: keycloak-db
restart: always
expose:
- 5432
volumes:
- /home/path/to/keycloak/database:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
healthcheck:
test:
[
"CMD",
"pg_isready",
"-q",
"-d",
"${POSTGRES_DB}",
"-U",
"${POSTGRES_USER}",
]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
networks:
- keycloak
keycloak:
image: quay.io/keycloak/keycloak:26.6.4 # don't use :latest - pin the version explicitly, check the current one at https://quay.io/repository/keycloak/keycloak?tab=tags
container_name: keycloak
command: start
environment:
KC_HOSTNAME: ${KEYCLOAK_HOSTNAME}
KC_BOOTSTRAP_ADMIN_USERNAME: ${KC_BOOTSTRAP_ADMIN_USERNAME}
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD}
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB}
KC_DB_USERNAME: ${POSTGRES_USER}
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
KC_PROXY_HEADERS: "xforwarded"
KC_HTTP_ENABLED: true
KC_HEALTH_ENABLED: true
PROXY_ADDRESS_FORWARDING: "true"
healthcheck:
test:
- "CMD-SHELL"
- |
exec 3<>/dev/tcp/localhost/9000 &&
echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 &&
cat <&3 | tee /tmp/healthcheck.log | grep -q '200 OK'
interval: 10s
timeout: 5s
retries: 3
start_period: 90s
#ports:
# - 8080:8080
#expose:
# - 8080 # web ui http
# - 9000 # health endpoint
restart: always
depends_on:
postgres:
condition: service_healthy
networks:
- keycloak
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.keycloak.entrypoints=http"
- "traefik.http.routers.keycloak.rule=Host(`keycloak.domain.ru`)"
- "traefik.http.middlewares.keycloak-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.keycloak.middlewares=keycloak-https-redirect"
- "traefik.http.routers.keycloak-secure.entrypoints=https"
- "traefik.http.routers.keycloak-secure.rule=Host(`keycloak.domain.ru`)"
- "traefik.http.routers.keycloak-secure.tls=true"
- "traefik.http.routers.keycloak-secure.service=keycloak"
- "traefik.http.services.keycloak.loadbalancer.server.port=8080"
- "traefik.docker.network=proxy"
networks:
keycloak:
internal: true
proxy:
external: trueThe values in the variables file
# define FQDN hostname
KEYCLOAK_HOSTNAME=keycloak.stilicho.ru
# define login credentials
KC_BOOTSTRAP_ADMIN_USERNAME=admin
KC_BOOTSTRAP_ADMIN_PASSWORD=password
# define database credentials
POSTGRES_DB=keycloak_db
POSTGRES_USER=keycloak_db_user
POSTGRES_PASSWORD=keycloak_db_user_passwordA few words about what’s happening here, because blindly copying someone else’s compose file is a bad idea.
The keycloak network is deliberately declared as internal: true - the database has no, and shouldn’t have any, direct exposure to the outside world; only the Keycloak container within the same docker network talks to it. Only the proxy network is exposed outward, and that’s how Traefik picks up the container via labels.
KC_PROXY_HEADERS: "xforwarded" and PROXY_ADDRESS_FORWARDING: "true" tell Keycloak that it’s sitting behind a reverse proxy and should trust the X-Forwarded-* headers - without this it will get confused about which protocol and host the client is actually using, and will generate incorrect redirect links. KC_HTTP_ENABLED: true allows Keycloak to listen on plain HTTP inside the container - TLS termination in this setup is handled by Traefik, not by Keycloak itself.
The healthcheck deserves a separate mention: as of a certain version, Keycloak’s health endpoints moved to a separate management port, 9000, rather than sitting on the main port 8080 with the rest of the application. That’s why the healthcheck uses plain bash via /dev/tcp instead of curl - the Keycloak image simply doesn’t include curl, and pulling in an extra package just for one check isn’t worth it.
First launch and creating a realm#
Deploy everything with docker compose up -d, wait for the database’s healthcheck to pass and for Keycloak itself to come up - on the first start this can take noticeably longer than subsequent restarts, since the service initializes its internal schemas in PostgreSQL.
After that, go to https://keycloak.stilicho.ru (you’ll of course have your own domain) and log in using the credentials from KC_BOOTSTRAP_ADMIN_USERNAME/KC_BOOTSTRAP_ADMIN_PASSWORD. Then, in order:
- Create a dedicated realm. By default Keycloak suggests working in the
masterrealm, but this is bad practice -masteris meant for administering Keycloak itself, not your applications. In the top-left corner, choose Create realm, and give it a meaningful name (for example,homelab). - Create a client. Inside the fresh realm, go to Clients → Create client, set a Client ID (usually matching the name of the protected service), enable the appropriate authentication type (Standard flow for a regular web app via OIDC), and specify the Valid redirect URIs - the address Keycloak will send the user back to after a successful login.
- Set up users. In Users, create the needed accounts, or configure integration with an external LDAP/Active Directory via User federation, if the users already exist somewhere and you don’t want to duplicate them manually.
- Configure MFA if desired. In Authentication, you can require specific groups of users to go through TOTP - this is done through flows, requires no config edits, and applies immediately to all clients in the realm.
From this point on, any application supporting OIDC or SAML can be connected to this realm as a separate client - and it will get single sign-on alongside all the other services connected the same way.
Conclusion#
Keycloak isn’t the lightest option for a home lab, and if you just need a quick forward-auth in front of a couple of services, Authelia or Authentik will probably be enough - I’ve covered both in neighboring articles. But if you want to get familiar with the exact tool actually used in enterprise, practice with realms, federation, and delegated administration, or you already have an LDAP/Active Directory that needs to be plugged in as-is - Keycloak more than earns its weight. A full comparison of all four solutions (including ZITADEL) is in a separate article.





