Caddy and Tailscale With Docker Compose

Caddy 2 is a powerful web server that can be used to serve static files, reverse proxy to other services, and more.

Tailscale is a VPN service that makes it easy to connect to your home network from anywhere.

In this post, I’ll show you how to set up Caddy and Tailscale, serve with an HTTPS certificate your ts.net domain with Docker compose.

problem

Caddy has the support to automatically get a certificate from tailscale ts.net. To achieve this, Caddy needs to communicate with the Tailscale socket.

Example of a simple docker-compose file:

services:
  tailscale-caddy:
    image: tailscale/tailscale:latest
    hostname: caddy # hostname is important and used for you DNS
    environment:
      - TS_AUTHKEY=tskey-XXXXXXXXXXXXXXX
      - TS_EXTRA_ARGS=--advertise-tags=tag:container
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=false
    volumes:
      - ${PWD}/tailscale/state:/var/lib/tailscale
      - ${PWD}/tailscale/config/ts/:/serve/
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin
      - sys_module
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    volumes:
      - $PWD/Caddyfile:/etc/caddy/Caddyfile
      - $PWD/site:/srv
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - tailscale-caddy
    network_mode: service:tailscale-caddy


volumes:
  caddy_data:
    external: true
  caddy_config:

If you try to access the server using your Tailscale domain ts.net, you will get an error like this:

caddy-1            | {"level":"warn","ts":1730215067.0461261,"logger":"http","msg":"could not get status; will try to get certificate anyway","error":"Get \"http://local-tailscaled.sock/localapi/v0/status\": dial unix /var/run/tailscale/tailscaled.sock: connect: no such file or directory"}

As you can see, Caddy can’t find the Tailscale socket.

Solution

The solution is to force Tailscale to share its socket. The socket is stored in /tmp/tailscaled.sock.

To accomplish this, we update the docker-compose file:

services:
  tailscale-caddy:
    image: tailscale/tailscale:latest
    hostname: caddy # hostname is important and used for you DNS
    environment:
      ...
    volumes:
      ...
      - ${PWD}/tailscale/tmp:/tmp # Share the tmp folder with the tailscale socket
    cap_add:
      - net_admin
      - sys_module
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    volumes:
      ...
      - $PWD/tailscale/tmp/tailscaled.sock:/var/run/tailscale/tailscaled.sock # mount the socket at the right place
    depends_on:
      - tailscale-caddy
    network_mode: service:tailscale-caddy


volumes:
  caddy_data:
    external: true
  caddy_config:

Once this is done, you can access your server using your Tailscale domain ts.net and get a valid certificate with HTTPS.