services: headscale-client: image: tailscale/tailscale:latest hostname: headscale-client-{{ deployment_time }} environment: - DEPLOYMENT_TIME={{ deployment_time }} - TZ={{ timezone }} - 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 healthcheck: test: ["CMD-SHELL", "tailscale status"] interval: 1s timeout: 5s retries: 10 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 update_config: parallelism: 1 order: start-first failure_action: rollback monitor: 2s traefik: image: traefik:v3 depends_on: - headscale-client ports: - target: 80 published: 80 mode: host - target: 443 published: 443 mode: host # to get x-forwarded-for correctly, see https://github.com/moby/moby/issues/25526 healthcheck: test: traefik healthcheck --ping interval: 10s retries: 2 timeout: 3s environment: - DEPLOYMENT_TIME={{ deployment_time }} - 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: - metrics - proxy - oauth_proxy - headnet deploy: mode: global update_config: parallelism: 1 order: stop-first # only one service eating 80/443 per host, since failure_action: rollback monitor: 2s # go go go. 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.services.dashboard.loadbalancer.server.port=8080" - "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.ping.loadbalancer.server.port=8080" - "traefik.http.middlewares.auth-headers.headers.stsSeconds=315360000" - "traefik.http.middlewares.auth-headers.headers.browserXssFilter=true" - "traefik.http.middlewares.auth-headers.headers.contentTypeNosniff=true" - "traefik.http.middlewares.auth-headers.headers.forceSTSHeader=true" - "traefik.http.middlewares.auth-headers.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.auth-headers.headers.stsPreload=true" - "traefik.http.middlewares.auth-headers.headers.frameDeny=true" - "traefik.http.middlewares.oauth-verify.forwardAuth.address=http://oauth2-proxy:4180" - "traefik.http.middlewares.oauth-verify.forwardAuth.trustForwardHeader=true" - "traefik.http.middlewares.oauth-verify.forwardAuth.authResponseHeaders={{ forwardauth_headers }}" - "traefik.http.middlewares.oauth-verify-noredirect.forwardAuth.address=http://oauth2-proxy:4180/oauth2/auth" - "traefik.http.middlewares.oauth-verify-noredirect.forwardAuth.trustForwardHeader=true" - "traefik.http.middlewares.oauth-verify-noredirect.forwardAuth.authResponseHeaders={{ forwardauth_headers }}" oauth2-proxy: image: quay.io/oauth2-proxy/oauth2-proxy:latest command: --alpha-config /conf/oauth_proxy_alpha.yml --config /conf/oauth_proxy.cfg volumes: - "{{ traefik_base }}/volumes/oauth2proxy:/conf" environment: - TZ={{ timezone }} - DEPLOYMENT_TIME={{ deployment_time }} networks: - oauth_cache - proxy - oauth_proxy - metrics deploy: mode: replicated update_config: parallelism: 1 failure_action: rollback order: start-first delay: 5s replicas: 1 labels: "traefik.enable": "true" "traefik.swarm.network": "proxy" "traefik.http.routers.fwdauth.tls": "true" "traefik.http.routers.fwdauth.tls.certResolver": "letsencrypt" "traefik.http.routers.fwdauth.rule": "!Host(`{{ idm_domain }}`) && (PathPrefix(`/oauth2`) || Host(`{{ oauth_proxy_domain }}`))" "traefik.http.routers.fwdauth.entrypoints": "websecure" "traefik.http.routers.fwdauth.middlewares": "auth-headers" "traefik.http.services.fwdauth.loadbalancer.server.port": "4180" oauth2-cache: image: redis:8-alpine restart: always command: redis-server --save 20 1 --loglevel warning volumes: - "{{ traefik_base }}/volumes/redis:/data" environment: - TZ={{ timezone }} - DEPLOYMENT_TIME={{ deployment_time }} networks: - oauth_cache deploy: mode: replicated update_config: parallelism: 1 failure_action: rollback order: start-first delay: 5s replicas: 1 networks: metrics: external: true proxy: name: proxy driver: overlay attachable: true oauth_proxy: oauth_cache: headnet: