Drill · platform · difficulty ●○○ · 5 min

The Docker node registers as a brand new machine on every restart

"Our admin console has 14 copies of the same container node, and this morning the auth key stopped working and the service went down."

Which failure areas the drills cover connectivity 3 drills identity 3 drills policy 2 drills dns 2 drills routing 3 drills platform 2 drills bar length is the count, not a difficulty or importance score

The ticket

The Customer runs a web service with a tailscale/tailscale sidecar container so internal tools can reach it over the tailnet (kb-docker). It works, mostly. But the machine list has grown a graveyard: web-1, web-1-1, web-1-2, on up to web-1-13, all but one permanently offline. Someone has been deleting them by hand every week. This morning the container restarted during a host patch, failed to authenticate, and the service dropped off the tailnet entirely, which turned a cosmetic annoyance into an outage ticket.

“Every time the container restarts we get a new machine in the console. We have been cleaning them up by hand, but today it could not log in at all and now nothing can reach the service.”

Evidence provided

The deployment command, from the Customer’s runbook:

docker run -d --name ts-sidecar \
  -e TS_AUTHKEY=tskey-auth-REDACTED \
  -e TS_HOSTNAME=web-1 \
  --cap-add=NET_ADMIN --device /dev/net/tun \
  tailscale/tailscale:stable

The machine list, trimmed:

web-1       100.64.0.51   linux   last seen Jun 30 (offline)
web-1-1     100.64.0.58   linux   last seen Jul 8  (offline)
web-1-2     100.64.0.64   linux   last seen Jul 15 (offline)
...
web-1-13    100.64.0.97   linux   last seen Aug 10 (offline)

Container logs from this morning’s failed restart:

boot: 2026/08/10 03:12:04 Starting tailscaled
boot: 2026/08/10 03:12:04 Waiting for tailscaled socket at /tmp/tailscaled.sock
boot: 2026/08/10 03:12:05 Running 'tailscale up'
backend error: invalid key: API key does not exist

The Running 'tailscale up' line on the fourteenth start of the same container is the whole case, if you know what it means. Unless TS_AUTH_ONCE is set, containerboot forcibly logs in every time the container starts, and this daemon has no stored login to make that a no-op.

Hypothesis tree

Duplicated machines with the same base hostname have three candidate explanations.

Hypothesis tree: container node multiplies on restartNew machine entry on every restartweb-1, web-1-1, ... web-1-13A. Same node flapping,console renaming itB. Auth key type forcesa fresh identity per loginC. Node state not persisted,identity lost with containerDiscriminator: entries havedistinct IPs and creation dates,so they are different nodesDiscriminator: key type controlsreuse and cleanup, not whetheran existing identity is keptDiscriminator: docker inspectshows no volume mount and noTS_STATE_DIR; state dies on stop

Investigation

  1. Read the machine list closely. Each web-1-N entry has its own tailnet IP and its own creation date, one per container restart. A single flapping node keeps one identity and one IP; these are fourteen different nodes. That rules out hypothesis A: the console is not renaming anything, it is deduplicating hostnames because a genuinely new machine keeps arriving with the name web-1 already taken.

  2. Inspect the container for persistent state.

    $ docker inspect ts-sidecar --format '{{json .Mounts}}'
    []

    No volumes at all, and the run command sets no TS_STATE_DIR. That combination is decisive. With neither TS_STATE_DIR nor a Kubernetes state Secret configured, containerboot starts the daemon with --state=mem: --statedir=/tmp, so tailscaled holds its state in memory and loses it when the process ends (src-containerboot). The documentation is blunt about the consequence: this directory “must persist across container restarts or your container will appear as a new node each time” (docs-docker-params).

  3. Confirm the mechanism in the logs. Running 'tailscale up' at boot, on the 14th start of the same container, with no stored login to short-circuit it. The daemon starts empty every time, so it registers as a new node every time. This rules out hypothesis B: the auth key is how a new node proves it may join (kb-auth-keys); it is not where an existing node’s identity lives. No key type would have preserved web-1.

  4. Audit the key itself for this morning’s outage. The key in the runbook was created 90 days ago. Auth keys expire after a user-specified duration between 1 and 90 days, and 90 is the maximum (kb-auth-keys). The key aged out overnight, the restart needed to re-register because of the missing state, and re-registration failed. Two findings, one root: if the node had kept its identity, this morning’s restart would not have needed an auth key at all.

Root cause

A Tailscale node’s identity is its node key and login state, which tailscaled keeps in its state directory. In the official container image that location is set with TS_STATE_DIR, which “specifies where tailscaled stores its state,” and it has to be backed by a persistent volume, because “the TS_STATE_DIR volume ensures the container keeps its identity across restarts” (docs-docker-params). This runbook set neither, so containerboot fell back to --state=mem: and the daemon kept its identity in memory only (src-containerboot). Every fresh start was therefore a fresh daemon with no identity: register with the control plane (Module 02), get a new node key and a new tailnet IP, collide with the old hostname, become web-1-N. The old entries never disappear because non-ephemeral nodes are expected to return; the control plane has no way to know they are corpses. The auth key burn is the same defect seen from the identity side (Module 04): every restart spends registration again, so key lifetime becomes service uptime. Platform lesson for Module 09: containers are the one platform where durable identity requires an explicit decision.

Fix and prevention

Immediate. Add the volume and state directory, per the Docker configuration parameters documentation (docs-docker-params):

docker run -d --name ts-sidecar \
  -v ts-state:/var/lib/tailscale \
  -e TS_STATE_DIR=/var/lib/tailscale \
  -e TS_AUTHKEY=tskey-auth-NEWKEY \
  -e TS_HOSTNAME=web-1 \
  --cap-add=NET_ADMIN --device /dev/net/tun \
  tailscale/tailscale:stable

Authenticate once with a fresh key, verify docker restart ts-sidecar brings back the same machine with the same IP and no new console entry, then delete the thirteen corpses.

Durable. Pick the right identity model per workload, deliberately:

The decision rule: if you would ever say “the same node came back,” you want persistent state. If every instance is legitimately a new node, you want ephemeral.

The handoff package

Not escalated (configuration defect), but as it would be filed:

The trap

The weak investigation treats the machine list as the problem and deletes the dead entries every week: tidy console, defect untouched, and a slow burn of auth keys until an expiry lands during an outage window, which is exactly what happened here. The opposite overcorrection is just as bad: reaching for an ephemeral key to “stop the clutter” on a node that should be stable, which makes the service surrender its identity and tailnet IP on every restart and silently breaks anything pinned to the old address. Both traps come from fixing the visible artifact instead of asking the mechanism question: where does this node keep who it is, and does that location survive a restart?

Sources

  1. Docker checked 2026-08-10
  2. Docker configuration parameters checked 2026-08-10
  3. Auth keys checked 2026-08-10
  4. Ephemeral nodes checked 2026-08-10
  5. tailscale/tailscale cmd/containerboot (tailscaled arguments) checked 2026-08-10

All drills