Deploy Keycloak on Docker: Centralized IAM with SSO, 2FA, and OpenLDAP Synchronization

The Identity Management Problem in Growing Infrastructure

Picture a team of 45 engineers managing credentials across 12 separate internal web apps. Every Jenkins dashboard, internal wiki, Grafana monitor, and staging cluster maintains its own siloed user database.

Onboarding a new engineer means manually generating accounts across five or six consoles, eating up an hour of DevOps time per hire. Offboarding hurts even more. When an engineer leaves on a Friday afternoon, revoking access across every standalone system risks leaving active session tokens or orphaned accounts wide open.

Predictably, team members hit password fatigue. They start reusing 10-character weak passwords across environments or dumping credentials into shared, unencrypted text files. Running without a unified Identity and Access Management (IAM) layer creates messy audit trails, zero single-click session terminations, and wildly inconsistent security standards across everyday tools.

Core IAM Concepts: Realms, Federation, and Single Sign-On

Before launching containers, let’s nail down four foundational concepts to save hours of troubleshooting later:

  • Keycloak Realm: An isolated security domain containing users, credentials, roles, and connected apps. The default master realm serves strictly as an admin control plane. Always create a separate realm (such as internal-corp) for your staff and day-to-day services.
  • OpenID Connect (OIDC) & SAML: Keycloak bridges modern identity protocols. Most web platforms connect to Keycloak via OIDC (built on OAuth 2.0 using signed RS256 JWT tokens), so apps never touch or store raw user passwords.
  • User Federation (LDAP): Instead of juggling duplicate databases, Keycloak binds directly to an external directory service like OpenLDAP or Active Directory. It reads user trees, validates credentials via LDAP bind requests, and caches profile metadata locally.
  • Multi-Factor Authentication (MFA / 2FA): Keycloak supports standard RFC 6238 Time-based One-Time Passwords (TOTP). Users scan a QR code into Google Authenticator or FreeOTP, eliminating the need for paid SMS gateways.

Hands-On Practice: Setting Up Keycloak, PostgreSQL, and OpenLDAP

Step 1: Docker Compose Infrastructure Blueprint

Keycloak runs on the Quarkus runtime and pairs cleanly with PostgreSQL for metadata storage. Alongside Keycloak, we will spin up OpenLDAP to store identity records and a lightweight phpLDAPadmin web client for schema management.

For generating database and admin passwords with at least 32 characters of high-entropy randomness, the client-side generator at toolcraft.app/en/tools/security/password-generator works well because it calculates entropy locally in browser memory without sending keys over the wire.

Save this configuration as docker-compose.yml:

version: '3.8'

networks:
  iam-network:
    driver: bridge

volumes:
  postgres_data:
  ldap_data:
  ldap_config:

services:
  postgres:
    image: postgres:15-alpine
    container_name: keycloak-db
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak_admin
      POSTGRES_PASSWORD: ReplaceWithAStrongSecretDbPass123!
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - iam-network
    restart: unless-stopped

  openldap:
    image: osixia/openldap:1.5.0
    container_name: openldap
    environment:
      LDAP_ORGANISATION: "Example Corp"
      LDAP_DOMAIN: "example.com"
      LDAP_ADMIN_PASSWORD: ReplaceWithLdapAdminPass123!
    volumes:
      - ldap_data:/var/lib/ldap
      - ldap_config:/etc/ldap/slapd.d
    networks:
      - iam-network
    restart: unless-stopped

  phpldapadmin:
    image: osixia/phpldapadmin:0.9.0
    container_name: phpldapadmin
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: openldap
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "8080:80"
    networks:
      - iam-network
    depends_on:
      - openldap
    restart: unless-stopped

  keycloak:
    image: quay.io/keycloak/keycloak:24.0.1
    container_name: keycloak-server
    command: start-dev
    environment:
      KC_DB: postgres
      KC_DB_URL_HOST: postgres
      KC_DB_URL_PORT: 5432
      KC_DB_URL_DATABASE: keycloak
      KC_DB_USERNAME: keycloak_admin
      KC_DB_PASSWORD: ReplaceWithAStrongSecretDbPass123!
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: ReplaceWithAdminConsolePass123!
    ports:
      - "8081:8080"
    networks:
      - iam-network
    depends_on:
      - postgres
    restart: unless-stopped

Boot the environment in the background:

docker compose up -d
docker compose logs -f keycloak

Watch the logs. In roughly 15 to 20 seconds, Keycloak will print Running the server in development mode. Started.... You can now reach Keycloak at http://localhost:8081 and phpLDAPadmin at http://localhost:8080.

Step 2: Seed Directory Users in OpenLDAP

Open http://localhost:8080 in your browser. Sign in using the admin DN cn=admin,dc=example,dc=com and your defined LDAP_ADMIN_PASSWORD. Create a structural Organizational Unit (OU) called users, then add a test account inside ou=users,dc=example,dc=com:

  • First name (givenName): Alex
  • Last name (sn): Mercer
  • Username (uid): amercer
  • Email (mail): [email protected]
  • Password (userPassword): TemporaryPass987!

Step 3: Connect Keycloak to OpenLDAP via User Federation

  1. Log in to the Keycloak admin console at http://localhost:8081 with your KEYCLOAK_ADMIN credentials.
  2. Click the realm selector at the top left, choose Create Realm, and name it internal-corp.
  3. In the left sidebar, navigate to User federation and select Add LDAP providers.
  4. Configure the connection parameters:
    • UI display name: Company-OpenLDAP
    • Vendor: Other
    • Connection URL: ldap://openldap:389
    • Users DN: ou=users,dc=example,dc=com
    • Authentication type: simple
    • Bind DN: cn=admin,dc=example,dc=com
    • Bind credential: ReplaceWithLdapAdminPass123!
    • Username LDAP attribute: uid
    • RDN LDAP attribute: uid
    • UUID LDAP attribute: entryUUID
    • User object classes: inetOrgPerson, organizationalPerson, person
    • Edit mode: READ_ONLY (keep this read-only if LDAP remains your single source of truth, or switch to WRITABLE to let users sync password updates back to the directory)
  5. Click Test connection, followed by Test authentication. Once both return green success badges, hit Save.
  6. Scroll down to the Sync settings pane and click Sync all users. Check the Users tab in Keycloak: amercer appears instantly with their directory email and profile fields intact.

Step 4: Enforce Two-Factor Authentication (TOTP) Policies

Once identities stream in from LDAP, rolling out mandatory 2FA across every connected tool takes under two minutes:

  1. Inside the internal-corp realm, click Authentication > Required actions.
  2. Find Configure OTP and enable Set as default action. This flags every account lacking an authenticator registration to enroll on their very next login.
  3. Navigate to Authentication > Policies > OTP Policy. We recommend setting the hashing algorithm to SHA-256 with standard 6-digit codes and a 30-second token window.

Test the flow right away. Head to http://localhost:8081/realms/internal-corp/account/ and sign in as amercer. Keycloak validates the password against OpenLDAP, immediately halts the session, and presents an enrollment QR code. The user cannot touch downstream tools until they verify their first six-digit code.

Practical Experience & Architecture Insights

After running containerized Keycloak deployments across high-traffic production environments, three operational rules stand out:

  • Keep start-dev strictly on local laptops: The start-dev flag turns off critical TLS validations and binds directly to plain HTTP. For staging and production, create a two-stage Dockerfile that builds assets using kc.sh build with your PostgreSQL drivers pre-cached. Then boot containers via kc.sh start --optimized behind a reverse proxy (like Traefik or NGINX) handling TLS termination on port 443.
  • Keep access tokens brief: A 5-minute access token lifespan hits the sweet spot. Don’t stretch access tokens to 8 hours just to avoid refresh handshakes; that turns leaked bearer tokens into an administrative nightmare. Pair short 5-minute access tokens with longer 12-hour refresh tokens, and turn on Revoke Refresh Token to rotate refresh credentials on each exchange.
  • Automate background sync jobs: Under a READ_ONLY LDAP setup, newly added team members won’t show up in Keycloak search lists until they log in or a manual sync triggers. Prevent this by turning on Periodic Full Sync (every 86,400 seconds, or 24 hours) and Periodic Changed Users Sync (every 300 seconds, or 5 minutes). This ensures newly added LDAP groups and team changes reflect in Keycloak role mappings with zero manual overhead.

Combining Keycloak and OpenLDAP in Docker gives you a resilient, battle-tested IAM architecture. Applications delegate auth via OIDC, SysAdmins maintain a clean centralized directory, and security baselines like mandatory TOTP apply across every internal dashboard you run.

Share: