How I Use Tailscale

Users tailnet · people and devices
laptops · phones · the odd server
People and devices

Every app has a tailnet hostname here, and every one of them resolves to the edge. Nothing on this tailnet can see a backend directly, because no backend is on it.

Flyscale edge · the only node on both
tailscaled · the node's own · users tailnet
Traefik · routes by hostname
Flyscale edge

Terminates TLS and looks the hostname up in the configuration ConfigFS delivers. The backend it finds is a VIP on the other tailnet, dialled through a tsnet node embedded in Traefik. Nothing flows the other way.

tsnet · embedded in Traefik · services tailnet · tag:infra
Services tailnet · no people on it
Tailscale Service · svc:name
A VIP per app

A stable address the edge dials, created by Pulumi with the app's tags. Grants name the service, so the edge may reach it and nothing else may. Whichever pods are alive answer for it.

ephemeral node · tag:injected
Replica one

A tailscaled sidecar tsinject injected, joined with the workload's own OAuth client. It advertises the VIP with tailscale serve and leaves the tailnet when the pod dies.

ephemeral node · tag:injected
Replica two

Same identity, same tags: all replicas of a Deployment share one OAuth client. A rollout is nodes leaving and arriving behind an address that never changes.

Nothing on the users tailnet can see a backend, and nothing on the services tailnet knows a person exists. The edge is the only thing that is on both.

How a workload joins

what you write · a label and an annotation
The Deployment

inject-tailscale: "true", and tailscale-tags: plex, media. Nothing else changes: no sidecar in the manifest, no Secret to make, no tailnet to know about.

tsinject · one operator, three loops
01 · admission webhook · no side effects
Inject the sidecar

Adds the tailscaled container, its volumes and its environment from the annotations, and stamps the pod with its identity: the shared name, else the Deployment's. It calls nothing. If the credential already exists the pod schedules at once; the first pod of a workload is admitted behind a scheduling gate and sits visibly SchedulingGated.

02 · pod gate controller
Materialise what the annotations ask for

A TailscaleCredential named for the identity and owned by the Deployment, the serve and config ConfigMaps, and RBAC for the tailscaled state Secret. Then it waits for Ready.

03 · credential controller
Mint the OAuth client

One per workload, carrying the tags plus tag:injected, minted in whichever tailnet the annotation names. Delivered as a Secret, tags updated in place when they drift, revoked by a finalizer when the credential goes.

Tailscale API · the operator's own client, one per tailnet
The pod, as it runs
yours · untouched
Workload container

Shares the pod's network namespace, so the tailnet is just another interface it can listen on.

injected · tailscaled
Tailscale sidecar

Joins with the Secret as its auth key and advertises the tags. Ephemeral if asked; kernel networking even under Kata, through a tun device the runtime provides.

tailnet · a node with those tags
Two lines of YAML in, a tagged node out. Nothing in the flow blocks on the Tailscale API: the first pod waits behind a visible gate, every pod after it does not wait at all.
DF
Dave Finster
Platform engineering, storage, and the occasional musing.
More writing