I’ve done something similar but I’m not sure how helpful my example would be because I use wireguard instead of tailscale and traefik instead of caddy.
The principle is the same though, iirc I have my traefik container set to network_mode: “service:wireguard” so that the traefik container uses the wireguard container’s network stack. That way the traefik container also sees the wireguard interface and can receive traffic going to the wireguard IP. Then at the other end of the wireguard tunnel I can use haproxy to pass traffic to the wireguard IP through the tunnel and it automatically hits traefik.
Partially yes, the tricky thing is that when using
network_mode: "service:tailscale"
(presumably on the caddy container since that’s what needs to receive traffic from the tailscale network), you won’t be able to attach the caddy container to any networks since it’s using the tailscale network stack. This means that in order for caddy to reach your containers, you will need to add the tailscale container itself to the relevant networks. Any attached containers will be connected as well.(Not sure if I misread the first time or if you edited but the way you say it is right, add the tailscale container to the proxy network so that caddy will also be added and can reach the containers)
Here’s the super condensed version of what matters for connecting traefik/caddy to a VPN like wireguard/tailscale.
My traefik compose:
services: wireguard: container_name: wireguard networks: - ingress traefik: network_mode: "service:wireguard" depends_on: - wireguard command: - "--entryPoints.web.proxyProtocol.trustedIPs=10.13.13.1" # Trust remote tunnel IP, the WG container is 10.13.13.2 - "--entrypoints.websecure.address=:443" - "--entryPoints.websecure.proxyProtocol.trustedIPs=10.13.13.1" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--entrypoints.web.http.redirections.entrypoint.priority=100" - "--providers.docker.exposedByDefault=false" - "--providers.docker.network=ingress" networks: ingress: external: true
And then in a service’s docker-compose:
services: ui: image: myapp read_only: true restart: always labels: - "traefik.enable=true" - "traefik.http.routers.myapp.rule=Host(`xxxx.xxxx.xxxx`)" - "traefik.http.services.myapp.loadbalancer.server.port=80" - "traefik.http.routers.myapp.entrypoints=websecure" - "traefik.http.routers.myapp.tls.certresolver=mytlschallenge" networks: - ingress networks: ingress: external: true
(edited to fix formatting on mobile)