Skip to content

Reach a tailnet

This guide puts xixo 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. xixo 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 xixo 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 xixo and worker in its network namespace. Both reach the tailnet through it, and both share its socket so xixo can ask tailscaled for its status.

x-xixo: &xixo
environment:
XIXO_TAILSCALE_SOCKET: /var/run/tailscale/tailscaled.sock
volumes:
- tailscale-socket:/var/run/tailscale
services:
tailscale:
image: tailscale/tailscale:stable
hostname: xixo
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
xixo:
<<: *xixo
network_mode: service:tailscale
worker:
<<: *xixo
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 xixo was on under tailscale instead, and point the reverse proxy at tailscale, which now answers for xixo on port 80.

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

With XIXO_TAILSCALE_SOCKET set, xixo 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.