Expose your GoClaw gateway securely on your Tailscale network — no port forwarding, no public IP required.
GoClaw can join your Tailscale network as a named node, making the gateway reachable from any of your devices without opening firewall ports. This is ideal for self-hosted setups where you want private remote access from your laptop, phone, or CI runners.
The Tailscale listener runs alongside the regular HTTP listener on the same handler — you get both local and Tailscale access simultaneously.
This feature is opt-in and compiled in only when you build with -tags tsnet. The default binary has zero Tailscale dependencies.
graph LR
A[Your laptop] -->|Tailscale network| B[goclaw-gateway node]
C[Your phone] -->|Tailscale network| B
B --> D[Gateway handler]
E[Local network] -->|Port 18790| D
When GOCLAW_TSNET_HOSTNAME is set, GoClaw starts a tsnet.Server that connects to Tailscale and listens on port 80 (or 443 with TLS). The Tailscale node appears in your Tailscale admin console as a regular device.
go build -tags tsnet -o goclaw .Or with Docker Compose using the provided overlay:
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
-f docker-compose.tailscale.yml \
upThe overlay passes ENABLE_TSNET: "true" as a build arg, which compiles the binary with -tags tsnet.
# From https://login.tailscale.com/admin/settings/keys
# Use a reusable auth key for long-lived deployments
export GOCLAW_TSNET_AUTH_KEY=tskey-auth-xxxxxxxxxxxxxxxx# Tailscale device name (default: goclaw-gateway)
export GOCLAW_TSNET_HOSTNAME=my-goclaw
# Directory for Tailscale state (persisted across restarts)
# Default: OS user config dir
export GOCLAW_TSNET_DIR=/app/tsnet-stateOr via config.json (auth key is never stored in config — env only):
{
"tailscale": {
"hostname": "my-goclaw",
"state_dir": "/app/tsnet-state",
"ephemeral": false,
"enable_tls": false
}
}| Field | Default | Description |
|---|---|---|
hostname |
goclaw-gateway |
Tailscale device name |
state_dir |
OS user config dir | Persists Tailscale identity across restarts |
ephemeral |
false |
If true, node is automatically removed from your tailnet when GoClaw stops — useful for CI/CD or short-lived containers |
enable_tls |
false |
Use Tailscale-managed HTTPS certs via Let's Encrypt (listens on :443 instead of :80) |
The docker-compose.tailscale.yml overlay mounts a named volume for Tailscale state so the node identity survives container restarts:
# docker-compose.tailscale.yml (full file)
services:
goclaw:
build:
args:
ENABLE_TSNET: "true"
environment:
- GOCLAW_TSNET_HOSTNAME=${GOCLAW_TSNET_HOSTNAME:-goclaw-gateway}
- GOCLAW_TSNET_AUTH_KEY=${GOCLAW_TSNET_AUTH_KEY}
volumes:
- tsnet-state:/app/tsnet-state
volumes:
tsnet-state:Set your auth key in .env:
GOCLAW_TSNET_AUTH_KEY=tskey-auth-xxxxxxxxxxxxxxxx
GOCLAW_TSNET_HOSTNAME=my-goclawThen bring it up:
docker compose -f docker-compose.yml -f docker-compose.postgres.yml -f docker-compose.tailscale.yml up -dOnce running, your gateway is reachable at:
http://my-goclaw.your-tailnet.ts.net # HTTP (default)
https://my-goclaw.your-tailnet.ts.net # HTTPS (if enable_tls: true)
You can find the full hostname in your Tailscale admin console.
| Issue | Likely cause | Fix |
|---|---|---|
| Node not appearing in Tailscale console | Invalid or expired auth key | Generate a new reusable key at admin/settings/keys |
| Tailscale listener not starting | Binary built without -tags tsnet |
Rebuild with go build -tags tsnet |
GOCLAW_TSNET_HOSTNAME ignored |
Tag missing from build | Check ENABLE_TSNET: "true" in docker build args |
| State lost on container restart | Missing volume mount | Ensure tsnet-state volume is mounted to state_dir |
| Connection refused from Tailscale | enable_tls mismatch |
Check whether you're using HTTP or HTTPS |
- Production Checklist — secure your deployment end to end
- Security Hardening — CORS, rate limits, and token auth
- Docker Compose Setup — full compose overlay reference