services: # Keycloak: OIDC Provider (Recommended) keycloak: image: quay.io/keycloak/keycloak:latest container_name: DEV_keycloak environment: # Admin credentials (change for production!) KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: admin # Database settings KC_DB: postgres KC_DB_URL_HOST: postgres KC_DB_USERNAME: keycloak KC_DB_PASSWORD: keycloak # Hostname configuration for development # Using full URL so issuer matches what fail2ban-ui expects # When using KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true, hostname must be a full URL # Set KEYCLOAK_PUBLIC_URL environment variable to your server's public URL # If not set, defaults to http://localhost:3000 KC_HOSTNAME: ${KEYCLOAK_PUBLIC_URL:-http://localhost:3000} KC_HOSTNAME_STRICT: "false" KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true" # Development mode (use "start" for production) KC_HEALTH_ENABLED: "true" KC_METRICS_ENABLED: "true" command: - start-dev - --http-host=0.0.0.0 - --http-port=8080 ports: - "0.0.0.0:3000:8080" - "0.0.0.0:9000:9000" # Management port for health checks depends_on: postgres: condition: service_healthy volumes: - ./keycloak-data:/opt/keycloak/data:z healthcheck: # Keycloak v26+ uses port 9000 for health endpoints (management port) test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/9000 && echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && cat <&3 | grep -q '200 OK' || exit 1"] interval: 10s timeout: 5s retries: 30 start_period: 90s restart: unless-stopped networks: - oidc-network # PostgreSQL: Database for Keycloak postgres: image: postgres:15-alpine container_name: DEV_keycloak-db environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: keycloak volumes: - ./keycloak-db:/var/lib/postgresql/data:z healthcheck: test: ["CMD-SHELL", "pg_isready -U keycloak"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped networks: - oidc-network # Pocket-ID: OIDC Provider (Alternative 1) - Not fully tested # Uncomment to use Pocket-ID instead of Keycloak # pocket-id: # image: ghcr.io/pocket-id/pocket-id:latest # container_name: DEV_pocket-id # environment: # - APP_URL=http://localhost:3005 # # Encryption key (choose one method): # # Method 1: Direct key (simple but less secure) # # Generate with: openssl rand -base64 32 # - ENCRYPTION_KEY=aF98qjUZuCJ_j_9Bh8hXx5U832ZpYQW # # Method 2: File-based key (recommended) # # Put the base64 key in a file and point to it here. # # ENCRYPTION_KEY_FILE=/path/to/encryption_key # - TRUST_PROXY=false # - MAXMIND_LICENSE_KEY= # - PUID=1000 # - PGID=1000 # - DB_PROVIDER=sqlite # - DB_CONNECTION_STRING=sqlite:///pocket-id/var/db.sqlite # - PORT=1411 # volumes: # - ./pocket-id-data:/pocket-id/var:z # ports: # - "3005:1411" # restart: unless-stopped # networks: # - oidc-network # Authentik: OIDC Provider (Alternative 2) - Not fully tested # Uncomment to use Authentik instead of Keycloak # Note: Authentik requires additional setup (migrations, initial admin user) # authentik-server: # image: ghcr.io/goauthentik/server:latest # container_name: DEV_authentik-server # environment: # - AUTHENTIK_SECRET_KEY=dev-secret-key-change-in-production-generate-with-openssl-rand-base64-60 # - AUTHENTIK_ERROR_REPORTING__ENABLED=false # - AUTHENTIK_DISABLE_UPDATE_CHECK=true # - AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true # - PG_PASS=authentik # - AUTHENTIK_REDIS__HOST=redis # - AUTHENTIK_POSTGRESQL__HOST=postgresql # - AUTHENTIK_POSTGRESQL__USER=authentik # - AUTHENTIK_POSTGRESQL__PASSWORD=authentik # - AUTHENTIK_POSTGRESQL__NAME=authentik # ports: # - "3007:9000" # - "3008:9443" # depends_on: # - authentik-postgresql # - authentik-redis # volumes: # - ./authentik-media:/media:z # restart: unless-stopped # networks: # - oidc-network # # authentik-worker: # image: ghcr.io/goauthentik/server:latest # container_name: DEV_authentik-worker # environment: # - AUTHENTIK_SECRET_KEY=dev-secret-key-change-in-production-generate-with-openssl-rand-base64-60 # - AUTHENTIK_ERROR_REPORTING__ENABLED=false # - AUTHENTIK_DISABLE_UPDATE_CHECK=true # - AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true # - PG_PASS=authentik # - AUTHENTIK_REDIS__HOST=redis # - AUTHENTIK_POSTGRESQL__HOST=postgresql # - AUTHENTIK_POSTGRESQL__USER=authentik # - AUTHENTIK_POSTGRESQL__PASSWORD=authentik # - AUTHENTIK_POSTGRESQL__NAME=authentik # command: ["authentik", "worker"] # depends_on: # - authentik-postgresql # - authentik-redis # volumes: # - ./authentik-media:/media:z # restart: unless-stopped # networks: # - oidc-network # # authentik-postgresql: # image: postgres:15-alpine # container_name: DEV_authentik-db # environment: # POSTGRES_PASSWORD: authentik # POSTGRES_USER: authentik # POSTGRES_DB: authentik # volumes: # - ./authentik-db:/var/lib/postgresql/data:z # restart: unless-stopped # networks: # - oidc-network # # authentik-redis: # image: redis:7-alpine # container_name: DEV_authentik-redis # restart: unless-stopped # networks: # - oidc-network # Keycloak Client Auto-Configuration # This container automatically creates the OIDC client in Keycloak # Set PUBLIC_FRONTEND_URL environment variable to your server's public URL (e.g., http://192.168.1.100:3080) # If not set, defaults to http://localhost:3080 keycloak-init: image: curlimages/curl:latest container_name: DEV_keycloak-init depends_on: keycloak: condition: service_healthy environment: - KEYCLOAK_URL=http://keycloak:8080 - KEYCLOAK_ADMIN=admin - KEYCLOAK_PASSWORD=admin - REALM=master - CLIENT_ID=fail2ban-ui # PUBLIC_FRONTEND_URL: Set this to your server's public URL (IP or hostname) # If not set, defaults to http://localhost:3080 - PUBLIC_FRONTEND_URL=${PUBLIC_FRONTEND_URL:-http://localhost:3080} - SECRET_FILE=/config/keycloak-client-secret volumes: - ./init-keycloak.sh:/init-keycloak.sh:ro - ./config:/config:z # Run as root to avoid permission issues user: "0:0" command: ["/bin/sh", "-c", "apk add --no-cache jq 2>/dev/null && /bin/sh /init-keycloak.sh 2>&1"] networks: - oidc-network restart: "no" # Only run once # Fail2ban-UI: Main application with OIDC authentication fail2ban-ui: image: localhost/fail2ban-ui:dev container_name: DEV_fail2ban-ui-oidc privileged: true # Use host network to access Keycloak on localhost:3000 # This allows issuer URL to match Keycloak's discovery document network_mode: host # ports: # - "3080:8080" # Not needed with host network environment: - PORT=3080 - BIND_ADDRESS=0.0.0.0 # OIDC Configuration for Keycloak (default) # PUBLIC_FRONTEND_URL: Server's public URL for redirects (if not set, defaults to http://localhost:3080) # KEYCLOAK_URL: Server's public URL for Keycloak (if not set, defaults to http://localhost:3000) # Create a .env file with these variables for remote server access - PUBLIC_FRONTEND_URL=${PUBLIC_FRONTEND_URL:-http://localhost:3080} - KEYCLOAK_URL=${KEYCLOAK_URL:-http://localhost:3000} # OIDC settings - OIDC_ENABLED=true - OIDC_PROVIDER=keycloak - OIDC_ISSUER_URL=${KEYCLOAK_URL}/realms/master - OIDC_CLIENT_ID=fail2ban-ui - OIDC_CLIENT_SECRET=auto-configured - OIDC_CLIENT_SECRET_FILE=/config/keycloak-client-secret - OIDC_REDIRECT_URL=${PUBLIC_FRONTEND_URL}/auth/callback - OIDC_SCOPES=openid,profile,email - OIDC_SESSION_MAX_AGE=7200 - OIDC_USERNAME_CLAIM=preferred_username - OIDC_SKIP_VERIFY=true # Optional: Skip login page and redirect directly to OIDC provider (default: false) # When set to true, users are immediately redirected to the OIDC provider without showing the login page #- OIDC_SKIP_LOGINPAGE=true # Optional: Logout URL #- OIDC_LOGOUT_URL=${KEYCLOAK_URL}/realms/master/protocol/openid-connect/logout # Alternative OIDC Configuration Examples (uncomment and adjust as needed): # For Pocket-ID: # - OIDC_PROVIDER=pocketid # - OIDC_ISSUER_URL=http://localhost:3005 # - OIDC_LOGOUT_URL=http://localhost:3005/logout # # For Authentik: # - OIDC_PROVIDER=authentik # - OIDC_ISSUER_URL=http://localhost:3007/application/o/fail2ban-ui/ volumes: # Required for fail2ban-ui: Stores SQLite database, application settings, and SSH keys - ./config:/config:z # Required for fail2ban-ui: Used for testing logpath before enabling jails - /var/log:/var/log:ro # Required for compose-local fail2ban instance: We mount the same Fail2Ban config as the linuxserver-fail2ban container (under /config/fail2ban to fail2ban-ui can modify configs) - ./fail2ban-config-local/fail2ban:/etc/fail2ban:z # Required for compose-local fail2ban instance: Mount the same run directory that contains fail2ban.sock for communication between fail2ban-ui and the linuxserver-fail2ban container - ./f2b-run-local:/var/run/fail2ban:z restart: unless-stopped # Wait for Keycloak to be healthy before starting # Note: keycloak-init runs as a one-time job, so we only wait for Keycloak # The application will retry connecting to Keycloak if needed # Note: With host network mode, depends_on still works but network isolation is bypassed depends_on: - keycloak # Fail2ban-Local: Local Fail2ban instance for testing local connector fail2ban-local: image: lscr.io/linuxserver/fail2ban:latest container_name: DEV_fail2ban-local cap_add: # Required for fail2ban container: Allows to manage network interfaces and iptables from the container - NET_ADMIN # Required for fail2ban container: Allows to create raw sockets (needed for fail2ban.sock) - NET_RAW # Required for fail2ban container: Allows to run as root (needed to manage network interfaces and raw sockets) - SYS_ADMIN #privileged: true network_mode: host # needed to add iptables rules to the host network environment: - TZ=Europe/Zurich - VERBOSITY=-vv volumes: # To make sure linuxserver-fail2ban configs are persistent across container restarts (also needed by fail2ban-ui to modify configs) - ./fail2ban-config-local:/config:z # Directory that contains fail2ban.sock for communication between fail2ban-ui and fail2ban container - ./f2b-run-local:/var/run/fail2ban:z # Log sources for fail2ban container - /var/log:/var/log:ro - /var/log/httpd:/remotelogs/apache2:ro restart: unless-stopped networks: oidc-network: driver: bridge