A Docker network plugin that gives each container its own Tailscale identity. Containers appear as individual nodes in your tailnet with their own Tailscale IPs.
When you create a Docker network with this plugin and run containers on it:
- Each container gets its own Tailscale node identity
- The container appears in your Tailscale admin console as a separate device
- The container can reach other nodes in your tailnet
- The container also has internet access via NAT
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Docker Host β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Container (Tailscale IP: 100.x.y.z) β β
β β β β
β β App βββΊ tailscaled βββΊ Tailnet β β
β β β β
β β eth0 βββΊ veth βββΊ NAT βββΊ Internet β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββ β
β β Plugin β β
β β β’ Manages tailscaled β β
β β β’ Creates veth pairs β β
β β β’ Sets up NAT β β
β ββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Docker 19.03+ or OrbStack
- A Tailscale auth key from https://login.tailscale.com/admin/settings/keys
# Create the data directory (required)
sudo mkdir -p /var/lib/docker-plugins/tailscaleDocker plugins require architecture-specific tags:
# For amd64 (Intel/AMD, most cloud VMs)
docker plugin install ghcr.io/aaomidi/tslink:latest-amd64
# For arm64 (Apple Silicon, AWS Graviton, Raspberry Pi)
docker plugin install ghcr.io/aaomidi/tslink:latest-arm64
# Or install a specific version
docker plugin install ghcr.io/aaomidi/tslink:v0.0.1-amd64
# Or follow main branch (latest development)
docker plugin install ghcr.io/aaomidi/tslink:main-amd64
# The plugin will be enabled automatically
docker plugin ls# With auth key in command (ephemeral nodes by default)
# Replace :latest-amd64 with :latest-arm64 for ARM systems
docker network create \
--driver ghcr.io/aaomidi/tslink:latest-amd64 \
--opt tslink.authkey=tskey-auth-xxxxx \
my-tailnet
# Or set the auth key globally when installing the plugin
docker plugin set ghcr.io/aaomidi/tslink:latest-amd64 TS_AUTHKEY=tskey-auth-xxxxx
docker network create --driver ghcr.io/aaomidi/tslink:latest-amd64 my-tailnet# Run a container - it automatically gets a Tailscale IP
docker run --rm --network my-tailnet alpine sh
# Inside the container:
# - Check your Tailscale IP in the Tailscale admin console
# - Ping other tailnet nodes
# - Access the internet normally# Run nginx, accessible from your tailnet
docker run -d --name web --network my-tailnet nginx
# From any device on your tailnet, access via the node's Tailscale hostname
curl http://web.your-tailnet.ts.net# Use :latest-amd64 or :latest-arm64 depending on your system
networks:
tailnet:
driver: ghcr.io/aaomidi/tslink:latest-amd64
driver_opts:
tslink.authkey: ${TS_AUTHKEY}
services:
api:
image: my-api
networks:
- tailnet
worker:
image: my-worker
networks:
- tailnet| Option | Description | Default |
|---|---|---|
tslink.authkey |
Tailscale auth key | Required (or set via plugin env) |
Configure per-container Tailscale settings using labels:
| Label | Description | Example |
|---|---|---|
tslink.hostname |
Tailscale hostname | tslink.hostname=my-api |
tslink.tags |
ACL tags (comma-separated) | tslink.tags=tag:server,tag:prod |
tslink.serve.<port> |
Expose port via Tailscale Serve | tslink.serve.443=https:8080 |
tslink.service |
Register as Tailscale Service backend | tslink.service=svc:my-api |
tslink.direct |
Enable direct machine serve | tslink.direct=true (default) |
# Example: Container with custom hostname and tags
docker run -d --network my-tailnet \
--label tslink.hostname=my-api \
--label tslink.tags=tag:server \
nginx
# Example: Expose HTTPS on port 443, forwarding to container port 8080
docker run -d --network my-tailnet \
--label tslink.hostname=web \
--label tslink.serve.443=https:8080 \
my-web-appInstead of passing the auth key with each network, set it as a plugin environment variable:
# Set default auth key (use :latest-arm64 for ARM systems)
docker plugin disable ghcr.io/aaomidi/tslink:latest-amd64
docker plugin set ghcr.io/aaomidi/tslink:latest-amd64 TS_AUTHKEY=tskey-auth-xxxxx
docker plugin enable ghcr.io/aaomidi/tslink:latest-amd64
# Now create networks without specifying the auth key
docker network create --driver ghcr.io/aaomidi/tslink:latest-amd64 my-tailnet| Key Type | Behavior |
|---|---|
| Ephemeral key | Nodes are automatically removed when the container stops |
| Reusable key | Nodes persist in your tailnet after container stops |
| Pre-approved key | Nodes don't require manual approval |
For most use cases, use an ephemeral, reusable, pre-approved key.
# Remove network (stops all containers using it)
docker network rm my-tailnet
# Ephemeral nodes are automatically removed from Tailscale
# Non-ephemeral nodes remain in your admin console and need manual removalEach container's Tailscale daemon writes logs to the host:
# List all endpoint logs
docker run --rm -v /var/lib/docker-plugins/tailscale:/data alpine \
sh -c 'for d in /data/by-hostname/*/; do echo "=== $d ==="; cat "$d/debug.log" 2>/dev/null | tail -20; done'
# Check plugin logs (Linux)
journalctl -u docker -f | grep -i tailscale
# Check plugin logs (macOS/OrbStack)
docker run --rm -it --privileged --pid=host alpine nsenter -t 1 -m -u -n -i sh
# Then: journalctl -u docker -f- Check debug logs (above) for errors
- Verify auth key is valid and not expired
- Ensure the plugin is enabled:
docker plugin ls
- Auth key may be expired - create a new one
- Container may have stopped - check
docker ps - Network issues - check debug logs for connection errors
Check debug logs for:
Switching ipn state Starting -> Running= connected successfullySwitching ipn state NeedsLogin= auth key issuenetwork is unreachable= veth setup failed
Containers with the same hostname reuse the same Tailscale identity. If you need a fresh identity:
# Clear state for a specific hostname
docker run --rm -v /var/lib/docker-plugins/tailscale:/data alpine \
rm -rf /data/by-hostname/<hostname>- macOS/Windows: Only works with Docker in a Linux VM (OrbStack, Docker Desktop)
- MagicDNS in container: Containers can reach tailnet by IP; MagicDNS resolution requires additional DNS config
See CLAUDE.md for development setup.
MIT