Skip to Content

Keycloak Docker Setup Guide: Self-Hosted SSO and Auth That Doesn't Make You Want to Quit

Deploy Keycloak with Docker Compose and PostgreSQL to get a production-ready identity provider handling SSO, OAuth2, and user federation in under 20 minutes.

Managing user accounts across a dozen internal tools is a recipe for shadow IT, password reuse, and late-night support calls. Keycloak is an open-source identity and access management server that gives you single sign-on, OAuth2, OpenID Connect, and SAML out of the box. Self-host it and you control the user store, the login flow, and the data residency.

This guide covers a complete Keycloak Docker setup with PostgreSQL persistence, reverse proxy configuration, and a working client application. For Traefik-based deployments, see our Traefik production guide. For advanced topics like user federation and high availability, check the advanced Keycloak guide.

What You Need Before Starting

Keycloak is not as lightweight as a static site generator. Plan for moderate resource use:

  • A Linux server with at least 2 vCPUs and 4 GB RAM
  • Docker Engine 24.x+ and Docker Compose v2 installed
  • At least 10 GB free disk space for the database and logs
  • A domain or subdomain with DNS pointing to your server
  • A valid TLS certificate (Let's Encrypt recommended)
  • Basic familiarity with OAuth2 concepts and realm configuration

Keycloak 26+ changed the admin bootstrapping process. The old KEYCLOAK_ADMIN variables are deprecated. We use the new KC_BOOTSTRAP_ADMIN format below.

Project Structure and Docker Compose

Create a dedicated directory and write a Compose file that defines PostgreSQL and Keycloak services. Use a Docker network for internal communication and a named volume for database persistence:

sudo mkdir -p /opt/keycloak
sudo chown $USER:$USER /opt/keycloak
cd /opt/keycloak

Create the docker-compose.yml:

services:
  postgres:
    image: postgres:16-alpine
    container_name: keycloak-db
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: ***
    networks:
      - keycloak
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    image: quay.io/keycloak/keycloak:26.3
    container_name: keycloak
    restart: unless-stopped
    command: start-dev
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: ***
      KC_HOSTNAME: auth.example.com
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: ***
      KC_PROXY_HEADERS: xforwarded
      KC_HTTP_ENABLED: 'true'
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - keycloak

volumes:
  postgres_data:

networks:
  keycloak:
    driver: bridge

We use start-dev for initial setup. For production, switch to start with a proper TLS termination setup. Generate strong passwords before starting the stack.

DB_PASSWORD=$(openssl rand -hex 24)
ADMIN_PASSWORD=$(openssl rand -hex 24)
echo "DB password: $DB_PASSWORD"
echo "Admin password: $ADMIN_PASSWORD"

# Update docker-compose.yml with generated passwords
sed -i "s/changeme/$DB_PASSWORD/g" docker-compose.yml
sed -i "s/adminpass/$ADMIN_PASSWORD/g" docker-compose.yml

Starting the Stack and First Login

Launch the containers and verify they start cleanly:

docker compose up -d
docker compose ps
docker compose logs --tail 30 keycloak

Wait for the database migrations to complete. Keycloak logs will show Keycloak 26.3.0 started when ready. Open the admin console:

http://your-server-ip:8080/admin

Log in with the bootstrap admin credentials. You will land in the master realm. Create a new realm for your applications rather than using master for day-to-day operations.

Reverse Proxy and HTTPS

Keycloak requires HTTPS for production use. Here is a minimal NGINX configuration with the required proxy headers:

server {
    listen 80;
    server_name auth.example.com;
    return 301 https://\$server_name\$request_uri;
}

server {
    listen 443 ssl http2;
    server_name auth.example.com;

    ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
        proxy_set_header X-Forwarded-Host \$host;
        proxy_set_header X-Forwarded-Port \$server_port;

        proxy_buffering off;
        proxy_request_buffering off;
    }
}

The X-Forwarded-* headers are critical. Without them, Keycloak generates incorrect redirect URLs and OAuth flows will break. The KC_PROXY_HEADERS: xforwarded environment variable tells Keycloak to trust these headers.

Obtain certificates with certbot:

sudo certbot --nginx -d auth.example.com
sudo systemctl reload nginx

Creating a Realm and Client

A realm is a tenant in Keycloak. Each realm has its own users, clients, and authentication flows. Create one for your application:

  1. Click the realm dropdown (currently showing master) and select Create realm.
  2. Enter a realm name, for example myapp, and click Create.
  3. Navigate to Clients and click Create client.
  4. Set Client ID to myapp-frontend and select OpenID Connect as the client type.
  5. In Valid redirect URIs, add https://myapp.example.com/*.
  6. In Web origins, add https://myapp.example.com.
  7. Save and note the client credentials from the Credentials tab.

Your application can now authenticate users against https://auth.example.com/realms/myapp.

User Management and Basic Federation

Creating Users Manually

For small teams, create users directly in the Keycloak admin console. Navigate to Users in your realm, click Add user, and set the username and email. Set a temporary password from the Credentials tab.

LDAP Federation

For larger organizations, connect Keycloak to your existing LDAP directory. In the realm settings, go to User federation and add an LDAP provider. Keycloak supports Active Directory, OpenLDAP, and generic LDAP servers. Users authenticate through Keycloak, but credentials are verified against your directory.

Backups and Updates

Database Backups

All realm configuration, users, and sessions live in PostgreSQL. Back it up regularly:

#!/bin/bash
BACKUP_DIR="/opt/backups/keycloak"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR

docker exec keycloak-db pg_dump -U keycloak keycloak \
  | gzip > $BACKUP_DIR/keycloak_$TIMESTAMP.sql.gz

find $BACKUP_DIR -name '*.sql.gz' -mtime +14 -delete

Updating Keycloak

Updates are straightforward. Pull the new image and recreate:

docker compose pull
docker compose up -d
docker compose logs --tail 20 keycloak

Always review the release notes before major version upgrades. Database migrations run automatically, but breaking changes to the admin API or client configuration can affect your applications.

Tips and Troubleshooting

Infinite redirect loop after login

This usually means the proxy headers are misconfigured. Verify KC_PROXY_HEADERS: xforwarded is set and that NGINX passes all X-Forwarded-* headers. Also confirm Valid redirect URIs in the client settings exactly match your application URLs, including the protocol.

Database connection fails on startup

Check that PostgreSQL passed its health check before Keycloak started. If Keycloak starts too fast, add an explicit wait or use a startup dependency with a longer retry count. Also verify the KC_DB_URL uses the service name postgres, not localhost.

Admin console shows a blank page

Keycloak's admin console is a React application that makes API calls relative to the hostname. If KC_HOSTNAME does not match the URL you are using in the browser, CORS and asset loading will fail. Set KC_HOSTNAME to your public domain.

Session timeouts are too aggressive

Adjust realm-level token settings from Realm settings > Tokens. Increase SSO Session Idle and SSO Session Max to match your security requirements. Shorter sessions are more secure but annoy users.

Cannot import realm from JSON

When importing a realm, ensure the JSON does not contain IDs that conflict with existing realms. Strip the id field from the realm object and let Keycloak generate a new one.

Next Steps

You now have a self-hosted Keycloak instance handling authentication for your applications. From here, you can add social identity providers (Google, GitHub, Microsoft), configure multi-factor authentication, and set up fine-grained authorization policies.

For related deployment patterns, explore our other guides:

Need help integrating Keycloak with your application stack, configuring LDAP federation, or scaling to clustered deployments? Contact our team for enterprise identity management consulting and managed infrastructure services.

Uptime Kuma Setup: The Self-Hosted Monitoring Stack You'll Actually Keep Running
Deploy Uptime Kuma with Docker Compose, configure monitors for HTTP, TCP, and Docker containers, and set up alerts that actually wake you up.