User Tools

Site Tools


computing:keycloak

  • 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

Once you create the quadlets, you want to start the service/container and make sure everything is in order. To do that, we just check the service and then run curl against the api endpoint.

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/

If everything is alright, we should see active. If you don't, debug and clean up before proceeding. So long as everything is functional, we can pivot to setting up an upgrade script.

6. Upgrade script

OCI Containers aren't worth much without the ability to pull updates and patches quickly and painlessly. For me, a simple shell script gets this job done. Here's and example folks can tweak/edit to their liking:

#!/bin/bash
set -euo pipefail
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DBUS_SESSION_BUS_ADDRESS=unix:path=${XDG_RUNTIME_DIR}/bus

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

Once this runs successfully, you can then move on to setting up a reverse proxy with apache in order to forward url requests to the host/vm upstream to the keycloak service inside the container.

7. Apache reverse proxy (root)

Let's make sure the apache proxy/tunnel packages are enabled, as well as tls and location, and the certificate, are all active and ready to go.

a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl authz_host
certbot certonly --apache -d auth.haacksnetworking.org

Create the virtual host for http nano /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>

Likewise, create the virtual host for the tls block. You can optionally drop this underneath the http vhost, but/and I prefer them to be separate.

<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>

Finally, check the configurations, restart the service, then check the endpoint.

a2ensite auth.haacksnetworking.org.conf
apache2ctl configtest && systemctl reload apache2
curl -sI https://auth.haacksnetworking.org/

Check the output for any errors. In my notes, I had: KC_PROXY_HEADERS=xforwarded matches the X-Forwarded-* headers. KC_HOSTNAME must be https://auth.haacksnetworking.org. I'm not sure these are 100% accurate output parameters, but/and they were in my notes. I can update later if I rebuild the instance.

8. First login

When you first open keycloak, you want to remove the temp password and set up the admin account and master realm.

  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

9. Incus OIDC

We will now create a dedicated real for Incus' Web GUI called “incus”. Go to Realms > Add > incus > save. The endpoint should be https://auth.haacksnetworking.org/realms/incus. Once you set it up, test with:

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

Users (in realm incus)

Create one user per person using these recommendations:

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

After you create the user account, you create the entry for what keycloak is being associated with:

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

Incus

Configure incus on the CLI to accept the issuer:

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
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. Alright, some of these are still in “raw note” form and I will be back to edit and update these notes if/when I have to rebuild or do further testing. While this instance is in production and working, some steps are unclear to me until I make it a third time.

— oemb1905 2026/10/10 04:30

computing/keycloak.txt · Last modified: by oemb1905