Self-Hosted SSO with Keycloak and Docker

• DevOps, Security, Authentication

Self-hosted OpenID Connect provider using Keycloak for SSO testing and development.

Requirements

  • Docker and Docker Compose
  • 1Password CLI for secure credential storage
  • Reverse proxy (Caddy) for SSL termination
  • Domain with DNS management access

Architecture

  • Keycloak: Identity provider container with persistent storage
  • Caddy: Reverse proxy handling SSL and external access
  • 1Password CLI: Secure credential retrieval

Docker Compose Configuration

version: '3.8'

services:
keycloak:
image: quay.io/keycloak/keycloak:latest
container_name: keycloak-sso
command: start-dev
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
KC_HTTP_PORT: 9090
KC_HOSTNAME: auth.yourdomain.com
KC_HOSTNAME_STRICT: false
KC_HTTP_ENABLED: true
KC_PROXY: edge
ports:
- "9090:9090"
volumes:
- keycloak_data:/opt/keycloak/data
restart: unless-stopped

volumes:
keycloak_data:
driver: local

Key settings: KC_PROXY: edge for reverse proxy, start-dev for development mode, persistent volume for data retention.

Secure Credential Management with 1Password

Store the Keycloak admin password in 1Password and retrieve it programmatically. Create an item called "keycloak-admin" with a strong generated password.

Startup Script

#!/bin/bash
# start-keycloak.sh

set -e  # Exit on any error

echo "Starting Keycloak with secure credentials..."

# Retrieve admin password from 1Password
echo "Retrieving admin credentials from 1Password..."
ADMIN_PASSWORD=$(op item get "keycloak-admin" --vault dev --reveal --field password)

if [ -z "$ADMIN_PASSWORD" ]; then
echo "Error: Failed to retrieve admin password from 1Password"
exit 1
fi

# Export password as environment variable for docker-compose
export ADMIN_PASSWORD

# Verify Keycloak container isn't already running
if docker ps | grep -q "keycloak-sso"; then
echo "Stopping existing Keycloak container..."
docker-compose down
fi

# Start the containers with password in environment
echo "Starting Keycloak container..."
docker-compose up -d

# Clear password from environment immediately
unset ADMIN_PASSWORD

# Wait for container to start
echo "Waiting for Keycloak to initialize..."
sleep 5

# Verify container is running
if docker ps | grep -q "keycloak-sso"; then
echo "✅ Keycloak started successfully"
echo "Admin console: http://localhost:9090/admin/"
echo "External URL: https://auth.yourdomain.dev/admin/"
else
echo "❌ Failed to start Keycloak"
docker-compose logs keycloak
exit 1
fi

This approach ensures passwords never appear in configuration files and provides secure credential retrieval through the 1Password CLI.

Initial Setup

Create OIDC Client

Access the admin console at http://localhost:9090/admin/ and login with admin and your 1Password-generated password.

  1. Navigate to ClientsCreate client
  2. Set Client type to "OpenID Connect"
  3. Enter Client ID (e.g., "test-app")
  4. Click Next, then Save

Configure Client Settings

In the client's Settings tab:

  • Valid redirect URIs: Add your application's callback URL
  • Web origins: Add your application's domain
  • Client authentication: Enable if you need a client secret

Get Client Credentials

In the Credentials tab, copy the Client Secret for your application configuration.

Create Test User

  1. Navigate to UsersAdd user
  2. Set Username and Email
  3. Click Create
  4. Go to Credentials tab
  5. Click Set password
  6. Enter password and set Temporary to "Off"

OIDC Endpoints

Your application will need these URLs:

  • Authorization: https://auth.yourdomain.dev/realms/master/protocol/openid-connect/auth
  • Token: https://auth.yourdomain.dev/realms/master/protocol/openid-connect/token
  • UserInfo: https://auth.yourdomain.dev/realms/master/protocol/openid-connect/userinfo

Reverse Proxy with Caddy

Configure Caddy to provide SSL termination and external access:

auth.yourdomain.com {
encode zstd gzip
    
tls {
issuer acme {
disable_http_challenge
}
}
    
reverse_proxy 127.0.0.1:9090
}

Caddy automatically handles:

  • Let's Encrypt certificate provisioning
  • HTTPS redirects
  • HTTP/2 support
  • Automatic certificate renewal

Client Configuration Gotchas

When integrating with applications, the redirect URI configuration is critical and often misunderstood:

Understanding OAuth2 Flow

Many applications use a three-party flow:

  1. Application redirects user to your Keycloak
  2. After authentication, Keycloak redirects to an intermediary (like AWS Cognito)
  3. Intermediary processes the response and redirects to final application

Redirect URI Configuration

The redirect URI in your Keycloak client must point to where the application expects the callback, not necessarily the application itself:

# Example: Application using AWS Cognito as intermediary
Valid Redirect URI: https://company-app.auth.us-east-2.amazoncognito.com/oauth2/idpresponse

# Not the application URL:
# Valid Redirect URI: https://app.company.com/callback

Domain Matching is Critical

SSO providers often route based on email domain. If your test email is user@company.com, configure the SSO domain as company.com, not your auth server hostname.