Skip to content

marvinvr/docktail

Repository files navigation

🍸 DockTail

Unleash your Containers as Tailscale Services

DockTail Header

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 │
               └─────────────────────┘

Features

  • 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.

Quick Start

Admin Console Setup

Before installing the DockTail, configure your Tailscale admin console at https://login.tailscale.com/admin/services:

  1. 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
  2. (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)
  3. (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:homelab to automatically advertise services tagged tag:homelab-service

See Tailscale Services documentation for detailed setup instructions.

Installation

Option 1: Docker Compose

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.sock

Option 2: Docker Run

docker 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

Usage

🚨 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.net

With 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

Available Labels

Service Labels (Tailnet-only Access)

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 to 80, OR 443 if service-protocol=https
  • **service-protocol: Defaults to match protocol for TCP backends, otherwise https if service-port=443, otherwise http
  • ***protocol: Defaults to https if container port=443, otherwise http

Critical: If ports: "9080:80", then docktail.service.port=80 (container port, NOT 9080)

Funnel Labels (Public Internet Access)

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 per funnel-port (Tailscale limitation). Multiple containers cannot share the same funnel-port.
  • When used together with serve:
    • Serve URL: https://<service-name>.<tailnet>.ts.net (tailnet only, uses service.port)
    • Funnel URL: https://<machine-hostname>.<tailnet>.ts.net:<funnel-port> (public, uses funnel.port)
  • funnel-port (public) can be: 443, 8443, or 10000 for HTTPS
  • Funnel exposes your service to the ⚠️ public internet - use with caution!

Supported Protocols

  • http: Layer 7 HTTP forwarding
  • https: Layer 7 HTTPS forwarding (auto TLS cert)
  • tcp: Layer 4 TCP forwarding
  • tls-terminated-tcp: Layer 4 with TLS termination

Environment Variables

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

How It Works

  1. Container Discovery: Monitors Docker events API for container lifecycle events (start, stop, die, restart)
  2. Label Parsing: Extracts Tailscale service configuration from container labels
  3. Port Detection: Queries Docker API for published host ports
  4. Configuration Generation: Creates Tailscale service configuration proxying to localhost:HOST_PORT
  5. Configuration Application: Executes Tailscale CLI commands to apply config and advertise services
  6. Stateless Operation: Periodically reconciles state by querying Docker and Tailscale APIs

Examples

Web Application (Simple)

services:
  nginx:
    image: nginx:latest
    ports:
      - "8080:80"
    labels:
      - "docktail.service.enable=true"
      - "docktail.service.name=web"
      - "docktail.service.port=80"

Database (Custom Port & Protocol)

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)

API with HTTPS (Auto TLS Certificate)

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!

Public Website with Funnel (Internet Access)

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.net

Security Note: Funnel exposes your service to the public internet. Ensure proper authentication and security measures are in place!

Funnel with Custom Public Port

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 protocol

Access 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

Building from Source

# Build binary
go build -o docktail .

# Build Docker image
docker build -t docktail:latest .

# Run locally
./docktail

Links

License

AGPL v3


By @marvinvr

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages