(Fire)fox + Tail(scale) = Foxtail
A Firefox browser that lives on your Tailscale network — accessible from any device on your tailnet, directly in a browser tab.
Foxtail runs Firefox inside Docker and joins it to your tailnet as a named node. From any device on your tailnet you open a tab, and you're in. No port forwarding. No public exposure. No VPN client on the remote device.
Some things this is good for:
- Browse the web through your home or office network from anywhere
- Reach internal services on your tailnet (dashboards, dev servers, NAS) without exposing them
- Give a teammate a shared browser pointed at your internal resources
- Test how a site behaves from a specific network's perspective
Userspace networking — Tailscale runs entirely in userspace inside the container. No /dev/net/tun, no host networking, no special kernel modules. It works anywhere Docker runs.
MagicDNS — the browser resolves .ts.net hostnames out of the box. Internal services are reachable by name, not IP.
Auth keys — the auth key is injected as an environment variable at runtime. You can pass it directly, fetch it from any password manager, or use the built-in 1Password CLI integration. It never touches disk or an image layer.
Ephemeral nodes — use an ephemeral auth key and the device disappears from your tailnet automatically when the container stops. No stale devices to clean up.
- Docker Desktop installed and running
- Task installed:
brew install go-task
- Tailscale account with an auth key (generate one at https://login.tailscale.com/admin/settings/keys)
Generate a key at https://login.tailscale.com/admin/settings/keys, then choose how to supply it:
Option A — direct (any password manager or shell)
# In your .env file:
TS_AUTHKEY=tskey-auth-...
# Or inline at runtime:
TS_AUTHKEY='tskey-auth-...' task start
# Or export from your password manager first, e.g. Bitwarden:
export TS_AUTHKEY=$(bw get password "Tailscale Foxtail key")
task startOption B — 1Password CLI (automatic fetch)
Install and sign in to the 1Password CLI, then store the key in 1Password:
brew install --cask 1password-cli
op signin- Vault:
Private(or setOP_VAULT) - Item name:
Foxtail(or setOP_ITEM_NAME) - Field name:
auth_key(or setOP_FIELD_NAME)
Leave TS_AUTHKEY unset and start.sh will fetch it automatically.
Key options to consider:
- Reusable — lets you recreate the container without generating a new key each time
- Ephemeral — the tailnet device is removed automatically when the container stops
cp .env.example .env
# Edit .env and set VNC_PW, VNC_VIEW_ONLY_PW, and optionally TS_AUTHKEYtask starthttps://localhost:6901
- Username:
kasm_user - Password: (your
VNC_PWfrom.env, default:foxtail)
Run task with no arguments to see all available commands.
| Command | Description |
|---|---|
task start |
Fetch auth key from 1Password and start |
task stop |
Stop and remove the container |
task restart |
Stop and restart |
task build |
Rebuild the Docker image from scratch |
task logs |
Stream container logs |
task status |
Check running container processes |
task debug |
Start with verbose logging |
task test-auth |
Validate 1Password CLI and auth key |
task tailscale-status |
Show Tailscale connection inside the container |
TAILSCALE_HOSTNAME=my-browser task startTS_AUTHKEY='tskey-auth-...' task startOP_VAULT="Personal" OP_ITEM_NAME="Tailscale Keys" task startTAILSCALE_TAGS="tag:browser,tag:docker" task start┌─────────────────────────────────────┐
│ Docker Container │
│ │
│ ┌──────────────────────────────┐ │
│ │ Tailscale (userspace mode) │ │
│ │ - No /dev/net/tun required │ │
│ │ - Connected to your tailnet │ │
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ Xvfb (Virtual Display) │ │
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ Firefox │ │
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ x11vnc → noVNC │ │
│ │ (Web-based VNC interface) │ │
│ └──────────────────────────────┘ │
│ │
└─────────────────────────────────────┘
│
│ Port 6901
▼
Your Web Browser
(https://localhost:6901)
Built on kasmweb/firefox. Supervisor manages all four processes inside the container. The entrypoint starts Tailscale as root, connects to your tailnet, then drops to an unprivileged user before handing off to Kasm.
task test-auth
# Or check manually
op whoami
op read "op://Private/Foxtail/auth_key"task logs
task tailscale-status- Verify the container is running:
docker-compose ps - Check port 6901 isn't in use:
lsof -i :6901 - Check noVNC logs:
task logsand search fornovnc
- The Tailscale auth key is injected at runtime as an environment variable — never stored in an image layer
- VNC credentials are set via
.envwhich is gitignored - The container requires
NET_ADMINandNET_RAWcapabilities for Tailscale userspace networking - Tailscale state persists in a Docker volume — delete it with
docker volume rm foxtail_tailscale-stateto force re-authentication
MIT