Setting Up Keycloak With Traefik and Postgres

Configuring and deploying Keycloak with a docker-compose example.

This docker-compose.yml example creates a setup with Traefik for reverse proxying, PostgreSQL for data storage, and Keycloak for authentication. Here’s a breakdown of the essential parts and configurations.

Example docker-compose.yml

services:
  traefik:
    image: traefik:v2.11
    restart: unless-stopped
    command:
      - "--certificatesresolvers.{your-resolver-name}.acme.email={your-email}"
      - "--certificatesresolvers.{your-resolver-name}.acme.storage=/etc/traefik/acme.json"
      - "--certificatesresolvers.{your-resolver-name}.acme.tlschallenge=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entryPoint.permanent=true"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.websecure.address=:443"
      - "--providers.docker=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "{your-traefik-directory}:/etc/traefik"

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${KEYCLOAK_DB}
      POSTGRES_USER: ${KEYCLOAK_DB_USERNAME}
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    secrets:
      - postgres_password
    healthcheck:
      test: ["CMD-SHELL", "sh -c 'pg_isready -U ${KEYCLOAK_DB_USERNAME} -d ${KEYCLOAK_DB}'"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M

  keycloak:
    image: keycloak/keycloak:26.0
    restart: unless-stopped
    command: start --optimized
    environment:
      KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD}
      KC_BOOTSTRAP_ADMIN_USERNAME: ${KC_BOOTSTRAP_ADMIN_USERNAME}
      KC_DB_PASSWORD: ${POSTGRES_KC_PASSWORD}
      KC_DB_URL: ${KEYCLOAK_DB_URL}
      KC_DB_USERNAME: ${KEYCLOAK_DB_USERNAME}
      KC_DB: postgres
      KC_HOSTNAME_STRICT: "true"
      KC_HOSTNAME: "auth.example.com"
      KC_HTTP_ENABLED: "true"
      KC_PROXY_HEADERS: "xforwarded"
    labels:
      - "traefik.http.routers.keycloak.rule=Host(\"auth.example.com\")"
      - "traefik.http.routers.keycloak.tls=true"
      - "traefik.http.routers.keycloak.tls.certresolver={your-resolver-name}"
      - "traefik.http.routers.keycloak.tls.domains[0].main=auth.example.com"
      - "traefik.http.services.keycloak.loadbalancer.server.port=8080"
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
    depends_on:
      postgres:
        condition: service_healthy

secrets:
  postgres_password:
    file: ./secrets/postgres_password

volumes:
  postgres_data:

Breaking Down the Configuration

Keycloak and Postgres Version

I used the latest version of Keycloak along with the corresponding compatible version of PostgreSQL. Ensuring compatibility is essential for stability, as not all PostgreSQL versions are supported by Keycloak. For the latest supported versions, refer to Keycloak’s Supported Databases page.

Using Docker Secrets

Docker secrets offer a secure way to manage these details, replacing environment variables with references to secret files.

In the configuration:

  • POSTGRES_PASSWORD_FILE is used to set the password for PostgreSQL by pointing to a secure Docker secret instead of an environment variable.
  • To set up Docker secrets, create a postgres_password file in a secure location (./secrets/postgres_password), which Docker Compose will reference directly.

Using secrets this way ensures passwords are isolated and accessible only to the specific services that require them.

Setting Resource Limits

Docker’s default resource allocation can sometimes be insufficient, particularly for resource-intensive applications like databases. PostgreSQL, in this setup, is limited by both CPU and memory usage to prevent resource exhaustion on shared systems.

Example:

resources:
  limits:
    cpus: '0.5'
    memory: 512M

Optimized Keycloak Command for Production

In production, it’s essential to use Keycloak’s start command with the --optimized flag, which is only used after the initial build. For the initial build of Keycloak container, use start.

Keycloak Environment Variables Explained

  • KC_BOOTSTRAP_ADMIN_PASSWORD and KC_BOOTSTRAP_ADMIN_USERNAME: These variables initialize the Keycloak admin user. They’re critical for the first login and should be set to secure values. For added security, consider storing these in Docker secrets.

  • KC_HTTP_ENABLED: Since Traefik handles HTTPS connections, Keycloak can communicate over HTTP within the internal Docker network. Setting KC_HTTP_ENABLED to true allows HTTP traffic internally, streamlining communication and performance between services.

  • KC_PROXY_HEADERS: This setting, introduced in newer Keycloak versions, replaces the previous KC_PROXY configuration (KC_PROXY=edge). It configures Keycloak to respect proxy headers (like X-Forwarded-For), which is essential when using a reverse proxy like Traefik to forward requests to Keycloak securely.


This setup serves as a foundational example, with room for further improvements—such as completely eliminating environment variables for database passwords to enhance security.