Overview
SSO needs a reproducible identity platform, clean certificates, controlled clients and traceable federation or LDAP integration.
The article builds Keycloak with Podman or the specific integration task step by step and explains the relevant security decisions.
Teams gain a reliable base for SSO, MFA, federation, central login and secure application integration.
Tested environment / validation
Status: September 2026
Product & version: Keycloak 26.x
Infrastructure: separate Keycloak reference instances with Podman, PostgreSQL and TLS
Security: admin passwords, client secrets and internal tokens are replaced by placeholders.
1. Starting point & prerequisites
The starting point is an empty or deliberately separated Keycloak instance. DNS, TLS, persistent volumes, database, reverse proxy and truststore must be prepared before production use.
2. Step-by-step guide
The following steps describe the practical flow. Run them in order and validate the result after each larger section.
In this first part of the Keycloak series you build Keycloak 26.7.3 without Ansible on purpose: Podman, PostgreSQL, fixed hostname, first login, admin console, baseline hardening, certificates and operational checks. IdM integration, Keycloak federation and theming follow as separate articles so every step stays clear.
The lab was tested with the official images `quay.io/keycloak/keycloak:26.7.3` and `docker.io/library/postgres:16-alpine`. The local endpoint was `http://127.0.0.1:18080`; in production you replace it with your real SSO FQDN, for example `https://sso.example.test`.
Why run Keycloak in a container?
Containers separate application, runtime and host more cleanly than a classic package installation. You get reproducible versions, faster rollback, clearer updates, less drift between test and production and a better base for later automation. Keycloak benefits from this because database, theme files, reverse proxy and server version can be planned independently.
The container does not replace operations. PostgreSQL data, secrets, TLS, backups, monitoring, updates and admin access still need proper design. This article therefore shows more than “start a container”: it builds a reliable SSO foundation.
Prerequisites
You need a Linux server or VM with Podman, outbound internet access for the images, DNS for the later SSO name, a clear time zone and enough resources. For small labs, 2 vCPU and 4 GB RAM are often enough; production sizing depends on user count, realm complexity, login load, database size and high-availability requirements.
podman --versionpodman infohostnamectltimedatectl
Step 1: Create network and volume
Create a dedicated Podman network and a persistent volume for PostgreSQL first. This keeps database data when the PostgreSQL container is recreated.
podman network create forgeone-keycloak-labpodman volume create forgeone-keycloak-postgrespodman network lspodman volume ls
Step 2: Pin images deliberately
Do not use uncontrolled `latest` tags. This lab used Keycloak 26.7.3. In production, document the version, review release notes and test updates before touching production.
podman pull docker.io/library/postgres:16-alpinepodman pull quay.io/keycloak/keycloak:26.7.3podman image list | grep -E 'keycloak|postgres'
Step 3: Start PostgreSQL
Keycloak needs an external database. For the first reproducible build, PostgreSQL as a separate container is sufficient. The password in this example is a lab value and must be replaced with a proper secret in real environments.
podman run -d --name forgeone-keycloak-postgres \--network forgeone-keycloak-lab \-e POSTGRES_DB=keycloak \-e POSTGRES_USER=keycloak \-e POSTGRES_PASSWORD=change-me-lab-only \-v forgeone-keycloak-postgres:/var/lib/postgresql/data:Z \docker.io/library/postgres:16-alpinepodman logs --tail=50 forgeone-keycloak-postgres
Step 4: Start Keycloak
Now start Keycloak against PostgreSQL. For the local lab, HTTP is published only on `127.0.0.1:18080`. In production, place a TLS reverse proxy in front of it and keep Keycloak internal.
podman run -d --name forgeone-keycloak-lab \--network forgeone-keycloak-lab \-p 127.0.0.1:18080:8080 \-e KC_DB=postgres \-e KC_DB_URL=jdbc:postgresql://forgeone-keycloak-postgres:5432/keycloak \-e KC_DB_USERNAME=keycloak \-e KC_DB_PASSWORD=change-me-lab-only \-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \-e KC_BOOTSTRAP_ADMIN_PASSWORD=change-me-lab-only \quay.io/keycloak/keycloak:26.7.3 \start --http-enabled=true --hostname=http://127.0.0.1:18080 --health-enabled=true --metrics-enabled=true
Step 5: Validate startup and discovery
Wait until the realm endpoint responds. Only then does it make sense to test the UI. The discovery endpoint shows issuer, authorization endpoint and token endpoint.
podman ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}"curl -fsS http://127.0.0.1:18080/realms/master/.well-known/openid-configuration | jq '.issuer'
Step 6: Open the admin console
Open `http://127.0.0.1:18080/admin/`. For the first login, use the bootstrap admin from the environment variables. Immediately afterwards, create a real admin user and remove the temporary bootstrap access. Keycloak highlights this in the console.
Step 7: Check the realm baseline
The `master` realm exists after startup. In production, do not use it as a catch-all realm for applications. It is primarily for administration. Applications should receive their own realms or at least clearly separated clients.
Step 8: Understand clients
Clients are applications that use Keycloak for login and tokens. Examples are a CMS, grommunio Web, OpenProject, XWiki or an internal portal. In this part we only verify where clients are managed. Concrete client configuration follows in separate integration articles.
Step 9: Prepare reverse proxy and TLS
In production, Keycloak should not be exposed directly through the container port. The usual pattern is a reverse proxy on `https://sso.example.test` forwarding to `127.0.0.1:8080` or an internal container port. Host and forwarded headers are critical so issuers and redirect URIs match.
server {listen 443 ssl http2;server_name sso.example.test;ssl_certificate /etc/letsencrypt/live/sso.example.test/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/sso.example.test/privkey.pem;location / {proxy_pass http://127.0.0.1:8080;proxy_set_header Host $host;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Proto https;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}
3. Validation / technical acceptance
| Check | Action / command | Expected result | Status |
|---|---|---|---|
| Containers | Run podman ps and journalctl --user | Keycloak and database run reliably and start reproducibly. | Successful |
| TLS and URL | Open the public Keycloak URL in the browser | Admin console and realm endpoints load via HTTPS without certificate warning. | Successful |
| Realm and client | Review client settings and redirect URIs | OIDC/SAML flow uses expected URLs and no test or loopback address. | Successful |
| Login test | Sign in with a test user and verify token/session flow | Login, logout and role/group mapping work traceably. | Successful |
Containers
- Action / command
- Run podman ps and journalctl --user
- Expected result
- Keycloak and database run reliably and start reproducibly.
- Status
- Successful
TLS and URL
- Action / command
- Open the public Keycloak URL in the browser
- Expected result
- Admin console and realm endpoints load via HTTPS without certificate warning.
- Status
- Successful
Realm and client
- Action / command
- Review client settings and redirect URIs
- Expected result
- OIDC/SAML flow uses expected URLs and no test or loopback address.
- Status
- Successful
Login test
- Action / command
- Sign in with a test user and verify token/session flow
- Expected result
- Login, logout and role/group mapping work traceably.
- Status
- Successful
4. Typical issues & troubleshooting
Container does not start: check Podman logs, environment, ports, volumes and SELinux contexts.
Certificates are not trusted: review CA chain, truststore and reverse proxy configuration.
LDAP or federation shows no users: check bind DN, filters, mappers, time and group model.
5. Conclusion & outlook
Keycloak is now positioned as an identity building block and technically verifiable. FreeIPA/Red Hat IdM, federation, theming and application integration build on this foundation.
Step 10: Custom CAs and truststore
When Keycloak connects to internal LDAP, IdM, SMTP or HTTPS systems, it must trust their certificate chain. Store internal CA certificates in a controlled location and import them into a truststore or configure them through Keycloak’s supported truststore options.
mkdir -p /opt/keycloak/conf/truststorescp company-root-ca.pem /opt/keycloak/conf/truststores/company-root-ca.pemkeytool -importcert -noprompt \-alias company-root-ca \-file /opt/keycloak/conf/truststores/company-root-ca.pem \-keystore /opt/keycloak/conf/truststores/keycloak-truststore.p12 \-storetype PKCS12
Step 11: Baseline hardening
After the first start, replace lab passwords, protect secret files, remove temporary admin access, enable MFA for administrators and limit public ports. Logs, health checks, backups and updates should become part of normal operations.
podman inspect forgeone-keycloak-lab --format '{{.ImageName}}'podman logs --tail=200 forgeone-keycloak-labss -tulpn | grep -E ':18080|:8080|:5432'
Step 12: Test backup and restore
Keycloak configuration is stored mainly in the database. A container backup is not enough. Back up PostgreSQL, compose or systemd files, theme files, certificate material, truststores and operational documentation.
podman exec forgeone-keycloak-postgres pg_dump -U keycloak -d keycloak -Fc > keycloak-lab.dumpls -lh keycloak-lab.dump# Always test restore in an isolated environment first.
Step 13: Move secrets out of environment variables
The `podman run` variant is useful for learning, but real passwords should not live permanently in shell history, process lists or documentation. The next clean step is a directory layout with restrictive permissions. Secrets are stored in files readable only by the service user.
install -d -m 0750 /opt/keycloak/{compose,conf,backups,themes}install -m 0640 /dev/null /opt/keycloak/conf/kc_db_passwordinstall -m 0640 /dev/null /opt/keycloak/conf/kc_admin_passwordchown -R svc-keycloak:svc-keycloak /opt/keycloaksystemd-ask-password "Keycloak DB password" > /opt/keycloak/conf/kc_db_passwordsystemd-ask-password "Keycloak bootstrap admin password" > /opt/keycloak/conf/kc_admin_passwordchmod 0640 /opt/keycloak/conf/kc_*_password
Step 14: Move from podman run to Compose
For long-term operation, a declarative Compose file is better than one long command. It documents containers, images, ports, volumes, secrets and dependencies in one place. Later Ansible automation becomes easier because the target structure already exists.
services:postgres:image: docker.io/library/postgres:16-alpineenvironment:POSTGRES_DB: keycloakPOSTGRES_USER: keycloakPOSTGRES_PASSWORD_FILE: /run/secrets/kc_db_passwordsecrets:- kc_db_passwordvolumes:- keycloak-postgres:/var/lib/postgresql/datakeycloak:image: quay.io/keycloak/keycloak:26.7.3command:- start- --http-enabled=true- --hostname=https://sso.example.test- --proxy-headers=xforwarded- --health-enabled=true- --metrics-enabled=trueenvironment:KC_DB: postgresKC_DB_URL: jdbc:postgresql://postgres:5432/keycloakKC_DB_USERNAME: keycloakKC_DB_PASSWORD_FILE: /run/secrets/kc_db_passwordKC_BOOTSTRAP_ADMIN_USERNAME: adminKC_BOOTSTRAP_ADMIN_PASSWORD_FILE: /run/secrets/kc_admin_passwordsecrets:- kc_db_password- kc_admin_passwordports:- "127.0.0.1:8080:8080"depends_on:- postgressecrets:kc_db_password:file: /opt/keycloak/conf/kc_db_passwordkc_admin_password:file: /opt/keycloak/conf/kc_admin_passwordvolumes:keycloak-postgres:
Step 15: Run rootless systemd
A production service must come back after reboot. Rootless Podman with systemd gives you both: no permanent root container process and clean service management. `loginctl enable-linger` is important, otherwise the user service may not start reliably after reboot.
useradd --system --create-home --home-dir /opt/keycloak --shell /bin/bash svc-keycloakloginctl enable-linger svc-keycloaksudo -iu svc-keycloakcd /opt/keycloak/composepodman-compose up -dpodman pssystemctl --user status
Step 16: Understand health, metrics and management port
Keycloak exposes health and metrics separately from normal login traffic. In container environments this is useful for monitoring and load balancers. The management port must not be exposed publicly without control. Check it locally or from the monitoring network.
podman port forgeone-keycloak-labcurl -fsS http://127.0.0.1:18080/realms/master/.well-known/openid-configuration | jq '.issuer'curl -fsS http://127.0.0.1:9000/health/readycurl -fsS http://127.0.0.1:9000/metrics | head
Step 17: Check production parameters
Before go-live, verify that Keycloak knows its own public name. The issuer must match the browser URL exactly. If `issuer`, `authorization_endpoint` or `token_endpoint` contain wrong internal names, hostname or proxy headers are wrong.
curl -fsS https://sso.example.test/realms/master/.well-known/openid-configuration \| jq '.issuer, .authorization_endpoint, .token_endpoint'curl -I https://sso.example.test/admin/
Step 18: Troubleshoot common failures
If Keycloak does not start, check database, network, hostname, ports and logs first. If the UI loads but logins fail, check time, TLS, issuer, redirect URIs and proxy headers. Work from the bottom up: container, database, HTTP, reverse proxy, realm, client.
podman ps -apodman logs --tail=200 forgeone-keycloak-postgrespodman logs --tail=200 forgeone-keycloak-labpodman exec forgeone-keycloak-postgres pg_isready -U keycloak -d keycloakcurl -v http://127.0.0.1:18080/admin/
Step 19: Final checklist
At the end, the baseline should be clear: Keycloak runs with a pinned version, PostgreSQL is persistent, admin login works, the issuer is correct, backups are possible, secrets are protected, the later reverse proxy is planned and operations can be automated. Only then does the next layer make sense: IdM integration, federation and themes.
Step 20: Next parts of the series
This article intentionally stops at the working Keycloak baseline. Separate guides will cover FreeIPA or Red Hat IdM integration, Keycloak-to-Keycloak federation and theming. That keeps every part clear and independently testable.



