Self-hosted service
marvinvr/docktail avatar
marvinvr/docktail

DockTail: Docker Labels as Tailscale Service Definitions

Expose Docker containers as Tailscale Services using label-based configuration.

1,233 stars44 forksGoAGPL-3.0

At a glance

What is it?
DockTail watches the Docker socket, reads docktail.* labels, and turns matching containers into native Tailscale Services. It is a small Go agent for homelabs and small fleets, and its main constraint is that the host must already be a Tailscale node allowed to advertise services.
Who is it for?
Adopt DockTail if you already run Tailscale on the Docker host and want container exposure driven entirely by labels, with no published ports and no per-app Tailscale device. Skip it if your host cannot be authorised to advertise services, or if you want a proxy that manages its own tailnet identity.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem: container exposure without a second tailnet device per app

The usual way to reach a container over Tailscale is to run Tailscale inside the container, or to publish a port and point a proxy at it. Both approaches have a cost. A Tailscale sidecar consumes a device slot in the tailnet for every app, and the tailnet admin then has to manage a growing device list. Publishing ports means the Docker host exposes the app on its own interfaces, which is exactly what you were trying to avoid. DockTail takes a third route: it runs once on the host, watches the Docker socket, and registers containers as Tailscale Services. The README states plainly that app containers do not need published Docker ports by default, because DockTail proxies directly to their Docker network IPs. The audience is the self-hosted and homelab crowd, plus small fleets where one host runs a handful of internal services. The topics list (homelab, self-hosted, reverse-proxy) matches that reading.

How the label-to-service pipeline works

The mechanism is a reconcile loop. DockTail reads the Docker API through the mounted socket, filters containers that carry docktail.service.enable=true, and maps the remaining docktail.* labels onto a Tailscale Service definition. The service name comes from docktail.service.name, the backend port from docktail.service.port, the protocol from docktail.service.protocol, and the externally visible port from docktail.service.service-port. Because DockTail talks to the Docker network directly, the backend address is the container IP, not a host port. That is why the nginx example in the Quick Start has no ports block at all. The README also claims automatic reconciliation when containers restart or IPs change, which is the part that makes the label approach usable: a container that comes back on a new IP does not need a manual edit. Service creation itself goes through Tailscale, using either an OAuth client or an API key, which the README lists as optional but recommended because it enables automatic service creation. Without those credentials you can still run the agent, but you are configuring services yourself. The agent is described as a stateless Docker container runtime, so there is no local database to back up or migrate.

Getting it running: the compose file and the labels that matter

The Quick Start is a two-service compose file. The docktail service uses image ghcr.io/marvinvr/docktail:latest, restart: unless-stopped, and mounts two paths: /var/run/docker.sock and /var/run/tailscale, both from the host. The Docker socket is mounted read-only in the example. Environment variables TAILSCALE_OAUTH_CLIENT_ID and TAILSCALE_OAUTH_CLIENT_SECRET supply credentials. The second service is the app, and its entire configuration is three labels: docktail.service.enable=true, docktail.service.name=myapp, docktail.service.port=80. The README then shows docker compose up -d followed by curl against myapp.your-tailnet.ts.net. For secret files, the README gives two forms: FILE__TAILSCALE_OAUTH_CLIENT_ID and FILE__TAILSCALE_OAUTH_CLIENT_SECRET, or TAILSCALE_OAUTH_CLIENT_ID_FILE and TAILSCALE_OAUTH_CLIENT_SECRET_FILE, both pointed at mounted file paths. The README's own caveat is worth repeating: this assumes the Docker host is connected to Tailscale and allowed to advertise services, and it defers host setup, sidecar setup, rootless Docker, OAuth permissions and ACLs to the full docs. That deferral is the largest gap in the README.

Funnel, PROXY protocol, and the labels that change the exposure model

Beyond basic service exposure, DockTail has labels for three situations. Tailscale HTTPS is selected by setting docktail.service.service-port=443, as in the api example. TCP services use docktail.service.protocol=tcp with an explicit service-port, as in the PostgreSQL example on 5432. For TCP reverse proxies, docktail.service.proxy-protocol=2 turns on PROXY protocol so the backend sees the tailnet client IP rather than the proxy address; the README shows this on a Traefik container listening on 443. Funnel is configured with a separate label family: docktail.funnel.enable=true, docktail.funnel.port, and either docktail.funnel.funnel-port=8443 or docktail.funnel.path=/webhook for path-mounted HTTP(S) Funnel. The release history shows this area is still moving: 1.4.0 is titled Funnel only containers, which suggests the Funnel label set was extended in that release. If you rely on Funnel, pin a version rather than tracking latest, and check the docs for the label combination that matches your case.

What DockTail does not do, and when it is the wrong tool

The design assumes the Docker host is itself a Tailscale node with permission to advertise services. That is a real precondition, not a footnote. On a host where Tailscale runs only inside containers, or where the tailnet ACLs do not allow service advertisement, DockTail has nothing to register against. The README does not walk through the host setup or the ACL changes in the Quick Start; it points at the full docs. A second limitation is that DockTail is a Docker-specific agent. It discovers containers through the Docker socket. Anything not running as a Docker container on that host is invisible to it, and there is no described mechanism for static service definitions outside labels. Third, the label vocabulary is the interface. There is no separate config file mentioned in the material, so a change to a service definition means editing compose labels and recreating the container. Finally, the optional cleanup of unused Tailscale service definitions is described as opt-in and safe with multiple instances, which implies the default is to leave stale definitions in place. That is the safer default, but it means removing a container does not necessarily remove its service.

The alternative: TSDProxy and what actually differs

The README's own comparison table puts DockTail next to TSDProxy, ScaleTail, tsbridge, and plain Tailscale Services. The distinguishing column is native Tailscale Services. DockTail and plain Services both use them; TSDProxy, ScaleTail and tsbridge do not. That single difference drives the rest. Plain Services means you write the service configuration yourself, with no Docker reconciliation and no label discovery. TSDProxy is label-configured and reconciles Docker like DockTail, but it does not use native Services, which is why the table marks apps as consuming separate Tailscale device slots. If device count in the tailnet is not a constraint for you, TSDProxy's approach is a smaller conceptual jump and does not require the host to be authorised to advertise services. If device count is the constraint, or if you want the service to appear in the Tailscale admin surface as a Service rather than as a device, DockTail is the one that matches. ScaleTail is template-based, so the README notes each app usually starts from its own Compose recipe. tsbridge is label-configured and reconciles, but the table marks port publishing as dependent on proxy and network setup.

Maintenance, licence, and the optional Cloud module

DockTail is AGPL-3.0. That is a copyleft licence with a network-use clause, and it matters if you plan to modify DockTail and offer it to others over a network. This is not legal advice; if you intend to redistribute a modified version or run it as part of a hosted offering, read the licence text and get your own counsel. On maintenance, the project is active: the last push recorded is 2026-08-30, and the release cadence shows 1.4.0 in April 2026, 1.5.0 in July, and 1.6.0 later in July. The 1.5.0 title is Quality of life improvements, which is a vague release note and gives you little to plan around. Upgrading is a container image swap plus a recreate, since the agent is described as stateless; the risk sits in label and service-definition changes between versions, not in local state. The optional DockTail Cloud module is worth reading before enabling. The README states it is opt-in via a single environment variable, DOCKTAIL_CLOUD_KEY, that without the key the module is completely inert with no connection opened, and that the link is outbound-only and metadata-only with no exec, deploy or shell message types in the protocol. It also points at the cloud/ directory in the repository so you can check that claim yourself. That is a reasonable amount of disclosure for a hosted add-on, but the dashboard is a separate service with its own account, so enabling it moves part of your monitoring off your own hardware.

Editorial conclusion

Adopt DockTail if you already run Tailscale on the Docker host and want container exposure driven entirely by labels, with no published ports and no per-app Tailscale device. Skip it if your host cannot be authorised to advertise services, or if you want a proxy that manages its own tailnet identity. Verify first that your tailnet ACLs permit the service names you plan to use, that the OAuth client has the scopes the docs list for service creation, and that /var/run/tailscale is mounted read-write as the Quick Start shows.

Official sources

  1. License: AGPL-3.0
  2. marvinvr/docktail on GitHub
  3. Project website
  4. README
  5. Releases
Community notes

Community notes