# FreeIPA + Keycloak + PostgreSQL constellation # # Setup: # cp .env.example .env && $EDITOR .env # docker compose up -d # docker compose logs -f freeipa # watch first-boot install (~10 min) # # Once freeipa is healthy: # ./keycloak-configure.sh # wire Keycloak → FreeIPA LDAP # # To run without Keycloak: # docker compose up -d freeipa # # To scale out (clients enroll against the same IPA server): # docker compose --profile clients up # # ── cgroupns NOTE ──────────────────────────────────────────────────────────── # On hosts using cgroup v2 (WSL2, recent Linux with unified hierarchy) the # FreeIPA container runs /sbin/init (systemd) and exits immediately with code # 255 unless the container shares the host cgroup namespace. Docker Compose # does not yet expose a cgroupns_mode property in its schema, so use the # provided run.sh wrapper instead of plain `docker compose up` on those hosts: # # ./run.sh — starts freeipa only (cgroupns host applied automatically) # ./run.sh all — starts freeipa + postgres + keycloak # # On plain cgroup v1 hosts `docker compose up -d` works without run.sh. volumes: freeipa-data: keycloak-db: cmk-data: # CheckMK site data (persistent across restarts) cmk-creds: # shared: CheckMK writes automation.secret here; FreeIPA reads it networks: ipa-net: ipam: config: - subnet: 172.30.0.0/24 services: # ── FreeIPA ───────────────────────────────────────────────────────────────── freeipa: build: context: . dockerfile: Dockerfile image: freeipa-server:local container_name: freeipa hostname: ${IPA_HOSTNAME:-ipa.example.com} privileged: true tmpfs: - /run - /tmp volumes: - freeipa-data:/data - /sys/fs/cgroup:/sys/fs/cgroup:rw - cmk-creds:/cmk-creds # read CMK secret + write push-mode requests for hosts environment: IPA_DOMAIN: ${IPA_DOMAIN:?set IPA_DOMAIN in .env} IPA_REALM: ${IPA_REALM:-} IPA_ADMIN_PASSWORD: ${IPA_ADMIN_PASSWORD:?set IPA_ADMIN_PASSWORD in .env} IPA_DM_PASSWORD: ${IPA_DM_PASSWORD:?set IPA_DM_PASSWORD in .env} IPA_SETUP_DNS: ${IPA_SETUP_DNS:-false} IPA_DNS_FORWARDER: ${IPA_DNS_FORWARDER:-} IPA_SETUP_KRA: ${IPA_SETUP_KRA:-false} SMB_SCAN_PASSWORD: ${SMB_SCAN_PASSWORD:?set SMB_SCAN_PASSWORD in .env} CMK_URL: http://172.30.0.12:5000 CMK_SITE_ID: ${CMK_SITE_ID:-cmk} ports: - "389:389" - "636:636" - "88:88" - "88:88/udp" - "464:464" - "464:464/udp" - "443:443" - "445:445" - "139:139" - "137:137/udp" - "138:138/udp" networks: ipa-net: ipv4_address: 172.30.0.10 healthcheck: test: ["CMD-SHELL", "ipactl status 2>/dev/null | grep -q 'running'"] interval: 30s timeout: 15s retries: 20 start_period: 600s # ── PostgreSQL (Keycloak backend) ──────────────────────────────────────────── postgres: image: postgres:16-alpine container_name: keycloak-db restart: unless-stopped environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: ${KC_DB_PASSWORD:?set KC_DB_PASSWORD in .env} volumes: - keycloak-db:/var/lib/postgresql/data networks: ipa-net: healthcheck: test: ["CMD", "pg_isready", "-U", "keycloak"] interval: 10s retries: 5 # ── CheckMK CE ─────────────────────────────────────────────────────────────── # Monitoring server. Clients register themselves via ansipa-enforce-policies.sh # when added to dev_mon_base. The automation user's secret is written to the # cmk-creds shared volume so ansipa-checkmk-setup.sh (in the freeipa container) # can store it in the IPA dev_mon_base hostgroup description for client discovery. # # Web UI: http://localhost:8090/cmk/ (user: cmkadmin, pass: CMK_ADMIN_PASSWORD) # Agent dl: http://localhost:8090/cmk/check_mk/agents/ # Note: CheckMK 2.3 uses port 5000 (OMD apache); host port 8090 maps to it. checkmk: image: checkmk/check-mk-raw:2.3.0-latest container_name: checkmk hostname: checkmk.test.local restart: unless-stopped # Override entrypoint to copy the auto-generated automation secret to the # shared volume so the freeipa container can read it without docker exec. # $$ → $ after Compose expansion (i.e., $$! → $! in bash = background PID). command: - bash - -c - >- /docker-entrypoint.sh & CMK_PID=$$!; until [ -f /omd/sites/${CMK_SITE_ID:-cmk}/var/check_mk/web/automation/automation.secret ]; do sleep 3; done; sleep 5; mkdir -p /cmk-creds; cp /omd/sites/${CMK_SITE_ID:-cmk}/var/check_mk/web/automation/automation.secret /cmk-creds/automation.secret; chmod 644 /cmk-creds/automation.secret; printf '\n Order allow,deny\n Allow from all\n Satisfy any\n\n' > /omd/sites/${CMK_SITE_ID:-cmk}/etc/apache/conf.d/cookie_auth.conf; omd reload ${CMK_SITE_ID:-cmk} apache 2>/dev/null || true; while true; do for _req in /cmk-creds/push-mode-*.req; do [ -f "$$_req" ] || continue; _hn=$$(cat "$$_req"); _hk=$$(echo "$$_hn" | tr . _); _mk=/omd/sites/${CMK_SITE_ID:-cmk}/etc/check_mk/conf.d/wato/ansipa/hosts.mk; [ -f "$$_mk" ] || continue; grep -q "explicit_host_conf.*$$_hk\|'$$_hn'.*push-agent" "$$_mk" 2>/dev/null && { rm -f "$$_req"; continue; }; printf '\nexplicit_host_conf.setdefault('"'"'cmk_agent_connection'"'"', {})\nexplicit_host_conf['"'"'cmk_agent_connection'"'"'].update({'"'"'%s'"'"': '"'"'push-agent'"'"'})\n' "$$_hn" >> "$$_mk"; su - ${CMK_SITE_ID:-cmk} -c 'cmk -O' 2>/dev/null || true; rm -f "$$_req"; done; sleep 30; done & wait $$CMK_PID environment: CMK_SITE_ID: ${CMK_SITE_ID:-cmk} CMK_PASSWORD: ${CMK_ADMIN_PASSWORD:?set CMK_ADMIN_PASSWORD in .env} ports: - "8090:5000" # web UI (CheckMK 2.3 uses port 5000 for OMD apache) - "8000:8000" # agent receiver (push mode / cmk-agent-ctl) - "6557:6557" # livestatus (optional, for external tools) volumes: - cmk-data:/omd/sites - cmk-creds:/cmk-creds networks: ipa-net: ipv4_address: 172.30.0.12 healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:5000/${CMK_SITE_ID:-cmk}/check_mk/api/1.0/version >/dev/null 2>&1"] interval: 30s timeout: 15s retries: 20 start_period: 180s # ── Keycloak ───────────────────────────────────────────────────────────────── # After first start, run ./keycloak-configure.sh to wire FreeIPA LDAP federation. keycloak: image: quay.io/keycloak/keycloak:latest container_name: keycloak restart: unless-stopped # Use 'start' (not start-dev) for production; requires a TLS certificate. command: start-dev environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://postgres/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: ${KC_DB_PASSWORD:?set KC_DB_PASSWORD in .env} KC_HOSTNAME: ${KC_HOSTNAME:-localhost} KC_HTTP_PORT: 8080 KC_HTTPS_PORT: 8443 KC_HTTP_ENABLED: "true" KC_FEATURES: preview KEYCLOAK_ADMIN: ${KC_ADMIN:-admin} KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD:?set KC_ADMIN_PASSWORD in .env} extra_hosts: - "${IPA_HOSTNAME:-ipa.example.com}:172.30.0.10" ports: - "8080:8080" - "8443:8443" depends_on: postgres: condition: service_healthy networks: ipa-net: healthcheck: test: ["CMD-SHELL", "curl -fs http://localhost:8080/health/ready || exit 1"] interval: 20s timeout: 10s retries: 20 start_period: 90s