Skip to content

Reach a tailnet

This guide puts uris on a Tailscale network, including one coordinated by Headscale, so it can attach storage, mail, and other services on machines that have no public address. uris joins the network as its own node through a tailscale container. Each resource on the network names the tailnet resource as the one it is reached through. See reaching through another resource.

You need a running uris from self-hosting, and an auth key for the network. On Headscale, headscale preauthkeys create --user <user> makes one.

Add a tailscale service to the Compose project, and run uris and worker in its network namespace. Both reach the tailnet through it, and both share its socket so uris can ask tailscaled for its status.

x-uris: &uris
environment:
URIS_TAILSCALE_SOCKET: /var/run/tailscale/tailscaled.sock
volumes:
- tailscale-socket:/var/run/tailscale
services:
tailscale:
image: tailscale/tailscale:stable
hostname: uris
environment:
TS_AUTHKEY: ...
TS_STATE_DIR: /var/lib/tailscale
TS_SOCKET: /var/run/tailscale/tailscaled.sock
TS_USERSPACE: "false"
TS_EXTRA_ARGS: --login-server=https://headscale.example.com
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- tailscale:/var/lib/tailscale
- tailscale-socket:/var/run/tailscale
uris:
<<: *uris
network_mode: service:tailscale
worker:
<<: *uris
network_mode: service:tailscale
volumes:
tailscale:
tailscale-socket:

Leave TS_EXTRA_ARGS off for Tailscale’s own coordination server. The auth key is used once. The node’s identity is kept in the tailscale volume after that.

A container in another service’s network namespace has no networks or ports of its own. List the networks uris was on under tailscale instead, and point the reverse proxy at tailscale, which now answers for uris on port 80.

uris reaches every node the network lets it reach. Tag its node, and allow the tag only the services uris should attach, in the tailnet’s access policy. A uris that is compromised can then reach those services and nothing else on the network.

With URIS_TAILSCALE_SOCKET set, uris declares a resource named tailnet in every tenant when it boots, beside the resources in config/resources.yml. It is never attached by hand. Its check asks tailscaled for its status, and passes when tailscaled reports Running.

Open Settings → Resources in the app. tailnet is under Networks. A failed check says what tailscaled reported, such as tailscaled is NeedsLogin.

Attach the resource as usual, give its address on the tailnet, and set Reached through to tailnet. Through GraphQL, pass via: "tailnet" to attachResource. Name the node by its tailnet address, such as http://100.64.0.5:8080. The container resolves names with its own resolver, which does not know MagicDNS names.

A resource reached through tailnet connects only to addresses on the tailnet. An address elsewhere fails with is not an address its transport reaches. When the tailnet is down, the resource’s check fails with <key> is reached through tailnet, which is down.

To move an existing resource onto the tailnet, change it and set Reached through, or pass via to updateResource. An empty via reaches it directly again.