Getting started
You’ll install the plugin, run nginx as a node of its own in your tailnet, and open it from your laptop at
https://web.<tailnet>.ts.net, with a valid certificate.
What you need
- A Linux host with Docker, or a Mac with OrbStack or Docker Desktop.
- A tailnet with HTTPS enabled, in the DNS settings of the admin console.
- An auth key that’s ephemeral, reusable and pre-approved, from Settings → Keys in the admin console. Ephemeral means the node goes away when its container does, so trying things out leaves nothing behind.
- A second device in the same tailnet to test from, such as your laptop.
1. Install the plugin
Install the plugin from the GitHub container registry. On an ARM host or an Apple silicon Mac, replace amd64 with
arm64:
docker plugin install --alias tslink --grant-all-permissions \
ghcr.io/matchory/tslink:latest-amd64
--grant-all-permissions accepts everything the plugin asks for: the host’s network, CAP_NET_ADMIN and
CAP_SYS_ADMIN, access to all devices, and mounts of the host’s /var/lib, /run and Docker socket. It needs them
to create network interfaces and firewall rules on the host, to enter containers’ network namespaces, to keep its
data directory, and to read containers’ labels. docker plugin install lists them before it asks, if you leave the
flag out. Check that Docker enabled it:
docker plugin ls
The list shows tslink:latest, with ENABLED set to true.
2. Create a network
A tslink network is where the credential lives: every container you attach to it joins your tailnet with it.
docker network create --driver tslink:latest \
--opt tslink.authkey=tskey-auth-… \
--opt tslink.ephemeral=true \
my-tailnet
tslink.ephemeral=true tells tslink that the key creates ephemeral nodes, so it logs the node out as soon as the
container stops instead of leaving it offline in your tailnet.
3. Run a container
Start nginx on the network. The two labels name the node web, and serve nginx’s port 80 as HTTPS on port 443:
docker run -d --name web --network my-tailnet \
--label tslink.hostname=web \
--label tslink.serve.443=https:80 \
nginx:alpine
After a few seconds, web appears in the Machines list of the admin console: a node of its own, with its own tailnet
address, not the host’s.
4. Open it from your tailnet
On your second device, replace <tailnet> with your tailnet’s name, such as tail1234, and open the page:
curl https://web.<tailnet>.ts.net
You get nginx’s welcome page. The first request can take a few seconds, while Tailscale gets a certificate for the name from Let’s Encrypt.
5. Clean up
Remove the container and the network:
docker rm -f web
docker network rm my-tailnet
tslink logs the node out, and it disappears from the admin console.
What just happened
When the container started, tslink ran a tailscaled inside the container’s network namespace, so the container itself became a node in your tailnet, with its own name and address. Tailscale Serve, inside that same node, answered HTTPS on port 443 and forwarded each request to nginx on port 80. The host’s own Tailscale, if it has one, wasn’t involved. Architecture explains the details.
Next steps
- Expose a service: put several containers behind one Tailscale Service.
- Deploy on Swarm: run stacks, each with its own tags.
- Provision a host: get a host ready for production.
- Container labels: everything a container can configure.