Docker Compose setup for running private web services behind Caddy with HTTPS on a Tailscale .ts.net name.
This is meant for a small self-hosted machine where services should be reachable over your tailnet without public DNS, public port forwarding, or manual certificate handling.
tailscale: joins the host to your tailnet and exposestailscaled.sock.caddy: reverse proxy with automatic HTTPS for the Tailscale domain.web1,web2,web3: sample backend services using Apachehttpd.proxy-network: private Docker network used by Caddy to reach the backend services.
Client on tailnet
-> https://your-machine.your-tailnet.ts.net/web1
-> Caddy
-> web1:80 on proxy-network
See architecture.md for the Mermaid diagram.
- Docker and Docker Compose
- Tailscale account
- Tailscale auth key from the admin console
Use an ephemeral auth key if the machine is disposable. Use a reusable key if you want the container to rejoin without manual work after rebuilds.
-
Clone the repository.
git clone <repository_url> cd https-caddy-tailscale
-
Create
.envin the project root.cp .env.example .env
Then set
TS_AUTHKEYin.env. -
Update the Tailscale domain in caddy/Caddyfile.
your-machine-name.your-tailnet.ts.net { import network_paths }
You can find the full machine name in the Tailscale admin console under Machines.
-
Update or remove the LAN-only HTTP block if it does not match your network.
http://192.168.0.31 { import network_paths }
-
Start the stack.
docker-compose up -d
-
Test from a device connected to the same tailnet.
https://your-machine-name.your-tailnet.ts.net/web1 https://your-machine-name.your-tailnet.ts.net/web2 https://your-machine-name.your-tailnet.ts.net/web3
caddy/Caddyfile defines one reusable route group:
(network_paths) {
handle_path /web1/* {
reverse_proxy web1:80
}
}Add more services by adding another handle_path block and putting the backend service on proxy-network.
Caddy uses /var/run/tailscale/tailscaled.sock from the Tailscale container. This lets it request certificates for the .ts.net domain.
The Tailscale container uses:
network_mode: hostNET_ADMINandNET_RAW/dev/net/tun- persistent state in
./tailscale/varlib
The auth key is read from .env. Do not commit .env.
Runtime state is intentionally ignored by Git:
caddy/datacaddy/configtailscale/tmptailscale/varlib
View logs:
docker-compose logs caddy
docker-compose logs tailscaleRestart:
docker-compose down
docker-compose up -dReload after Caddyfile changes:
docker-compose restart caddy- Check that
TS_AUTHKEYis set in.env. - Confirm the key is valid and not expired.
- Check logs with
docker-compose logs tailscale.
- Confirm the Caddy site address is the exact
.ts.netname of this machine. - Confirm Caddy can read
./tailscale/tmp/tailscaled.sock. - Check logs with
docker-compose logs caddy.
- Confirm the backend service is running.
- Confirm it is attached to
proxy-network. - Confirm the service name in
reverse_proxymatches the Compose service name.
- Services are exposed through Tailscale, not directly to the public internet.
- Keep backend services off host ports unless required.
- Treat the Tailscale auth key as a secret.
- Review ACLs in Tailscale if this is used by more than one person.