The easiest way to expose Docker containers on your Tailscale network. One label. Zero sidecars.
Get running in under a minute. One compose file, one label.
Step 1: Create docker-compose.yml
services:
tsdproxy:
image: almeidapaulopt/tsdproxy:2
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- tsdproxy-data:/data
- ./config:/config
ports:
- "8080:8080"
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
myapp:
image: nginx:alpine
labels:
tsdproxy.enable: "true"
tsdproxy.name: "myapp"
volumes:
tsdproxy-data:Step 2: Start it up
docker compose up -dTSDProxy creates a default config at /config/tsdproxy.yaml on first run. Open the dashboard at http://localhost:8080, click the proxy card, and authenticate with Tailscale.
Your container is now available at https://myapp.<tailnet-name>.ts.net with automatic HTTPS.
For automated (headless) setup, configure an AuthKey or OAuth before adding services.
| Feature | Description |
|---|---|
| Zero sidecars | No Tailscale container needed per service. One proxy handles everything. |
| Label-based config | Add tsdproxy.enable=true to any container. Done. |
| Automatic HTTPS | Tailscale provisions Let's Encrypt certs for every machine. |
| Multi-port support | Expose multiple ports per container with granular protocol control. |
| TCP proxying | Proxy TCP traffic (SSH, databases) alongside HTTP/HTTPS services. |
| Funnel support | Expose services to the public internet with tailscale_funnel option. |
| Dynamic lifecycle | Containers start and stop. Tailscale machines appear and disappear. |
| Live config reload | Change settings without restarting TSDProxy. |
| Dashboard | Real-time web UI with SSE streaming to monitor all your proxies. |
| List provider | Expose non-Docker services via a simple YAML file. |
graph LR
A[Docker Containers] -->|tsdproxy.enable label| B[TSDProxy]
B -->|creates tsnet.Server| C[Tailscale Network]
C -->|automatic HTTPS| D[Secure URLs]
D -->|reverse proxy| A
Under the hood:
- Container Scanning - TSDProxy watches your Docker daemon for containers tagged with
tsdproxy.enable=true. - Machine Creation - When a tagged container appears, TSDProxy spins up a Tailscale machine via
tsnet. - Hostname Assignment - The machine gets a hostname from the
tsdproxy.namelabel or the container name. - Port Mapping - TSDProxy maps the container's internal port to the Tailscale machine.
- Traffic Routing - Incoming requests to
https://myapp.<tailnet>.ts.netare reverse-proxied to the container. - Dynamic Cleanup - When a container stops, its Tailscale machine and routes are removed automatically.
Expose multiple ports with per-port protocol and options:
labels:
tsdproxy.enable: "true"
tsdproxy.name: "myservice"
# HTTPS on 443 -> container port 80
tsdproxy.port.1: "443/https:80/http"
# HTTP on 80 -> container port 8080
tsdproxy.port.2: "80/http:8080/http"
# HTTP redirect to HTTPS
tsdproxy.port.3: "81/http->https://myservice.tailnet.ts.net"
# TCP proxy for SSH
tsdproxy.port.4: "22/tcp:22/tcp"| Tag | Description |
|---|---|
almeidapaulopt/tsdproxy:2 |
Latest v2 release |
almeidapaulopt/tsdproxy:latest |
Latest stable release |
almeidapaulopt/tsdproxy:dev |
Latest development build |
almeidapaulopt/tsdproxy:vx.x.x |
Specific version |
Full setup guides, configuration reference, and advanced usage:
almeidapaulopt.github.io/tsdproxy
Key docs: Getting Started | Docker Labels | Port Configuration | List Provider | TCP Proxy | Funnel | Upgrading from v1
Bug reports, feature requests, documentation improvements, and pull requests are all welcome. See CONTRIBUTING.md for guidelines.
If you'd rather support the project financially, sponsorships help keep development going.
This project is licensed under the MIT License. See the LICENSE file for details.
