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 so I kept everything local and behind the ssh tunnel while I tinkered. To me, it was mandatory that Incus' Web GUI be fully public-facing, secure w/ TLS, and absolutely no janky browser barf -based solutions. For that reason, I first spun up podman, i.e., to spin up a Keycloak instance to attach to Incus' web GUI and liberate it from the tunnel. To begin with, the most minimal setup is the keycloack container and a separate postgres database for it to use:

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

Keycloak requires its own network in docker so I created a project directory, the network, and pulled the images.

1. Directory (worker)

mkdir -p ~/keycloak/postgres-data
podman pull docker.io/library/postgres:16-alpine
podman pull quay.io/keycloak/keycloak:latest
podman network exists keycloak_default || podman network create keycloak_default

3. Secrets

Now that we have the images and project directories, we can create the required secrets.

umask 077
openssl rand -hex 32 > ~/keycloak/db.secret
openssl rand -hex 32 > ~/keycloak/bootstrap-admin.secret
chmod 600 ~/keycloak/db.secret ~/keycloak/bootstrap-admin.secret

4. Quadlets

We can now safely create the quadlet systemd service units that run/monitor the keycloak and postgres containers. Tweak these snippets to your needs and or liking:

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

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 04:18

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