User Tools

Site Tools


computing:keycloak

This is an old revision of the document!



  • keycloak
  • Jonathan Haack
  • Haack's Networking
  • webmaster@haacksnetworking.org

Keycloak


Introduction

This tutorial is for Debian users desiring to spin up an OCI container for Keycloak. In my case, Keycloak was a compatible auth endpoint for Incus' Web GUI. The web GUI for Incus is open by default, which is not wise for a production container/vm server! You either needed to build a custom cert for the reverse proxy and server to everyone and/or lock down with </Location> directives, or you had to tunnel via a proxy and access it locally. Both are cumbersome and/or risky and it seemed mandatory that I setup a fully public-facing instance that also had secure log in with no browser barf -based solutions. For that reason, I first spun up podman, i.e., to spin up a Keycloak instance for Incus' web GUI and use it.

Images:

  • docker.io/library/postgres:16-alpine
  • quay.io/keycloak/keycloak:26.7

Network: keycloak_default Data: ~/keycloak/postgres-data Do not use podman generate systemd. Units come from Quadlet files in ~/.config/containers/systemd/.

1. Directory (worker)

mkdir -p ~/keycloak/postgres-data

2. Network (worker)

podman network exists keycloak_default || podman network create keycloak_default

3. Secrets

Placeholders used below:

POSTGRES_PASSWORD=replace-with-your-db-password
KC_BOOTSTRAP_ADMIN_PASSWORD=replace-with-your-bootstrap-admin-password

Bootstrap admin credentials apply only on an empty database. After the first start, change the admin password in the GUI. Do not rely on the env vars after that.

4. Quadlets

File: ~/.config/containers/systemd/container-keycloak-postgres.container

systemctl --user disable --now container-keycloak.service container-keycloak-postgres.service 2>/dev/null || true
rm -f ~/.config/systemd/user/container-keycloak.service \
      ~/.config/systemd/user/container-keycloak-postgres.service
podman rm -f keycloak keycloak-postgres

mkdir -p ~/.config/containers/systemd

cat > ~/.config/containers/systemd/container-keycloak-postgres.container << 'EOF'
[Container]
ContainerName=keycloak-postgres
Image=docker.io/library/postgres:16-alpine
Network=keycloak_default
Volume=/home/worker/keycloak/postgres-data:/var/lib/postgresql/data:Z
Environment=POSTGRES_DB=keycloak
Environment=POSTGRES_USER=keycloak
Environment=POSTGRES_PASSWORD=replace-with-your-db-password
PodmanArgs=--cpus=2 --memory=4g
[Service]
Restart=always
TimeoutStopSec=120
[Install]
WantedBy=default.target
EOF

cat > ~/.config/containers/systemd/container-keycloak.container << 'EOF'
[Unit]
After=container-keycloak-postgres.service
Wants=container-keycloak-postgres.service
[Container]
ContainerName=keycloak
Image=quay.io/keycloak/keycloak:26.7
Exec=start
Network=keycloak_default
PublishPort=127.0.0.1:8080:8080
Environment=KC_DB=postgres
Environment=KC_DB_URL=jdbc:postgresql://keycloak-postgres:5432/keycloak
Environment=KC_DB_USERNAME=keycloak
Environment=KC_DB_PASSWORD=replace-with-your-db-password
Environment=KC_HOSTNAME=https://auth.haacksnetworking.org
Environment=KC_HEALTH_ENABLED=true
Environment=KC_HTTP_ENABLED=true
Environment=KC_PROXY_HEADERS=xforwarded
PodmanArgs=--cpus=2 --memory=6g
[Service]
Restart=always
TimeoutStopSec=120
[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user reset-failed container-keycloak-postgres.service container-keycloak.service
systemctl --user start container-keycloak-postgres.service
systemctl --user start container-keycloak.service

WantedBy=default.target starts them. Do not systemctl enable a Quadlet unit. Do not podman generate systemd.

Keycloak depends on Postgres via the [Unit] After/Wants lines.

5. Verify

systemctl --user is-active container-keycloak-postgres.service container-keycloak.service
podman inspect keycloak-postgres keycloak --format '{{.Name}} cpus={{.HostConfig.NanoCpus}} memory={{.HostConfig.Memory}}'
podman exec keycloak-postgres pg_isready -U keycloak
curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/health/ready || curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/

Expect both active. Health may take a minute on first start while Keycloak builds. On 26.x the management health endpoint can be on port 9000 inside the container. If curl http://127.0.0.1:8080/ returns the welcome page, the proxy path is fine.

6. Upgrade script

/usr/local/bin/upgrade-keycloak.sh. A tag change is an edit to Image= in the matching .container file before daemon-reload. The script does not recreate the units.

#!/bin/bash
set -euo pipefail

podman pull docker.io/library/postgres:16-alpine
podman pull quay.io/keycloak/keycloak:26.7

podman network exists keycloak_default || podman network create keycloak_default

systemctl --user stop container-keycloak.service
systemctl --user stop container-keycloak-postgres.service

systemctl --user daemon-reload
systemctl --user reset-failed container-keycloak-postgres.service container-keycloak.service
systemctl --user start container-keycloak-postgres.service
systemctl --user start container-keycloak.service

Run it as worker:

su - worker -c '/bin/bash /usr/local/bin/upgrade-keycloak.sh'

Do not sudo -u worker. That drops the session bus.

7. Apache reverse proxy (root)

a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl

/etc/apache2/sites-available/auth.haacksnetworking.org.conf:

<VirtualHost *:80>
    ServerName auth.haacksnetworking.org
    RewriteEngine On
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

<VirtualHost *:443>
    ServerName auth.haacksnetworking.org
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/auth.haacksnetworking.org/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/auth.haacksnetworking.org/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf
    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port "443"
    RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
    ProxyPass / http://127.0.0.1:8080/ upgrade=websocket
    ProxyPassReverse / http://127.0.0.1:8080/
    ProxyTimeout 60
    LimitRequestFieldSize 65535
    ErrorLog ${APACHE_LOG_DIR}/auth.haacksnetworking.org-error.log
    CustomLog ${APACHE_LOG_DIR}/auth.haacksnetworking.org-access.log combined
</VirtualHost>
a2ensite auth.haacksnetworking.org.conf
apache2ctl configtest && systemctl reload apache2
curl -sI https://auth.haacksnetworking.org/

KC_PROXY_HEADERS=xforwarded matches the X-Forwarded-* headers. KC_HOSTNAME must be https://auth.haacksnetworking.org.

8. First login

  1. Administration Console
  2. User: admin
  3. Password: the bootstrap value (first empty volume only)
  4. Admin → admin user → Credentials — set a new password, Temporary off
  5. Realm settings → General

Do not keep the bootstrap password.

9. Incus OIDC

Stay out of master for apps. Create a realm.

Realm

  • Name: incus
  • Enabled: on

Issuer (must match Incus exactly):

https://auth.haacksnetworking.org/realms/incus

curl -sI https://auth.haacksnetworking.org/realms/incus/.well-known/openid-configuration

Must be 200. The issuer value in that JSON is what Incus uses.

Users (in realm incus)

  • Username for SSO
  • Email filled in if oidc.claim=email
  • Email verified: on
  • Credentials → password, Temporary off

Create one user per human. Do not log into Incus as Keycloak admin unless that user also exists in the incus realm.

Client

  • Clients → Create client
  • Type: OpenID Connect
  • Client ID: incus
  • Client authentication: Off (public / PKCE)
  • Standard flow: On
  • Valid redirect URIs: https://<incus-ui-host>/oidc/callback

Example: https://support.haacksnetworking.org:8443/oidc/callback

  • Web origins: https://<incus-ui-host>
  • Save

No client secret. Incus does not send one.

Incus

incus config set oidc.issuer=https://auth.haacksnetworking.org/realms/incus/
incus config set oidc.client.id=incus
incus config set oidc.scopes=openid,email,profile
incus config set oidc.claim=preferred_username

oidc.claim can be email instead.

incus config show | grep oidc

Open the Incus UI → Login with SSO → Keycloak incus realm user.

Then grant that identity. SSO alone is not admin:

incus auth identity list

From the unix socket as root, grant Admin (or project access) to the new oidc/ identity. Until you do, the UI logs in and then shows nothing / 403.

Facts

— oemb1905 2026/10/10 01:12

computing/keycloak.1791605890.txt.gz · Last modified: by oemb1905