Quick Start

Get DockTail running in under a minute. Add it to your Docker Compose file alongside your services.

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.

Installation

Tailscale on Host

For systems with Tailscale already 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/tailscale directory rather than the socket file directly. When tailscaled restarts, it recreates the socket with a new inode — a file bind mount would go stale, but a directory mount stays in sync.

The host machine must advertise a tag that matches your ACL auto-approvers (see Admin Setup):

sudo tailscale up --advertise-tags=tag:server --reset
Warning: The --reset flag briefly drops the Tailscale connection. If connected via SSH over Tailscale, your session will be interrupted momentarily.

Tailscale Sidecar

For systems without Tailscale on the host — runs Tailscale as a container:

services:
  tailscale:
    image: tailscale/tailscale:latest
    hostname: docktail-host
    environment:
      - TS_AUTHKEY=${TAILSCALE_AUTH_KEY}
      - TS_EXTRA_ARGS=--advertise-tags=tag:server
      - 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). The sidecar must advertise tag:server so it can satisfy the ACL auto-approver.

Configuration

DockTail works in three modes. Choose based on your needs.

OAuth Credentials Recommended

OAuth lets DockTail auto-create services in your Tailscale Admin Console. No manual setup required.

  1. Go to Tailscale Admin → Settings → OAuth clients
  2. Create a new OAuth client with these permissions, scoped to your server tag:
    • General → Services: Write
    • Devices → Core: Write
    • Keys → Auth Keys: Write (sidecar method only)
  3. 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.

API Key

Also auto-creates services, but expires every 90 days.

  1. Go to Tailscale Admin → Settings → Keys
  2. Generate an API key
  3. 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.

  1. Go to Tailscale Admin → Services
  2. Create a service for each container you want to expose
  3. Configure ACL auto-approvers (see ACL Configuration)

Admin Setup

After deploying DockTail, configure your Tailscale Admin Console for services to work.

ACL Configuration

Services require tag definitions in tagOwners and auto-approvers to allow the host to advertise them. Go to Access Controls and add both blocks:

{
  "tagOwners": {
    "tag:server": ["autogroup:admin"],
    "tag:container": ["tag:server"]
  },
  "autoApprovers": {
    "services": {
      "tag:container": ["tag:server"]
    }
  }
}
Note: If you manage ACLs via GitOps (e.g., tailscale/gitops-acl-action), both tags must be defined in tagOwners — Tailscale rejects references to undefined tags.

How the tags work:

  • tag:server — assigned to the host machine (or sidecar auth key). Identifies who can advertise services.
  • tag:container — default tag DockTail assigns to services. Identifies what is being advertised.

Approve Services

The first time a new service is advertised, it must be manually approved:

  1. Go to the Services tab
  2. Find the newly advertised service
  3. Approve it to allow traffic

After the initial approval, the service works automatically on subsequent container restarts. With OAuth or API key, service definitions are auto-created, but the first approval may still be required depending on your ACL configuration.

Labeling Containers

DockTail watches for containers with docktail.* labels and automatically advertises them as Tailscale services. Each labeled container becomes its own independent service.

Direct Container IP Proxying 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"

Container IPs are automatically detected and updated on restart. Set docktail.service.direct=false to use published port bindings instead.

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
docktail.service.network No bridge Docker network for container IP
docktail.service.protocol No Smart* Container protocol
docktail.service.service-port No Smart** Port Tailscale listens on
docktail.service.service-protocol No Smart*** Tailscale-facing protocol
docktail.tags No tag:container Comma-separated ACL tags

Smart Defaults:

  • * protocol: https if container port is 443, otherwise http
  • ** service-port: 443 if service-protocol is https, otherwise 80
  • *** service-protocol: https if service-port is 443, matches protocol for TCP, otherwise http

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

Use numbered labels (docktail.service.N.*) to expose multiple Tailscale services from a single container. Useful for VPN gateway containers where multiple apps route through one container.

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, 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"

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"

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 (enables auto-service-creation)
TAILSCALE_OAUTH_CLIENT_SECRET - OAuth Client Secret
TAILSCALE_API_KEY - API Key (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 HTTP
  • https — Layer 7 HTTPS (auto TLS)
  • tcp — Layer 4 TCP
  • tls-terminated-tcp — Layer 4 with TLS termination

Container-facing (protocol)

  • http — HTTP backend
  • https — HTTPS with valid certificate
  • https+insecure — HTTPS with self-signed certificate
  • tcp — TCP backend
  • tls-terminated-tcp — TCP with TLS termination

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 │
               └─────────────────────┘
  1. Container Discovery — Monitors Docker events for container start/stop
  2. Label Parsing — Extracts service configuration from container labels
  3. IP Detection — Gets container IP from Docker network settings (default: bridge)
  4. Config Generation — Creates Tailscale service config proxying to container IP
  5. Service Advertisement — Executes Tailscale CLI to advertise services
  6. Control Plane Sync — If OAuth/API key configured, creates service definitions via API
  7. 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