Expose Docker containers as Tailscale Services using label-based configuration.
See the codeUnleash your containers as Tailscale Services.
DockTail watches Docker containers, reads docktail.* labels, and exposes matching containers as Tailscale Services. App containers do not need published Docker ports by default; DockTail proxies directly to their Docker network IPs.
DockTail uses native Tailscale Services, not per-container Tailscale devices.
| DockTail | TSDProxy | ScaleTail | tsbridge | Plain Services | |
|---|---|---|---|---|---|
| Native Tailscale Services | ✅ | ❌ | ❌ | ❌ | ✅ |
| Configured via Docker labels | ✅ | ✅ | ❌ | ✅ | ❌ |
| Apps do not consume separate Tailscale device slots | ✅ | ❌ | ❌ | ❌ | ✅ |
| No app port publishing | ✅ | ⚠️¹ | ⚠️² | ⚠️¹ | ⚠️³ |
| Automatic Docker reconciliation | ✅ | ✅ | ❌ | ✅ | ❌ |
| Low manual setup after install | ✅ | ✅ | ⚠️⁴ | ✅ | ❌ |
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 automatic 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 the 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
This assumes the Docker host is connected to Tailscale and allowed to advertise services. See the full docs for host setup, sidecar setup, rootless Docker, OAuth permissions, ACLs, labels, Funnel, and examples. docker-compose.yaml is a ready-to-run version of this setup; for Podman, Synology, Unraid, TrueNAS, macOS, Windows, Swarm and Kubernetes see the platform guides, and for a read-only Docker socket proxy and dropped capabilities see Hardening.
For Docker secrets or other mounted secret files, set FILE__TAILSCALE_OAUTH_CLIENT_ID / FILE__TAILSCALE_OAUTH_CLIENT_SECRET or TAILSCALE_OAUTH_CLIENT_ID_FILE / TAILSCALE_OAUTH_CLIENT_SECRET_FILE to the mounted file paths instead of putting the values directly in the environment.
Expose an app with Tailscale HTTPS:
labels:
- "docktail.service.enable=true"
- "docktail.service.name=api"
- "docktail.service.port=3000"
- "docktail.service.service-port=443"
Expose a database over TCP:
labels:
- "docktail.service.enable=true"
- "docktail.service.name=db"
- "docktail.service.port=5432"
- "docktail.service.protocol=tcp"
- "docktail.service.service-port=5432"
Preserve the tailnet client IP on a TCP reverse proxy:
labels:
- "docktail.service.enable=true"
- "docktail.service.name=traefik"
- "docktail.service.port=443"
- "docktail.service.protocol=tcp"
- "docktail.service.service-port=443"
- "docktail.service.proxy-protocol=2"
Expose a service publicly with Tailscale Funnel:
labels:
- "docktail.funnel.enable=true"
- "docktail.funnel.port=3000"
- "docktail.funnel.funnel-port=8443"
Mount an HTTP(S) Funnel at a path:
labels:
- "docktail.funnel.enable=true"
- "docktail.funnel.port=3000"
- "docktail.funnel.path=/webhook"
Once you run DockTail on more than one machine, "is it still up?" gets tedious. DockTail Cloud is a hosted dashboard for that — and because it already has the Docker and Tailscale context, it tells you which kind of broken you're looking at:
● down · OOM-killed → the container was killed for running out of memory
● local up · awaiting approval → the app is fine, the exposure isn't
● host offline · heartbeat missing → the box stopped reporting
It rides along with the agent you already run — no second binary. Set one environment variable and the same container starts reporting:
environment:
- DOCKTAIL_CLOUD_KEY=${DOCKTAIL_CLOUD_KEY} # from cloud.docktail.org
Without the key the module is completely inert: no connection is opened and DockTail behaves exactly as before. The link is outbound-only and metadata-only — the protocol has no exec, deploy, or shell message types, which you can verify in cloud/.
DockTail Cloud is a paid service (plans and pricing); a new workspace can connect one host as an unmonitored preview before you choose a plan. docker-compose.cloud.yaml is a complete example.
Explore DockTail Cloud · open the dashboard · agent setup
Human docs: https://docktail.org/docs/
Markdown docs: https://docktail.org/docs.md
LLM guide: https://docktail.org/llms.txt
Full LLM docs: https://docktail.org/llms-full.txt
DockTail Cloud overview: https://docktail.org/cloud/
Optional Cloud agent setup: docs/06-cloud.md
The canonical documentation source lives in docs/. The site at
docktail.org is built from a separate repository, which pins this one as a
submodule and renders those Markdown files with tools/docsgen at build time.
go build -o docktail .
docker build -t docktail:latest .
The image is published for linux/amd64, linux/arm64 and linux/arm/v7, and releases newer than 1.8.3 carry static Linux binaries for the same platforms; see Platforms And Release Binaries.
AGPL v3
By @marvinvr
1,115 followers · starred Aug 2026
469 followers · starred Jun 2026
58 followers · starred Feb 2026
516 followers · starred Aug 2026
Expose Docker containers as Tailscale Services using label-based configuration.
See the codeUnleash your containers as Tailscale Services.
DockTail watches Docker containers, reads docktail.* labels, and exposes matching containers as Tailscale Services. App containers do not need published Docker ports by default; DockTail proxies directly to their Docker network IPs.
DockTail uses native Tailscale Services, not per-container Tailscale devices.
| DockTail | TSDProxy | ScaleTail | tsbridge | Plain Services | |
|---|---|---|---|---|---|
| Native Tailscale Services | ✅ | ❌ | ❌ | ❌ | ✅ |
| Configured via Docker labels | ✅ | ✅ | ❌ | ✅ | ❌ |
| Apps do not consume separate Tailscale device slots | ✅ | ❌ | ❌ | ❌ | ✅ |
| No app port publishing | ✅ | ⚠️¹ | ⚠️² | ⚠️¹ | ⚠️³ |
| Automatic Docker reconciliation | ✅ | ✅ | ❌ | ✅ | ❌ |
| Low manual setup after install | ✅ | ✅ | ⚠️⁴ | ✅ | ❌ |
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 automatic 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 the 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
This assumes the Docker host is connected to Tailscale and allowed to advertise services. See the full docs for host setup, sidecar setup, rootless Docker, OAuth permissions, ACLs, labels, Funnel, and examples. docker-compose.yaml is a ready-to-run version of this setup; for Podman, Synology, Unraid, TrueNAS, macOS, Windows, Swarm and Kubernetes see the platform guides, and for a read-only Docker socket proxy and dropped capabilities see Hardening.
For Docker secrets or other mounted secret files, set FILE__TAILSCALE_OAUTH_CLIENT_ID / FILE__TAILSCALE_OAUTH_CLIENT_SECRET or TAILSCALE_OAUTH_CLIENT_ID_FILE / TAILSCALE_OAUTH_CLIENT_SECRET_FILE to the mounted file paths instead of putting the values directly in the environment.
Expose an app with Tailscale HTTPS:
labels:
- "docktail.service.enable=true"
- "docktail.service.name=api"
- "docktail.service.port=3000"
- "docktail.service.service-port=443"
Expose a database over TCP:
labels:
- "docktail.service.enable=true"
- "docktail.service.name=db"
- "docktail.service.port=5432"
- "docktail.service.protocol=tcp"
- "docktail.service.service-port=5432"
Preserve the tailnet client IP on a TCP reverse proxy:
labels:
- "docktail.service.enable=true"
- "docktail.service.name=traefik"
- "docktail.service.port=443"
- "docktail.service.protocol=tcp"
- "docktail.service.service-port=443"
- "docktail.service.proxy-protocol=2"
Expose a service publicly with Tailscale Funnel:
labels:
- "docktail.funnel.enable=true"
- "docktail.funnel.port=3000"
- "docktail.funnel.funnel-port=8443"
Mount an HTTP(S) Funnel at a path:
labels:
- "docktail.funnel.enable=true"
- "docktail.funnel.port=3000"
- "docktail.funnel.path=/webhook"
Once you run DockTail on more than one machine, "is it still up?" gets tedious. DockTail Cloud is a hosted dashboard for that — and because it already has the Docker and Tailscale context, it tells you which kind of broken you're looking at:
● down · OOM-killed → the container was killed for running out of memory
● local up · awaiting approval → the app is fine, the exposure isn't
● host offline · heartbeat missing → the box stopped reporting
It rides along with the agent you already run — no second binary. Set one environment variable and the same container starts reporting:
environment:
- DOCKTAIL_CLOUD_KEY=${DOCKTAIL_CLOUD_KEY} # from cloud.docktail.org
Without the key the module is completely inert: no connection is opened and DockTail behaves exactly as before. The link is outbound-only and metadata-only — the protocol has no exec, deploy, or shell message types, which you can verify in cloud/.
DockTail Cloud is a paid service (plans and pricing); a new workspace can connect one host as an unmonitored preview before you choose a plan. docker-compose.cloud.yaml is a complete example.
Explore DockTail Cloud · open the dashboard · agent setup
Human docs: https://docktail.org/docs/
Markdown docs: https://docktail.org/docs.md
LLM guide: https://docktail.org/llms.txt
Full LLM docs: https://docktail.org/llms-full.txt
DockTail Cloud overview: https://docktail.org/cloud/
Optional Cloud agent setup: docs/06-cloud.md
The canonical documentation source lives in docs/. The site at
docktail.org is built from a separate repository, which pins this one as a
submodule and renders those Markdown files with tools/docsgen at build time.
go build -o docktail .
docker build -t docktail:latest .
The image is published for linux/amd64, linux/arm64 and linux/arm/v7, and releases newer than 1.8.3 carry static Linux binaries for the same platforms; see Platforms And Release Binaries.
AGPL v3
By @marvinvr
1,115 followers · starred Aug 2026
469 followers · starred Jun 2026
58 followers · starred Feb 2026
516 followers · starred Aug 2026