Comparison

DockTail vs tsbridge
One tagged host, or one tsnet node per app.

tsbridge is a small reverse proxy that puts each service on your tailnet as its own embedded tsnet node, with HTTPS, metrics and access logs built in and nothing to install on the host. DockTail takes the other route: one tagged host advertises your containers as Tailscale Services. Most of the differences below follow from that one choice.

tsbridge: MIT · Go · v0.15.1 · HTTP only · DockTail: AGPL · Tailscale Services · HTTP and TCP

How each one works

Same three app labels, different thing on the tailnet.

Both watch Docker for labelled containers and both work best with a Tailscale OAuth client. What they register with Tailscale is where they part ways.

tsbridge

docker-compose.yml
services:
  tsbridge:
    image: ghcr.io/jtdowney/tsbridge:latest
    command: ["-provider", "docker"]
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - tsbridge-state:/var/lib/tsbridge
    environment:
      - TS_OAUTH_CLIENT_ID=${TS_OAUTH_CLIENT_ID}
      - TS_OAUTH_CLIENT_SECRET=${TS_OAUTH_CLIENT_SECRET}
    labels:
      - tsbridge.tailscale.oauth_client_id_env=TS_OAUTH_CLIENT_ID
      - tsbridge.tailscale.oauth_client_secret_env=TS_OAUTH_CLIENT_SECRET
      - tsbridge.tailscale.state_dir=/var/lib/tsbridge
      - tsbridge.tailscale.default_tags=tag:server

  myapp:
    image: nginx:latest
    labels:
      - tsbridge.enabled=true
      - tsbridge.service.name=myapp
      - tsbridge.service.port=80

volumes:
  tsbridge-state:
Credentials and settings are labels on the tsbridge container itself. myapp becomes a tsnet node of that name at https://myapp.<tailnet>.ts.net. tsbridge reaches it by container name, which works here because both share the Compose default network; node state lives in the tsbridge-state volume. Adapted from the tsbridge Docker labels docs.

DockTail

docker-compose.yml
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:
      - 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 to the container IP.
    labels:
      - docktail.service.enable=true
      - docktail.service.name=myapp
      - docktail.service.port=80
DockTail drives the host's tailscaled through its socket. myapp becomes a Tailscale Service advertised by the host and proxied to the container IP, at http://myapp.<tailnet>.ts.net; add docktail.service.service-port=443 for HTTPS. The host must be tagged and allowed to advertise services, see the DockTail quick start.

Key differences

Three things decide it.

Most of the table below follows from these. Neither tool is the wrong design; they put Tailscale in different places.

One machine per app, or one host

tsbridge starts a separate tsnet node for every service, with its own hostname, node key and state folder, so each app is a tagged machine in the admin console. DockTail advertises each app as a Tailscale Service from one tagged host; the host is the only machine, the apps are listed under Services.

Where Tailscale runs

tsbridge embeds Tailscale: no tailscaled on the host, no tun device, and it works with Headscale. The flip side is per-app node state that must persist, and each node runs the Tailscale version the tsbridge release was built with. DockTail drives the host's (or a sidecar's) tailscaled and keeps no state of its own.

What gets proxied

tsbridge is an HTTP reverse proxy: HTTPS on :443 by default, Prometheus metrics, access logs, X-Tailscale-* identity headers, header rewriting. One port per service, no TCP. DockTail hands traffic to Tailscale Serve: HTTP, HTTPS, TCP and TLS-terminated TCP, several services per container, no metrics endpoint of its own.

Side by side

DockTail and tsbridge, row by row.

The rows a homelab setup usually runs into, from the tailnet side down to the container.

DockTail tsbridge
Each app on the tailnet as ✅ A Tailscale Service, advertised by one tagged host ⚠️ Its own tsnet machine, no Tailscale Services (issue #133 open)
Tailscale installed on the Docker host ⚠️ Needed: host tailscaled or a Tailscale sidecar ✅ Not needed, tsnet is embedded
Configuration ✅ docktail.* labels ✅ tsbridge.* labels, or a TOML file
Backends that are not containers ❌ Docker containers only ✅ Host ports, unix sockets, other machines (TOML)
Reaching the container ✅ Container IP by default, no published ports ⚠️ By container name: tsbridge must share a Docker network with each app
TCP (SSH, databases, game servers) ✅ tcp, tls-terminated-tcp, optional PROXY protocol ❌ HTTP(S) only
Several ports from one container ✅ Indexed labels ❌ One port per service
HTTPS out of the box ⚠️ HTTP on port 80 unless service-port is 443 or service-protocol is https ✅ HTTPS on :443 by default
Public access with Funnel ✅ On the host's machine name (Funnel cannot attach to a Service), split by path or port; TCP too ✅ Each app gets its own public hostname; HTTPS
Metrics, access logs, identity headers ❌ Not provided by the agent (DockTail Cloud is separate and optional) ✅ Prometheus endpoint, access log, X-Tailscale-* headers
State to keep ✅ None, the agent is stateless ⚠️ A state folder per app on a persistent volume
Headscale — Not covered by the docs ✅ Documented: auth key, tls_mode = "off"

tsbridge column based on the project's own documentation as of October 2026. If we got something wrong, tell us.

Choose tsbridge if

You want Tailscale inside the proxy, not on the host.

  • →You run Headscale, or want nothing Tailscale-related installed on the host.
  • →Some of what you expose is not a container: a host process, a unix socket, another machine on the LAN.
  • →Each public Funnel app should get its own hostname.
  • →You want Prometheus metrics, access logs and identity headers from the proxy itself.

Choose DockTail if

You want Tailscale Services from one host.

  • →Your apps should show up as Tailscale Services, not as one tagged machine each.
  • →You need TCP (SSH, Postgres, a game server) or several ports from one container.
  • →You would rather not attach a proxy container to every app's Docker network, or keep node state per app.
  • →You want the Tailscale version to follow the host's tailscaled, not a proxy's release schedule.

Both can run on one host: tsbridge brings its own tsnet nodes, DockTail configures the host's tailscaled. Give each app a name in only one of them.

Moving from tsbridge

One app at a time.

  1. 1

    Prepare the tailnet policy: tag the Docker host (for example tag:server), define it and tag:container in tagOwners, add an autoApprovers.services rule, and create an OAuth client with Services: Write and Devices Core: Write. Funnel apps also need the funnel node attribute on tag:server. The DockTail docs have the exact policy.

  2. 2

    Start DockTail next to tsbridge, with the host's Tailscale socket or the sidecar setup.

  3. 3

    Pick an app. Remove its tsbridge.* labels and recreate the container; tsbridge closes that app's node, which then shows as offline in the admin console. Delete that machine there so the name is free.

  4. 4

    Add the docktail.* labels (see the label mapping) and recreate the container again. A Funnel app's public URL moves to the host's machine name.

  5. 5

    When no app carries tsbridge labels anymore, stop tsbridge and remove its state volume and any Docker networks you created only for it.

Label mapping per app
  • tsbridge.enabled=true → docktail.service.enable=true
  • tsbridge.service.name=myapp → docktail.service.name=myapp
  • tsbridge.service.port=80 → docktail.service.port=80
  • tsbridge.service.funnel_enabled=true → docktail.funnel.enable=true + docktail.funnel.port=80
For HTTPS like tsbridge's default, also set docktail.service.service-port=443.

Questions

tsbridge and DockTail.

Does tsbridge use Tailscale Services?
No. tsbridge starts one tsnet node per service, and each node is its own machine on the tailnet with the service name as hostname. Its maintainer opened an issue to investigate Tailscale Services in October 2025; as of v0.15.1 there is no Services support in the code. DockTail is built on Tailscale Services from the start.
Is tsbridge still maintained?
Yes, by one developer, John Downey. The latest release is v0.15.1 from August 2026, a dependency refresh on top of v0.15.0 from April; work continues on the main branch. Releases have become less frequent than in its first months, so the Tailscale version inside each node can lag behind.
Does every tsbridge service show up as a separate machine in Tailscale?
Yes. Each service is its own tsnet node, so it appears as its own machine in the admin console, tagged with default_tags or the service's own tags when you use OAuth. Stopping an app closes its node, which stays in the list as offline. TSDProxy uses the same one-node-per-app model; DockTail registers one tagged host and lists the apps under Services.
Can tsbridge proxy TCP, SSH or a database?
Not today. tsbridge is built on Go's HTTP reverse proxy and only proxies HTTP(S); the maintainer has said TCP support would be a major overhaul with no timeline (issue #75). DockTail can advertise tcp and tls-terminated-tcp services, with an optional PROXY protocol header.
What is a tsbridge alternative that uses Tailscale Services?
DockTail reads the same kind of Docker labels and publishes each container as a Tailscale Service from one tagged host, proxying to the container IP. It needs Tailscale on the host or in a sidecar, a tag and an auto-approver rule in your tailnet policy; the DockTail quick start covers the setup.
Can I run tsbridge and DockTail on the same host?
Yes. They share nothing: tsbridge brings its own embedded tsnet nodes, DockTail configures the host's tailscaled. Give each app a name in only one of them. A realistic split is DockTail for TCP services and Tailscale Services, tsbridge for HTTP backends outside Docker or a Headscale tailnet.
Open source · AGPL

Move one app and compare.

Three labels on one app, one DockTail container next to it, and the app shows up under Services in your admin console. tsbridge can keep running for everything else while you try.