This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| computing:keycloak [2026/10/10 00:59] – created oemb1905 | computing:keycloak [2026/10/10 04:51] (current) – oemb1905 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | # Keycloak + Postgres — Rootless Podman Quadlet | + | ------------------------------------------- |
| - | Rootless Podman as user `worker` on host `support`. Apache + Let' | + | * **keycloak** |
| + | * **Jonathan Haack** | ||
| + | * **Haack' | ||
| + | * **webmaster@haacksnetworking.org** | ||
| - | | Role | Bind | Public | | + | ------------------------------------------- |
| - | | --- | --- | --- | | + | |
| - | | Keycloak | `127.0.0.1: | + | |
| - | | Postgres | internal only | none | | + | |
| - | Images: | + | // |
| - | - `docker.io/ | + | ------------------------------------------- |
| - | - `quay.io/ | + | ~~NOTOC~~ |
| - | Network: `keycloak_default` | + | ==== Introduction ==== |
| - | Data: `~/ | + | |
| - | Do not use `podman generate systemd`. Units come from Quadlet files in `~/ | + | |
| - | --- | + | 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/ |
| - | ## 1. Directory (worker) | + | * '' |
| + | * '' | ||
| - | ```bash | + | Keycloak requires its own network in docker so I created a project directory, the network, and pulled the images. |
| - | mkdir -p ~/ | + | |
| - | ``` | + | |
| - | --- | ||
| - | ## 2. Network | + | ==== 1. Directory |
| - | + | < | |
| - | ```bash | + | mkdir -p ~/ |
| + | podman pull docker.io/ | ||
| + | podman pull quay.io/ | ||
| podman network exists keycloak_default || podman network create keycloak_default | 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. | + | |
| - | --- | + | ==== 3. Secrets ==== |
| - | ## 4. Quadlets | + | Now that we have the images and project directories, |
| - | File: `~/.config/containers/systemd/ | + | < |
| + | umask 077 | ||
| + | openssl rand -hex 32 > ~/keycloak/db.secret | ||
| + | openssl rand -hex 32 > ~/keycloak/bootstrap-admin.secret | ||
| + | chmod 600 ~/keycloak/db.secret ~/ | ||
| + | </ | ||
| - | ```bash | + | ==== 4. Quadlets ==== |
| - | systemctl --user disable --now container-keycloak.service container-keycloak-postgres.service 2>/ | + | |
| - | rm -f ~/ | + | |
| - | ~/ | + | |
| - | podman rm -f keycloak keycloak-postgres | + | |
| - | mkdir -p ~/.config/ | + | 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 > ~/ | cat > ~/ | ||
| [Container] | [Container] | ||
| Line 107: | Line 94: | ||
| systemctl --user start container-keycloak-postgres.service | systemctl --user start container-keycloak-postgres.service | ||
| systemctl --user start container-keycloak.service | systemctl --user start container-keycloak.service | ||
| - | ``` | + | </ |
| - | `WantedBy=default.target` starts them. Do not `systemctl enable` a Quadlet unit. Do not `podman generate systemd`. | + | ==== 5. Verify ==== |
| - | Keycloak depends on Postgres via the `[Unit]` After/Wants lines. | + | Once you create |
| - | --- | + | < |
| - | + | ||
| - | ## 5. Verify | + | |
| - | + | ||
| - | ```bash | + | |
| systemctl --user is-active container-keycloak-postgres.service container-keycloak.service | systemctl --user is-active container-keycloak-postgres.service container-keycloak.service | ||
| podman inspect keycloak-postgres keycloak --format ' | podman inspect keycloak-postgres keycloak --format ' | ||
| podman exec keycloak-postgres pg_isready -U keycloak | podman exec keycloak-postgres pg_isready -U keycloak | ||
| curl -fsS -o /dev/null -w ' | curl -fsS -o /dev/null -w ' | ||
| - | ``` | + | </code> |
| - | + | ||
| - | 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:// | + | |
| - | --- | + | If everything is alright, we should see '' |
| - | ## 6. 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: |
| - | ```bash | + | < |
| #!/bin/bash | #!/bin/bash | ||
| set -euo pipefail | set -euo pipefail | ||
| + | export XDG_RUNTIME_DIR=/ | ||
| + | export DBUS_SESSION_BUS_ADDRESS=unix: | ||
| podman pull docker.io/ | podman pull docker.io/ | ||
| Line 148: | Line 131: | ||
| systemctl --user start container-keycloak-postgres.service | systemctl --user start container-keycloak-postgres.service | ||
| systemctl --user start container-keycloak.service | systemctl --user start container-keycloak.service | ||
| - | ``` | + | </ |
| - | Run it as `worker`: | + | Once this runs successfully, |
| - | ```bash | + | ==== 7. Apache reverse proxy (root) ==== |
| - | su - worker -c '/ | + | |
| - | ``` | + | |
| - | Do not `sudo -u worker`. That drops the session bus. | + | Let's make sure the apache proxy/ |
| - | --- | + | < |
| + | a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl authz_host | ||
| + | certbot certonly | ||
| + | </ | ||
| - | ## 7. Apache reverse proxy (root) | + | Create the virtual host for http '' |
| - | ```bash | + | < |
| - | a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl | + | |
| - | ``` | + | |
| - | + | ||
| - | `/ | + | |
| - | + | ||
| - | ```apache | + | |
| < | < | ||
| ServerName auth.haacksnetworking.org | ServerName auth.haacksnetworking.org | ||
| Line 174: | Line 152: | ||
| RewriteRule ^ https:// | RewriteRule ^ https:// | ||
| </ | </ | ||
| + | </ | ||
| + | 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. | ||
| + | |||
| + | < | ||
| < | < | ||
| ServerName auth.haacksnetworking.org | ServerName auth.haacksnetworking.org | ||
| Line 192: | Line 174: | ||
| CustomLog ${APACHE_LOG_DIR}/ | CustomLog ${APACHE_LOG_DIR}/ | ||
| </ | </ | ||
| - | ``` | + | </ |
| - | ```bash | + | Finally, check the configurations, |
| + | |||
| + | < | ||
| a2ensite auth.haacksnetworking.org.conf | a2ensite auth.haacksnetworking.org.conf | ||
| apache2ctl configtest && systemctl reload apache2 | apache2ctl configtest && systemctl reload apache2 | ||
| curl -sI https:// | curl -sI https:// | ||
| - | ``` | + | </ |
| - | `KC_PROXY_HEADERS=xforwarded` matches the `X-Forwarded-*` headers. | + | Check the output for any errors. In my notes, I had: '' |
| - | --- | + | ==== 8. First login ==== |
| - | ## 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. Open `https:// | + | - Open '' |
| - | 2. Administration Console | + | |
| - | 3. User: `admin` | + | |
| - | | + | |
| - | 4. Admin → admin user → Credentials — set a new password, Temporary **off** | + | |
| - | 5. Realm settings → General | + | |
| - | - Frontend URL: `https:// | + | |
| - | - Require SSL: external requests (or all) | + | |
| - | Do not keep the bootstrap password. | + | ==== 9. Incus OIDC ==== |
| - | --- | + | We will now create a dedicated real for Incus' Web GUI called " |
| - | ## 9. Incus OIDC | + | curl -sI https:// |
| - | Stay out of `master` for apps. Create a realm. | + | **Users** (in realm '' |
| - | **Realm** | + | Create one user per person using these recommendations: |
| - | - Name: `incus` | + | * Username for SSO |
| - | - Enabled: on | + | * Email filled in if '' |
| + | * Email verified: on | ||
| + | * Credentials → password, Temporary **off** | ||
| - | Issuer (must match Incus exactly): | + | After you create the user account, you create the entry for what keycloak |
| - | + | ||
| - | `https:// | + | |
| - | + | ||
| - | ```bash | + | |
| - | curl -sI https:// | + | |
| - | ``` | + | |
| - | + | ||
| - | 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** | **Client** | ||
| - | - Clients → Create client | + | * Clients → Create client |
| - | - Type: OpenID Connect | + | |
| - | - Client ID: `incus` | + | |
| - | - Client authentication: | + | |
| - | - Standard flow: **On** | + | |
| - | - Valid redirect URIs: `https://< | + | |
| - | Example: | + | Example: |
| - | - Web origins: | + | |
| - | - Save | + | |
| - | + | ||
| - | No client secret. Incus does not send one. | + | |
| **Incus** | **Incus** | ||
| - | ```bash | + | Configure incus on the CLI to accept the issuer: |
| + | |||
| + | < | ||
| incus config set oidc.issuer=https:// | incus config set oidc.issuer=https:// | ||
| incus config set oidc.client.id=incus | incus config set oidc.client.id=incus | ||
| incus config set oidc.scopes=openid, | incus config set oidc.scopes=openid, | ||
| incus config set oidc.claim=preferred_username | incus config set oidc.claim=preferred_username | ||
| - | ``` | ||
| - | |||
| - | `oidc.claim` can be `email` instead. | ||
| - | |||
| - | ```bash | ||
| incus config show | grep oidc | incus config show | grep oidc | ||
| - | ``` | + | </ |
| - | Open the Incus UI → Login with SSO → Keycloak | + | Open the Incus UI → Login with SSO → Keycloak |
| - | Then grant that identity. SSO alone is not admin: | + | < |
| - | + | ||
| - | ```bash | + | |
| incus auth identity list | incus auth identity list | ||
| - | ``` | + | </code> |
| - | + | ||
| - | 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 | + | From the unix socket as root, grant Admin (or project access) to the new '' |
| - | - | + | --- // |