User Tools

Site Tools


computing:keycloak

This is an old revision of the document!


# Keycloak + Postgres — Rootless Podman Quadlet

Rootless Podman as user `worker` on host `support`. Apache + Let's Encrypt run as `root`. Keycloak is bound to localhost only.

Role Bind Public
— — —
Keycloak `127.0.0.1:8080` → container `8080` `https://auth.haacksnetworking.org`
Postgres internal only none

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)

```bash mkdir -p ~/keycloak/postgres-data ```

—

## 2. Network (worker)

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

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

```bash 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.

```bash #!/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`:

```bash 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)

```bash a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl ```

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

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

```bash 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. Open `https://auth.haacksnetworking.org` 2. Administration Console 3. User: `admin`

 Password: the bootstrap value (first empty volume only)

4. Admin → admin user → Credentials — set a new password, Temporary off 5. Realm settings → General

  1. Require SSL: external requests (or all)

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`

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

```bash 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.

```bash 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:

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

-

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