System Architecture

Tailnet Addressing for Lab Services

Lab services are addressed by their Tailscale IP, not by a 192.168.x LAN address. The tailnet address is the same whether the client is on the lab LAN or remote, and it does not change when a DHCP lease moves. The docs previously recorded gpu-node-3 as 192.168.1.78 while the router had already moved it to 192.168.1.185, which is the failure mode this rule exists to prevent.

ServiceTailnet addressNode
RustFS S3 APIhttp://100.118.64.53:9000truenas-scale
RustFS console UIhttp://100.118.64.53:9001truenas-scale
DuckLake PostgreSQL100.118.64.53:5432truenas-scale
Ray head dashboardhttp://100.85.176.38:8265gpu-node-3

The TrueNAS instance is a VM on the samos host, and both are tailnet members. The services are published by the VM, so truenas-scale is the address to use; samos (100.69.41.13) exposes only SSH and is not a service endpoint.

Coexistence with Cloudflare WARP

Development machines run WARP in WarpWithDnsOverHttps mode alongside Tailscale. The two do not conflict for routing, because the WARP split-tunnel policy is in Exclude mode and already excludes both the tailnet CGNAT range 100.64.0.0/10 and 192.168.0.0/16. Traffic to a tailnet address therefore leaves via tailscale0 rather than the WARP tunnel. Confirm on any host with:

ip route get 100.118.64.53      # expect: dev tailscale0
warp-cli --accept-tos settings  # expect 100.64.0.0/10 in the exclude list

Docker containers inherit this. A container on a default bridge network reaches 100.118.64.53:9000 and :5432 without extra configuration, because WARP also excludes 172.16.0.0/12 and the host forwards to tailscale0.

MagicDNS

MagicDNS is enabled tailnet-wide with the suffix tail9b840.ts.net, but names do not resolve on a WARP-managed machine, and they do not resolve inside containers at all. WARP owns the system resolver and forwards queries to Cloudflare Gateway, which has no knowledge of ts.net. Tailscale is consequently configured with accept-dns=false so the two do not fight over /etc/resolv.conf.

This is why configuration files carry tailnet IPs rather than names such as truenas-scale.tail9b840.ts.net. Do not switch them to names before the following is in place on every consumer, containers included.

To make names resolve, add a Local Domain Fallback entry in the Cloudflare Zero Trust dashboard under Settings, then WARP, then Local Domain Fallback: domain ts.net, resolver 100.100.100.100. The WARP settings on these machines are delivered by network policy, so the change belongs in the dashboard rather than in warp-cli. Then enable the Tailscale resolver on each host:

tailscale set --accept-dns=true

Verify from the host and from a container before relying on it, since the container path is the one that breaks:

getent hosts truenas-scale.tail9b840.ts.net
docker run --rm busybox nslookup truenas-scale.tail9b840.ts.net

Short names are not a substitute. The lab router serves an internal search domain, so a bare gpu-node-3 resolves to a LAN address rather than a tailnet one.

Subnet routing

No node advertises 192.168.1.0/24, so LAN addresses are unreachable from off-LAN. Adding a subnet router would make every 192.168.x address work remotely without editing any configuration, at the cost of depending on one advertising node and on route approval in the Tailscale admin console. That option remains open, but it is not what the configuration in this repository relies on.

Verifying an endpoint

tailscale status                                   # peer online
nc -z -w3 100.118.64.53 9000                       # port reachable from the host
docker run --rm busybox nc -z -w3 100.118.64.53 9000   # and from a container

An unauthenticated GET against the S3 API returns 403 Forbidden. That response confirms RustFS is answering rather than a connection silently succeeding against something else.

On this page