Skip to content
tslink

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