Unleash your Containers as Tailscale Services
Automatically expose Docker containers as Tailscale Services using label-based configuration - zero-config service mesh for your dockerized services.
┌────────────────────────────────────────────────────────┐
│ Docker Host │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ DockTail │────────▶│ Tailscale Daemon │ │
│ │ (Container) │ CLI │ (Host Process) │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ │ Docker Socket │ Proxies to │
│ │ Monitoring │ localhost │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ App Container │◀────────│ localhost:9080 │ │
│ │ Port 80 │ Mapped │ localhost:9081 │ │
│ │ ports: 9080:80 │◀────────│ │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘
│
│ Tailscale Network
▼
┌─────────────────────┐
│ Tailnet Clients │
│ Access services: │
│ web.tailnet.ts.net │
└─────────────────────┘
- Automatically discover and advertise Docker containers as Tailscale Services
- HTTP, HTTPS and TCP protocols for running services
- Support Tailscale HTTPS (auto TLS certificate)
- Automatically drain Tailscale service configurations on container stop
- Runs entirely in a stateless Docker container
- Tailscale Funnel support (public internet access)
- More? => Create an Issue :)
Warning
This project is still being developed and it is not yet recommended to use for mission critical services.
Before installing the DockTail, configure your Tailscale admin console at https://login.tailscale.com/admin/services:
-
Create service definitions (Services → Add service):
- Create a service for each application you want to expose
- Example: Service name
web,api,db, etc. - Note: DockTail will automatically configure and advertise these services
-
(Optional) Configure service tags:
- Navigate to Access Controls
- Add tags for service identification (e.g.,
tag:homelab-service) - Tag your Docker host (e.g.,
tag:homelab)
-
(Recommended) Enable auto-approval:
- Navigate to Access Controls and edit your ACL policy
- Add auto-approvers to skip manual approval for service advertisements:
{ "autoApprovers": { "services": { "tag:homelab-service": ["tag:homelab"] } } }- This allows devices tagged
tag:homelabto automatically advertise services taggedtag:homelab-service
See Tailscale Services documentation for detailed setup instructions.
Create a docker-compose.yaml:
version: '3.8'
services:
docktail:
image: ghcr.io/marvinvr/docktail:latest
container_name: docktail
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sockdocker run -d \
--name docktail \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock \
ghcr.io/marvinvr/docktail:latest🚨 CRITICAL: Container ports MUST be published to host. Tailscale serve only supports localhost proxies.
Basic example:
services:
myapp:
image: nginx:latest
ports:
- "8080:80" # REQUIRED! HOST:CONTAINER format
labels:
- "docktail.service.enable=true"
- "docktail.service.name=myapp"
- "docktail.service.port=80" # CONTAINER port (RIGHT side of "8080:80")Access from any device in your tailnet:
curl http://myapp.your-tailnet.ts.netWith HTTPS (auto TLS cert from Tailscale):
services:
myapp:
image: nginx:latest
ports:
- "8080:80"
labels:
- "docktail.service.enable=true"
- "docktail.service.name=myapp"
- "docktail.service.port=80" # Container port
- "docktail.service.protocol=http" # Container speaks HTTP
- "docktail.service.service-port=443" # Tailscale listens on 443
- "docktail.service.service-protocol=https" # Tailscale serves HTTPS (auto TLS!)Smart defaults (minimal config):
services:
myapp:
image: nginx:latest
ports:
- "8080:80"
labels:
- "docktail.service.enable=true"
- "docktail.service.name=myapp"
- "docktail.service.port=80"
- "docktail.service.service-port=443" # Port 443 → auto-defaults to HTTPS!
# service-protocol auto-defaults to "https" (based on port 443)
# protocol auto-defaults to "http" (TLS termination at Tailscale)Port Mapping Rules:
ports:="HOST:CONTAINER"(e.g.,"8080:80"= host 8080 → container 80)docktail.service.port= CONTAINER port (always the RIGHT side)- Result: Tailscale → localhost:8080 → Container:80
See Tailscale Service documentation for detailed setup instructions.
| Label | Required | Default | Description |
|---|---|---|---|
docktail.service.enable |
Yes | - | Enable DockTail for container |
docktail.service.name |
Yes | - | Service name (e.g., web, api-v2) |
docktail.service.port |
Yes | - | CONTAINER port (RIGHT side of ports:) |
docktail.service.protocol |
No | Smart*** | Protocol container speaks: http, https, tcp, tls-terminated-tcp |
docktail.service.service-port |
No | Smart* | Port Tailscale listens on |
docktail.service.service-protocol |
No | Smart** | Protocol Tailscale uses: http, https, tcp |
Smart Defaults:
- *
service-port: Defaults to80, OR443ifservice-protocol=https - **
service-protocol: Defaults to matchprotocolfor TCP backends, otherwisehttpsifservice-port=443, otherwisehttp - ***
protocol: Defaults tohttpsif containerport=443, otherwisehttp
Critical: If ports: "9080:80", then docktail.service.port=80 (container port, NOT 9080)
See Tailscale Funnel documentation for detailed setup instructions.
Independent from serve labels
| Label | Required | Default | Description |
|---|---|---|---|
docktail.funnel.enable |
Yes (for funnel) | false |
Enable Tailscale Funnel (public internet access) |
docktail.funnel.port |
Yes (for funnel) | - | CONTAINER port (same concept as service.port) |
docktail.funnel.funnel-port |
No | 443 |
PUBLIC port (must be 443, 8443, or 10000 for HTTPS) |
docktail.funnel.protocol |
No | https |
Protocol: https, tcp, tls-terminated-tcp |
Notes about Funnel:
- Funnel is independent of serve - different labels, different ports, different everything
- Can run funnel alone, serve alone, or both side-by-side on the same container
- Funnel uses the machine's hostname, NOT service names (unlike serve)
- Public URL format:
https://<machine-hostname>.<tailnet>.ts.net:<funnel-port> ⚠️ IMPORTANT: Only ONE funnel can be active perfunnel-port(Tailscale limitation). Multiple containers cannot share the samefunnel-port.- When used together with serve:
- Serve URL:
https://<service-name>.<tailnet>.ts.net(tailnet only, usesservice.port) - Funnel URL:
https://<machine-hostname>.<tailnet>.ts.net:<funnel-port>(public, usesfunnel.port)
- Serve URL:
funnel-port(public) can be: 443, 8443, or 10000 for HTTPS- Funnel exposes your service to the
⚠️ public internet - use with caution!
http: Layer 7 HTTP forwardinghttps: Layer 7 HTTPS forwarding (auto TLS cert)tcp: Layer 4 TCP forwardingtls-terminated-tcp: Layer 4 with TLS termination
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
info |
Logging level (debug, info, warn, error) |
RECONCILE_INTERVAL |
60s |
State reconciliation interval |
DOCKER_HOST |
unix:///var/run/docker.sock |
Docker daemon socket |
TAILSCALE_SOCKET |
/var/run/tailscale/tailscaled.sock |
Tailscale daemon socket |
- Container Discovery: Monitors Docker events API for container lifecycle events (start, stop, die, restart)
- Label Parsing: Extracts Tailscale service configuration from container labels
- Port Detection: Queries Docker API for published host ports
- Configuration Generation: Creates Tailscale service configuration proxying to
localhost:HOST_PORT - Configuration Application: Executes Tailscale CLI commands to apply config and advertise services
- Stateless Operation: Periodically reconciles state by querying Docker and Tailscale APIs
services:
nginx:
image: nginx:latest
ports:
- "8080:80"
labels:
- "docktail.service.enable=true"
- "docktail.service.name=web"
- "docktail.service.port=80"services:
postgres:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: secret
labels:
- "docktail.service.enable=true"
- "docktail.service.name=db"
- "docktail.service.port=5432"
- "docktail.service.protocol=tcp"
- "docktail.service.service-port=5432"
# service-protocol auto-defaults to "tcp" (matches backend protocol)services:
api:
image: myapi:latest
ports:
- "8080:3000"
labels:
- "docktail.service.enable=true"
- "docktail.service.name=api"
- "docktail.service.port=3000" # Container listens on 3000
- "docktail.service.service-port=443" # Tailscale listens on 443 (HTTPS)
# service-protocol auto-defaults to "https" (based on service-port=443)
# protocol auto-defaults to "http" (based on container port=3000, not 443)Access with automatic TLS:
curl https://api.your-tailnet.ts.net # TLS cert auto-provisioned!services:
website:
image: nginx:latest
ports:
- "8080:80"
labels:
- "docktail.service.enable=true"
- "docktail.service.name=website"
- "docktail.service.port=80"
- "docktail.service.service-port=443" # Serve HTTPS on tailnet
- "docktail.funnel.enable=true" # Enable public internet access
- "docktail.funnel.port=80" # Container port for funnel
# funnel.protocol defaults to "https" and funnel.funnel-port defaults to "443"Access from your tailnet and the public internet:
# Tailnet-only access (via serve, uses service name):
curl https://website.your-tailnet.ts.net
# Public internet access (via funnel, uses machine hostname):
curl https://your-machine-name.your-tailnet.ts.netSecurity Note: Funnel exposes your service to the public internet. Ensure proper authentication and security measures are in place!
services:
app:
image: myapp:latest
ports:
- "8080:3000"
labels:
- "docktail.service.enable=true"
- "docktail.service.name=app"
- "docktail.service.port=3000"
- "docktail.service.service-port=443" # Tailnet HTTPS
- "docktail.funnel.enable=true" # Enable funnel
- "docktail.funnel.port=3000" # Container port for funnel
- "docktail.funnel.funnel-port=8443" # Public port (443, 8443, or 10000)
- "docktail.funnel.protocol=https" # Funnel protocolAccess via custom public port:
# Tailnet (serve):
curl https://app.your-tailnet.ts.net
# Public internet (funnel):
curl https://your-machine-name.your-tailnet.ts.net:8443# Build binary
go build -o docktail .
# Build Docker image
docker build -t docktail:latest .
# Run locally
./docktail- Tailscale Services Documentation
- Tailscale Funnel Documentation
- Tailscale Service Configuration Reference
- Docker SDK for Go
AGPL v3
By @marvinvr
