Convention — tailnet service exposure (dedicated node per service)

Standing rule (user preference, 2026-08-08): every self-hosted web service gets its own dedicated Tailscale node + hostname (its own tag:telep device), NOT a shared tailscale serve port on the telep-mainframe node. So a service lives at https://<service>.taild4189d.ts.net, not https://telep-mainframe.taild4189d.ts.net:<port>.

Why

  • Clean, memorable per-service hostnames (pdf.taild4189d.ts.net, bambuddy.taild4189d.ts.net) instead of port-on-shared-host.
  • Independent ACLs / sharing / node lifecycle per service.
  • Matches the existing knowledgebase and home-portal deployments.

Supersedes the older “simple same-node tailscale serve --https=<port>” pattern (frigate :443, top4 :8443, filestash :8445) for new services. Existing shared-serve services can stay as-is; new ones get their own node.

The pattern (second tailscaled per identity)

  1. Bind the app to loopback only (127.0.0.1:<port>) — never exposed on the LAN.
  2. Run a dedicated tailscaled with userspace networking, its own socket and state:
    /usr/sbin/tailscaled \
      --tun=userspace-networking \
      --statedir=/var/lib/tailscale-<service> \
      --socket=/run/tailscale-<service>/tailscaled.sock \
      --port=0
    
    as a systemd unit tailscaled-<service>.service (model: tailscaled-knowledgebase.service, tailscaled-home.service).
  3. Authenticate the node once (interactive login URL, or a reusable auth key):
    tailscale --socket=/run/tailscale-<service>/tailscaled.sock up --hostname=<service>
    
  4. Serve the loopback app as HTTPS on that node:
    tailscale --socket=/run/tailscale-<service>/tailscaled.sock serve --bg --https=443 http://127.0.0.1:<port>
    
    https://<service>.taild4189d.ts.net.
  5. Add a tile to the home portal (/home/levander/home-portal/index.html).

The gotcha that cost real time

Use --statedir=<dir>, NOT --state=<file>. With --state=, serve --https fails with “no TailscaleVarRoot” and TLS returns 000. Full write-up: 2026-07-24-knowledgebase §“The two that cost thought (separate-node + TLS)“.

Auth: always a TAGGED node (tag:telep)

Service nodes join tagged (tag:telep) — owned by the tailnet, non-expiring, ACL-governed — never as a personal-user device. The reliable way to tag a node is a reusable auth key minted WITH tag:telep at login.tailscale.com/admin/settings/keys (Tags: tag:telep), passed as --authkey=<key>.

tailscale --socket=/run/tailscale-<service>/tailscaled.sock up \
  --hostname=<service> --authkey=<tag:telep key>

Let the tag come from the key

Do NOT also pass --advertise-tags=tag:telep — that validates against the authenticating user’s tagOwners and can be rejected. The node inherits the tag from the key. (Same gotcha bit the babylon sidecar — see babylon-deploy-notes / gotcha #32.)

Observed 2026-08-11 — --advertise-tags did work for the ruview node

tailscale up --hostname=ruview --advertise-tags=tag:telep succeeded and the node came up tags: ['tag:telep'], keyexpiry: None, owned by the tag — see 2026-08-11-ruview-esp32-csi-real-hardware. This does not overturn the rule above: --advertise-tags validates against the authenticating user’s tagOwners, and it passed only because that user owns tag:telep. The auth-key route is still preferred because it doesn’t depend on who authenticates.

Generating the key requires the Tailscale admin console (user action) — a subagent cannot mint it. Interactive tailscale up login would attach the node to a user, not a tag, so it’s not used for services.

Exception — host-network services (e.g. Bambuddy)

Services that must run network_mode: host (Bambuddy — SSDP discovery + virtual-printer ports) are awkward to put behind a userspace-networking dedicated node, because they bind real host ports and advertise the host’s tailnet IP for their virtual printers. For those: the web UI can still get its own node (serve the loopback UI port through a dedicated tailscaled), but any device-advertised IP (virtual printers) will use the host node’s IP. Decide per service; note the split in that service’s plan.