# headscale stack -- caddy (TLS) + headscale (Tailscale-compatible coordinator) # # Topology: # Tailscale clients -> caddy:443 -> headscale:8080 # # No Anubis here: Tailscale clients speak HTTP/2 control protocol, not HTML, # so a PoW challenge would break them. Headscale's API surface is what # clients hit; treat it like any other API service. # # OIDC login is delegated to pocket-id at $POCKETID_DOMAIN. Register a # client there first; copy the client_id + client_secret into .env. name: headscale volumes: headscale-data: headplane-data: caddy-data: caddy-config: services: caddy: image: caddy:${CADDY_TAG:-2-alpine} container_name: caddy restart: unless-stopped ports: - "80:80" - "443:443" # - "443:443/udp" # HTTP/3 -- disabled until Caddy QUIC + reverse_proxy # placeholder bug is fixed (X-Real-IP comes through # empty on some streams). Re-enable later. volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config environment: HEADSCALE_DOMAIN: "${HEADSCALE_DOMAIN}" ACME_EMAIL: "${ACME_EMAIL}" depends_on: headscale: condition: service_healthy healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:2019/config/"] interval: 30s timeout: 5s retries: 3 start_period: 10s headscale: image: headscale/headscale:${HEADSCALE_TAG:-0.28.0} container_name: headscale restart: unless-stopped command: serve read_only: true tmpfs: - /var/run/headscale volumes: - ./config.yaml:/etc/headscale/config.yaml:ro - ./policy.hujson:/etc/headscale/policy.hujson:ro - headscale-data:/var/lib/headscale healthcheck: test: ["CMD", "headscale", "health"] interval: 30s timeout: 5s retries: 3 start_period: 10s # --------------------------------------------------------------------------- # Headplane — web UI for headscale, served by Caddy at /admin. API-only: # talks to the headscale API with a key (no Docker socket, no host control). # config.yaml is generated by deploy.sh from .env. # --------------------------------------------------------------------------- headplane: image: ghcr.io/tale/headplane:${HEADPLANE_TAG:-latest} container_name: headplane restart: unless-stopped volumes: - ./headplane.yaml:/etc/headplane/config.yaml:ro - headplane-data:/var/lib/headplane depends_on: headscale: condition: service_healthy