Keycloak Docker Setup Guide: Self-Hosted SSO Without the Headaches
Every app wants its own user database. Before long you are juggling five password policies, two-factor setups, and a spreadsheet of who has access to what. Keycloak fixes this. It is an open-source identity broker that speaks OAuth2, OpenID Connect, and SAML. This guide gives you a production-ready Keycloak Docker setup guide—from a running container to a working login flow for your application.
If you want a quicker walkthrough focused on speed, see Keycloak Docker Setup Guide: From Zero to Self-Hosted SSO in 30 Minutes.
What You Will Build
By the end of this guide you will have:
- Keycloak running in Docker with a PostgreSQL backend
- HTTPS access behind a reverse proxy
- A custom realm with users and groups
- An OIDC client registered for your application
- A working login and logout flow you can test in a browser
Prerequisites
Before you start, make sure you have:
- A Linux server (Ubuntu 22.04+ or Debian 12+) with at least 2 CPU cores and 4 GB RAM
- Docker and Docker Compose installed
- A domain or subdomain pointed at your server (for HTTPS)
- Basic familiarity with YAML, OAuth2 concepts, and the command line
- Ports 80, 443, and 8080 available
Check your Docker version:
docker --version
docker compose version
If Docker is missing:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
Step 1: Deploy Keycloak with PostgreSQL
Keycloak ships with an embedded H2 database for testing. In production, you want PostgreSQL. It handles concurrency, survives restarts, and backs up cleanly.
Create a project directory:
mkdir ~/keycloak-stack && cd ~/keycloak-stack
mkdir -p postgres-data
Write a docker-compose.yml:
version: "3.8"
services:
postgres:
image: postgres:16
container_name: keycloak-db
restart: unless-stopped
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: change…e-db
volumes:
- ./postgres-data:/var/lib/postgresql/data
networks:
- keycloak-net
keycloak:
image: quay.io/keycloak/keycloak:26.1.0
container_name: keycloak
restart: unless-stopped
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: change…e-db
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: change…n123
KC_HOSTNAME: auth.yourdomain.com
KC_HOSTNAME_STRICT: 'false'
KC_HTTP_ENABLED: 'true'
KC_PROXY: edge
KC_LOG_LEVEL: info
ports:
- "8080:8080"
depends_on:
- postgres
networks:
- keycloak-net
command: start --optimized
Start the stack:
docker compose up -d
Wait about 30 seconds for the first startup, then open the Admin Console at http://your-server-ip:8080/admin. Log in with the bootstrap credentials from your Compose file.
Step 2: Create a Realm and a User
Keycloak organizes everything into realms. Think of a realm as a tenant. The master realm is for managing Keycloak itself. Create a separate realm for your applications.
Create a Realm
- In the Admin Console, click the realm dropdown (currently showing master)
- Click Create realm
- Name it
myapp - Click Create
Create a User
- Make sure you are in the
myapprealm - Click Users in the left menu, then Create new user
- Set username to
alice - Click Create
- Go to the Credentials tab and set a password
- Toggle Temporary to Off so the user is not forced to reset it
You now have a realm and a user. Next, register an application so Alice can actually log in somewhere.
Step 3: Register an OIDC Client
Keycloak calls applications clients. Each client gets a client ID and secret, plus a list of allowed redirect URLs.
- In the
myapprealm, click Clients, then Create client - Set Client type to OpenID Connect
- Set Client ID to
my-web-app - Click Next
- Enable Standard flow (this is the authorization code flow)
- Click Next
- Under Valid redirect URIs, add
https://app.yourdomain.com/* - Under Web origins, add
https://app.yourdomain.com - Click Save
Go to the Credentials tab and copy the Client secret. You will need it in your application.
For a deeper dive into client configuration and advanced flows, check out Keycloak Docker Setup Guide: Self-Hosted SSO That Actually Works.
Step 4: Test the Login Flow
Before wiring this into your real app, verify the flow works. You can use Keycloak's built-in account console as a smoke test.
Open this URL in a browser:
http://your-server-ip:8080/realms/myapp/account
Log in with alice and the password you set. If you see the account management page, your realm, user, and authentication flow are working.
Inspect the OpenID Configuration
Keycloak publishes its OIDC discovery document at a well-known URL:
curl http://your-server-ip:8080/realms/myapp/.well-known/openid-configuration | jq .
This JSON contains the authorization endpoint, token endpoint, and JWKS URI. Any standard OIDC library can consume it.
Step 5: Secure Keycloak with HTTPS and a Reverse Proxy
Running Keycloak on port 8080 over HTTP is fine for testing. In production, you need TLS. The cleanest path is Caddy.
Add Caddy to Your Compose File
caddy:
image: caddy:2.8
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
networks:
- keycloak-net
volumes:
caddy-data:
caddy-config:
Create Caddyfile:
auth.yourdomain.com {
reverse_proxy keycloak:8080
}
Update the Keycloak environment to use the public hostname:
KC_HOSTNAME: auth.yourdomain.com
KC_HOSTNAME_STRICT: 'false'
KC_HTTP_ENABLED: 'true'
KC_PROXY: edge
Restart the stack:
docker compose up -d
Caddy fetches a certificate from Let's Encrypt automatically. Your SSO is now at https://auth.yourdomain.com.
Step 6: Wire Keycloak Into Your Application
Now that Keycloak is running and secured, connect your application. Here is a minimal Node.js Express example using passport-openidconnect.
Install dependencies:
npm install express express-session passport passport-openidconnect
Create server.js:
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const { Strategy } = require('passport-openidconnect');
const app = express();
app.use(session({ secret: 'change-me', resave: false, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
passport.use('oidc', new Strategy({
issuer: 'https://auth.yourdomain.com/realms/myapp',
authorizationURL: 'https://auth.yourdomain.com/realms/myapp/protocol/openid-connect/auth',
tokenURL: 'https://auth.yourdomain.com/realms/myapp/protocol/openid-connect/token',
userInfoURL: 'https://auth.yourdomain.com/realms/myapp/protocol/openid-connect/userinfo',
clientID: 'my-web-app',
clientSecret: 'your-client-secret-here',
callbackURL: 'https://app.yourdomain.com/auth/callback',
scope: 'openid profile'
}, (issuer, profile, done) => {
return done(null, profile);
}));
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((obj, done) => done(null, obj));
app.get('/auth/login', passport.authenticate('oidc'));
app.get('/auth/callback',
passport.authenticate('oidc', { failureRedirect: '/' }),
(req, res) => res.redirect('/profile')
);
app.get('/profile', (req, res) => {
if (!req.isAuthenticated()) return res.redirect('/auth/login');
res.json(req.user);
});
app.listen(3000, () => console.log('App running on port 3000'));
Start the app, visit /auth/login, and you will be redirected to Keycloak. After authenticating, you land back on /profile with Alice's user info.
If you are struggling with the mental model behind realms, clients, and flows, Keycloak Docker Setup Guide: Self-Hosted SSO and Auth That Doesn't Make You Want to Quit breaks it down in plain language.
Tips and Troubleshooting
Keycloak fails to start with database connection errors
- Ensure PostgreSQL is healthy before Keycloak starts. The
depends_onin Compose helps, but Postgres still needs time to initialize - Verify the
KC_DB_URLmatches the Postgres service name:jdbc:postgresql://keycloak-db:5432/keycloak - Check that the Postgres password matches in both services
Infinite redirect loops after login
- Confirm Valid redirect URIs in the client settings includes your callback URL exactly, including the path
- Make sure
KC_PROXY=edgeis set so Keycloak trusts headers from the reverse proxy - Verify
KC_HOSTNAMEmatches the public URL users see
HTTPS required error in production mode
- Keycloak production mode disables HTTP by default. Either terminate TLS at the reverse proxy with
KC_PROXY=edge, or provide certificates directly to Keycloak - Do not set
KC_HTTP_ENABLED=truein production without understanding the risk
Admin console shows a blank page
- This usually means the hostname is misconfigured. Check
KC_HOSTNAMEand browser dev tools for mixed-content errors - If running behind a proxy, ensure
KC_PROXY=edgeis set and the proxy forwardsX-Forwarded-Proto
Sessions not persisting across restarts
- Without a persistent database, sessions live in memory and vanish on restart. The PostgreSQL setup in this guide fixes that
- For distributed setups, configure Infinispan caching with
KC_CACHE=ispn
Users cannot log in after password change
- If you left Temporary password on, Keycloak forces a reset at first login. Set it to Off for service accounts or initial user setup
Next Steps
You now have a working Keycloak Docker setup guide in action: a secured realm, a registered client, and a tested login flow. That is the foundation. From here, consider:
- Adding social identity providers (Google, GitHub, Microsoft) so users can log in with existing accounts
- Configuring groups and role mappings for fine-grained access control
- Enabling two-factor authentication via OTP or WebAuthn
- Setting up email verification and password reset flows with SMTP
- Using Keycloak's LDAP federation to sync with an existing corporate directory
SSO is not just about convenience. It is about owning your auth stack, auditing access, and sleeping better at night.
Need Help at Scale?
If you are running production workloads and need Keycloak clustering, custom themes, advanced federation, or migration from Auth0 / Okta, we can help. Contact the Sysbrix team and we will build an identity layer that fits your architecture.