services: # PostgreSQL database for Keycloak postgres: image: postgres:15-alpine container_name: headscale-postgres environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: password volumes: - postgres-data:/var/lib/postgresql/data networks: headscale-net: ipv4_address: 10.99.0.5 # Keycloak OIDC provider keycloak: image: quay.io/keycloak/keycloak:23.0 container_name: headscale-keycloak environment: KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin KC_DB: postgres KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: password KC_HOSTNAME_STRICT: false KC_HOSTNAME_STRICT_HTTPS: false ports: - "8280:8080" # Keycloak admin console depends_on: - postgres networks: headscale-net: ipv4_address: 10.99.0.6 command: start-dev volumes: - ./keycloak-config:/opt/keycloak/data/import:ro # Build Headscale with our OIDC groups changes headscale: build: context: .. dockerfile: Dockerfile.debug container_name: headscale-server volumes: - ./headscale-config-oidc.yaml:/etc/headscale/config.yaml - ./acl.hujson:/etc/headscale/acl.hujson - headscale-data:/var/lib/headscale ports: - "8180:8080" # HTTP API - "9090:9090" # Metrics - "50443:50443" # gRPC command: serve restart: unless-stopped networks: headscale-net: ipv4_address: 10.99.0.10 depends_on: - keycloak healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 5s timeout: 3s retries: 5 start_period: 30s # Headplane with OIDC configuration headplane: build: context: ../../headplane dockerfile: Dockerfile container_name: headscale-headplane ports: - "3000:3000" # Headplane UI environment: - HEADPLANE_CONFIG_PATH=/app/config.yaml volumes: - ./headplane-config-oidc.yaml:/app/config.yaml:ro networks: headscale-net: ipv4_address: 10.99.0.15 depends_on: - headscale restart: unless-stopped # Test tailscale clients (unchanged) tailscale-client1: image: tailscale/tailscale:latest container_name: tailscale-client1 hostname: client1 cap_add: - NET_ADMIN - SYS_MODULE environment: - TS_STATE_DIR=/var/lib/tailscale - TS_EXTRA_ARGS=--login-server=http://headscale:8080 - TS_HOSTNAME=client1 - TS_AUTHKEY=${TS_AUTHKEY_CLIENT1:-} - TS_ACCEPT_ROUTES=true - TS_USERSPACE=false volumes: - tailscale-client1-state:/var/lib/tailscale - /dev/net/tun:/dev/net/tun - ./scripts/client-init.sh:/usr/local/bin/client-init.sh:ro networks: headscale-net: ipv4_address: 10.99.0.21 depends_on: - headscale restart: unless-stopped command: sh -c "tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock --tun=userspace-networking & sleep 5 && /usr/local/bin/client-init.sh client1 && wait" tailscale-client2: image: tailscale/tailscale:latest container_name: tailscale-client2 hostname: client2 cap_add: - NET_ADMIN - SYS_MODULE environment: - TS_STATE_DIR=/var/lib/tailscale - TS_EXTRA_ARGS=--login-server=http://headscale:8080 - TS_HOSTNAME=client2 - TS_AUTHKEY=${TS_AUTHKEY_CLIENT2:-} - TS_ACCEPT_ROUTES=true - TS_USERSPACE=false volumes: - tailscale-client2-state:/var/lib/tailscale - /dev/net/tun:/dev/net/tun - ./scripts/client-init.sh:/usr/local/bin/client-init.sh:ro networks: headscale-net: ipv4_address: 10.99.0.22 depends_on: - headscale restart: unless-stopped command: sh -c "tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock --tun=userspace-networking & sleep 5 && /usr/local/bin/client-init.sh client2 && wait" networks: headscale-net: driver: bridge ipam: config: - subnet: 10.99.0.0/24 gateway: 10.99.0.1 volumes: headscale-data: tailscale-client1-state: tailscale-client2-state: postgres-data: