README
ΒΆ
πΈ DockTail
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.
Features
- Automatically discover and expose Docker containers as Tailscale Services
- Auto-create service definitions via Tailscale API (with OAuth or API key)
- HTTP, HTTPS and TCP protocols
- Tailscale HTTPS with automatic TLS certificates
- Tailscale Funnel support (public internet access)
- Multiple services from a single container
- Automatic cleanup when containers stop
- Runs entirely in a stateless Docker container
Quick Start
services:
docktail:
image: ghcr.io/marvinvr/docktail:latest
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/run/tailscale:/var/run/tailscale
environment:
# Optional but recommended - enables auto-service-creation
- TAILSCALE_OAUTH_CLIENT_ID=${TAILSCALE_OAUTH_CLIENT_ID}
- TAILSCALE_OAUTH_CLIENT_SECRET=${TAILSCALE_OAUTH_CLIENT_SECRET}
myapp:
image: nginx:latest
# No ports needed! DockTail proxies directly to container IP
labels:
- "docktail.service.enable=true"
- "docktail.service.name=myapp"
- "docktail.service.port=80"
docker compose up -d
curl http://myapp.your-tailnet.ts.net
That's it! See Configuration for OAuth setup (recommended) or running without credentials.
Configuration
DockTail works in three modes. Choose based on your needs:
Recommended: OAuth Credentials
OAuth lets DockTail auto-create services in your Tailscale Admin Console. No manual setup required.
Setup:
- Go to Tailscale Admin Console β Settings β OAuth clients
- Create a new OAuth client with scope
alland your service tags (e.g.,tag:container) - Add to your DockTail environment:
environment:
- TAILSCALE_OAUTH_CLIENT_ID=your-client-id
- TAILSCALE_OAUTH_CLIENT_SECRET=your-client-secret
Benefits:
- Services auto-created when containers start
- Never expires (unlike API keys)
- Proper tag-based ACL support
Alternative: API Key
Also auto-creates services, but expires every 90 days.
Setup:
- Go to Tailscale Admin Console β Settings β Keys
- Generate an API key
- Add to your DockTail environment:
environment:
- TAILSCALE_API_KEY=tskey-api-...
No Credentials (Manual Mode)
DockTail works without any credentials for basic use. Services are advertised locally via the Tailscale CLI, but you must manually create service definitions in the Admin Console.
Manual setup required:
- Go to Tailscale Admin Console β Services
- Create a service for each container you want to expose
- Configure ACL auto-approvers (see ACL Configuration)
Installation
Tailscale on Host
For systems with Tailscale installed on the host:
services:
docktail:
image: ghcr.io/marvinvr/docktail:latest
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/run/tailscale:/var/run/tailscale
environment:
# Optional but recommended - enables auto-service-creation
- TAILSCALE_OAUTH_CLIENT_ID=${TAILSCALE_OAUTH_CLIENT_ID}
- TAILSCALE_OAUTH_CLIENT_SECRET=${TAILSCALE_OAUTH_CLIENT_SECRET}
Note: We mount the
/var/run/tailscaledirectory rather than the socket file directly. Whentailscaledrestarts, it recreates the socket with a new inode β a file bind mount would go stale, but a directory mount stays in sync automatically.
Host tag requirement: The host machine running Tailscale must advertise a tag that matches your ACL auto-approvers (see Tailscale Admin Setup). For example:
sudo tailscale up --advertise-tags=tag:server --reset
Warning: The
--resetflag briefly drops the Tailscale connection. If you are connected via SSH over Tailscale, your session will be interrupted momentarily. The connection will restore automatically once Tailscale reconnects.
When using the sidecar setup below, the sidecar container handles its own tags via TS_AUTHKEY, so this step is not needed.
Tailscale Sidecar
For systems without Tailscale installed on the host:
services:
tailscale:
image: tailscale/tailscale:latest
hostname: docktail-host
environment:
- TS_AUTHKEY=${TAILSCALE_AUTH_KEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_SOCKET=/var/run/tailscale/tailscaled.sock
volumes:
- tailscale-state:/var/lib/tailscale
- tailscale-socket:/var/run/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- NET_ADMIN
- SYS_MODULE
network_mode: host
restart: unless-stopped
docktail:
image: ghcr.io/marvinvr/docktail:latest
depends_on:
- tailscale
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- tailscale-socket:/var/run/tailscale
environment:
# Optional but recommended - enables auto-service-creation
- TAILSCALE_OAUTH_CLIENT_ID=${TAILSCALE_OAUTH_CLIENT_ID}
- TAILSCALE_OAUTH_CLIENT_SECRET=${TAILSCALE_OAUTH_CLIENT_SECRET}
volumes:
tailscale-state:
tailscale-socket:
Set TAILSCALE_AUTH_KEY to authenticate the Tailscale container (generate at Tailscale Admin β Settings β Keys).
Tailscale Admin Setup
After deploying DockTail, you need to configure a few things in the Tailscale Admin Console for services to work.
1. Configure Access Controls (ACLs)
Services require ACL auto-approvers to allow the host machine to advertise them. Go to Access Controls and add an autoApprovers block:
{
"autoApprovers": {
"services": {
"tag:container": ["tag:server"]
}
}
}
This allows machines tagged tag:server to advertise services tagged tag:container (the default DockTail tag). Adjust the tags to match your setup β the right side must match the tag on your host machine (or sidecar auth key), and the left side must match the docktail.tags label on your containers (defaults to tag:container).
2. Approve the Service
The first time a new service is advertised, it must be manually approved in the Tailscale Admin Console:
- Go to the Services tab
- Find the newly advertised service
- Approve it to allow traffic
After the initial approval, the service will continue to work automatically on subsequent container restarts. If you are using OAuth or API key mode, service definitions are auto-created, but the first approval may still be required depending on your ACL configuration.
Labeling Containers
Direct Container IP Proxying (Default)
By default, DockTail proxies directly to container IPs on the Docker bridge network. No port publishing required!
services:
myapp:
image: nginx:latest
# No ports needed!
labels:
- "docktail.service.enable=true"
- "docktail.service.name=myapp"
- "docktail.service.port=80"
DockTail automatically detects the container's IP address and configures Tailscale to proxy directly to it. When containers restart and get new IPs, DockTail automatically updates the configuration.
Opt-out: Set docktail.service.direct=false to use published port bindings instead (legacy behavior).
Service Labels
| Label | Required | Default | Description |
|---|---|---|---|
docktail.service.enable |
Yes | - | Enable DockTail for container |
docktail.service.name |
Yes | - | Service name (e.g., web, api) |
docktail.service.port |
Yes | - | Container port to proxy to |
docktail.service.direct |
No | true |
Proxy directly to container IP (no port publishing needed) |
docktail.service.network |
No | bridge |
Docker network to use for container IP |
docktail.service.protocol |
No | Smart* | Container protocol: http, https, https+insecure, tcp, tls-terminated-tcp |
docktail.service.service-port |
No | Smart** | Port Tailscale listens on |
docktail.service.service-protocol |
No | Smart*** | Tailscale protocol: http, https, tcp |
docktail.tags |
No | tag:container |
Comma-separated tags for ACLs |
Smart Defaults:
- *
protocol:httpsif container port is 443, otherwisehttp - **
service-port:443if service-protocol ishttps, otherwise80 - ***
service-protocol:httpsif service-port is 443, matchesprotocolfor TCP, otherwisehttp
Funnel Labels (Public Internet Access)
Funnel exposes your service to the public internet. Independent from service labels.
| Label | Required | Default | Description |
|---|---|---|---|
docktail.funnel.enable |
Yes | false |
Enable Tailscale Funnel |
docktail.funnel.port |
Yes | - | Container port |
docktail.funnel.funnel-port |
No | 443 |
Public port (443, 8443, or 10000) |
docktail.funnel.protocol |
No | https |
Protocol: https, tcp, tls-terminated-tcp |
Notes:
- Only ONE funnel per port (Tailscale limitation)
- Uses machine hostname, not service name:
https://<machine>.<tailnet>.ts.net
Examples
Web Application
services:
nginx:
image: nginx:latest
# No ports needed - DockTail proxies directly to container IP
labels:
- "docktail.service.enable=true"
- "docktail.service.name=web"
- "docktail.service.port=80"
HTTPS with Auto TLS
services:
api:
image: myapi:latest
labels:
- "docktail.service.enable=true"
- "docktail.service.name=api"
- "docktail.service.port=3000"
- "docktail.service.service-port=443" # Auto-enables HTTPS
Access: https://api.your-tailnet.ts.net
Database (TCP)
services:
postgres:
image: postgres:16
labels:
- "docktail.service.enable=true"
- "docktail.service.name=db"
- "docktail.service.port=5432"
- "docktail.service.protocol=tcp"
- "docktail.service.service-port=5432"
Multiple Services from One Container
A single container can expose multiple separate Tailscale services using numbered labels (docktail.service.N.*). Each indexed entry requires its own name and port, and becomes an independent service. Tags and network are inherited from the primary config.
This is useful for VPN gateway containers (e.g., gluetun) where multiple apps route through a single container but each needs its own Tailscale service.
services:
gluetun:
image: gluetun:latest
labels:
- "docktail.service.enable=true"
- "docktail.service.name=qbittorrent"
- "docktail.service.port=8000"
- "docktail.service.1.name=bitmagnet"
- "docktail.service.1.port=8001"
Per-index overridable labels: name (required), port (required), service-port, protocol, and service-protocol.
Custom Docker Network
services:
app:
image: myapp:latest
networks:
- backend
labels:
- "docktail.service.enable=true"
- "docktail.service.name=app"
- "docktail.service.port=3000"
- "docktail.service.network=backend" # Specify which network to use
networks:
backend:
Legacy Mode (Published Ports)
services:
app:
image: myapp:latest
ports:
- "8080:3000" # Required when direct=false
labels:
- "docktail.service.enable=true"
- "docktail.service.name=app"
- "docktail.service.port=3000"
- "docktail.service.direct=false" # Use published port instead of container IP
Public Website with Funnel
services:
website:
image: nginx:latest
labels:
- "docktail.service.enable=true"
- "docktail.service.name=website"
- "docktail.service.port=80"
- "docktail.service.service-port=443"
- "docktail.funnel.enable=true"
- "docktail.funnel.port=80"
Access:
- Tailnet:
https://website.your-tailnet.ts.net - Public:
https://your-machine.your-tailnet.ts.net
Reference
Environment Variables
| Variable | Default | Description |
|---|---|---|
TAILSCALE_OAUTH_CLIENT_ID |
- | OAuth Client ID (optional, enables auto-service-creation) |
TAILSCALE_OAUTH_CLIENT_SECRET |
- | OAuth Client Secret (optional, enables auto-service-creation) |
TAILSCALE_API_KEY |
- | API Key (optional alternative to OAuth, expires 90 days) |
TAILSCALE_TAILNET |
- |
Tailnet ID (defaults to key's tailnet) |
DEFAULT_SERVICE_TAGS |
tag:container |
Default tags for services |
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 |
If both OAuth and API key are set, OAuth takes precedence.
Supported Protocols
Tailscale-facing (service-protocol):
http- Layer 7 HTTPhttps- Layer 7 HTTPS (auto TLS)tcp- Layer 4 TCPtls-terminated-tcp- Layer 4 with TLS termination
Container-facing (protocol):
http- HTTP backendhttps- HTTPS with valid certificatehttps+insecure- HTTPS with self-signed certificatetcp- TCP backendtls-terminated-tcp- TCP with TLS termination
ACL Configuration
See Tailscale Admin Setup for the required ACL auto-approver configuration and service approval steps.
How It Works
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Docker Host β
β β
β ββββββββββββββββββββ ββββββββββββββββββββ β
β β DockTail ββββββββββΆβ Tailscale Daemon β β
β β (Container) β CLI β (Host Process) β β
β ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ β
β β β β
β β Docker Socket β Proxies to β
β β Monitoring β container IP β
β βΌ βΌ β
β ββββββββββββββββββββ ββββββββββββββββββββ β
β β App Container βββββββββββ 172.17.0.3:80 β β
β β Port 80 β β (bridge network)β β
β β No ports needed βββββββββββ β β
β ββββββββββββββββββββ ββββββββββββββββββββ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β Tailscale Network
βΌ
βββββββββββββββββββββββ
β Tailnet Clients β
β Access services: β
β web.tailnet.ts.net β
βββββββββββββββββββββββ
- Container Discovery - Monitors Docker events for container start/stop
- Label Parsing - Extracts service configuration from container labels
- IP Detection - Gets container IP from Docker network settings (default: bridge)
- Config Generation - Creates Tailscale service config proxying to container IP
- Service Advertisement - Executes Tailscale CLI to advertise services
- Control Plane Sync - If OAuth/API key configured, creates service definitions via API
- Reconciliation - Periodically syncs state; auto-updates when container IPs change
Notes:
- DockTail does NOT delete service definitions from the API when containers stop (conservative deletion strategy)
- Container IP changes on restart are handled automatically during reconciliation
Building from Source
go build -o docktail .
docker build -t docktail:latest .
./docktail
Links
- Tailscale Services Documentation
- Tailscale Funnel Documentation
- Tailscale Service Configuration Reference
- Docker SDK for Go
Star History
License
AGPL v3
By @marvinvr
Documentation
ΒΆ
There is no documentation for this package.