services: headscale-client: image: tailscale/tailscale:latest hostname: headscale-traefik restart: unless-stopped environment: - TS_AUTHKEY={{ headscale_user_auth_key }} - TS_EXTRA_ARGS=--login-server=https://{{ headscale_host }} --accept-dns --stateful-filtering=false --advertise-routes={{ loadbalancer_ip }}/32 - TS_STATE_DIR=/var/lib/tailscale - TS_USERSPACE=false - TZ={{ timezone }} volumes: - {{ traefik_base }}/volumes/headscale:/var/lib/tailscale - /dev/net/tun:/dev/net/tun cap_add: - NET_ADMIN - SYS_ADMIN networks: - headnet deploy: mode: replicated replicas: 1 placement: constraints: [node.role == manager] traefik: image: traefik:v3 restart: unless-stopped depends_on: - headscale-client ports: - 80:80 - 443:443 - 53:53 - 53:53/udp - 3636:3636 environment: - TZ={{ timezone }} - CF_API_EMAIL={{ cloudflare_email }} - CF_DNS_API_TOKEN={{ cloudflare_dns_api_token }} volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - {{ traefik_base }}/stacks/traefik.yml:/traefik.yml - {{ traefik_base }}/volumes/certs:/certs networks: - proxy - headnet deploy: mode: global placement: constraints: [node.role == manager] labels: - traefik.enable=true - traefik.http.routers.dashboard.rule=Host(`{{ traefik_domain }}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`)) - traefik.http.routers.dashboard.service=api@internal - traefik.http.routers.dashboard.tls=true - traefik.http.routers.dashboard.tls.certresolver=letsencrypt - traefik.http.routers.ping.rule=Host(`{{ traefik_domain }}`) && PathPrefix(`/ping`) - traefik.http.routers.ping.service=ping@internal - traefik.http.routers.ping.tls=true - traefik.http.routers.ping.tls.certresolver=letsencrypt - traefik.http.services.dashboard.loadbalancer.server.port=8080 - traefik.http.services.ping.loadbalancer.server.port=8080 networks: proxy: name: proxy driver: overlay attachable: true headnet: