Optional embedded tailnet

The default deployment is a container behind a reverse proxy; an embedded tailnet endpoint is optional and experimental.

Context

The vision described the binary joining a tailnet directly, so no inbound port would be needed and a tailnet name would address it. A Rust implementation of Tailscale, tailscale-rs, now exists and is officially maintained, with a userspace network stack and an in-process device API, which would keep the single-binary promise. It is pre-1.0, and it does not yet provide MagicDNS, HTTPS certificate issuance, or working NAT traversal, so a device is reachable by tailnet IP and traffic may relay through public relays.

Decision

Two deployment modes. The default is the plain container behind a reverse proxy, which owns TLS. The optional embedded tailnet endpoint uses tailscale-rs: the process joins the tailnet in userspace and listens there, addressed by tailnet IP, carrying plain HTTP inside the tailnet's own tunnel. Embedded mode is experimental until NAT traversal, name resolution, and certificates are available upstream.

Consequences

  • The default path needs no Tailscale code and no open ports beyond the proxy.
  • Embedded mode keeps one binary and no sidecar, at the cost of MagicDNS naming and relay latency.
  • The tailnet integration is a build concern isolated behind one seam, so the plain and embedded builds share the rest of the code.