Skip to content

Servers and containers

Making a server, a VM or a container a device your others reach, with nobody there. Servers need only alto (the command and its server); the app is for the devices people sit at.

Terminal window
alto setup --trust "*@example.com"

It:

  • turns remote access on (over Tailscale only);
  • lets in, without pairing, what each --trust names: a Tailscale tag (tag:ci), a person (sam@example.com) or a domain (*@example.com). A tagged server belongs to nobody’s account, so it needs one of these, or pairing, before anyone reaches it;
  • installs the login service, so the server starts with the device (systemd user unit, LaunchAgent, Run key);
  • on Linux, turns on lingering, so that service runs with nobody logged in. Turning it on for yourself may need root: setup says so, with the command (sudo loginctl enable-linger USER);
  • waits until the server listens on Tailscale, and says how other devices reach it.

Run it again whenever: what’s already so is reported and left alone, and nothing is restarted. Other ways to run it:

  • --no-autostart: start the server now, but not again after a restart; nothing is installed.
  • --foreground: after setting up, keep running the server as this process, in the foreground: a container’s main process. Nothing is installed.

On the devices that should reach it, if it’s tagged: alto devices trust tag:ci (or Settings → Devices shows it once trusted). Then alto --on NAME new …, or New tab on NAME in the app.

See Remote access with Tailscale for how access works.

Tailscale does the networking and says who’s who. For a server, make an auth key in the admin console (Settings → Keys) with the server’s tag, pre-approved, and, for machines that come and go, ephemeral. The tag needs an owner in the access policy:

"tagOwners": { "tag:agents": ["autogroup:admin"] },

and, if the policy doesn’t already allow everything, a rule letting people reach it on Alto’s port:

{ "src": ["autogroup:member"], "dst": ["tag:agents"], "ip": ["tcp:7655"] }

Until there are packages (planned), put a Linux build of alto where the VM can fetch it (ALTO_URL below). The user agent runs the agents; its server starts at boot through its systemd user service.

#cloud-config
users:
- name: agent
shell: /bin/bash
runcmd:
- curl -fsSL https://tailscale.com/install.sh | sh
- tailscale up --authkey=TS_AUTHKEY --advertise-tags=tag:agents --hostname=gpu-1
- curl -fsSL -o /usr/local/bin/alto ALTO_URL && chmod 755 /usr/local/bin/alto
- loginctl enable-linger agent
- su - agent -c 'XDG_RUNTIME_DIR=/run/user/$(id -u) alto setup --trust "*@example.com"'

XDG_RUNTIME_DIR lets su reach the user’s systemd, which lingering started. Checked 2026-11-08 in a container running systemd: the service installed, and after a restart with nobody logged in the server was running again, with the session from before shown as ended, its screen saved.

The server is the main process (--foreground), so the container lives as long as it does. Tailscale runs inside (it needs the TUN device: --cap-add=NET_ADMIN --device /dev/net/tun), and the server listens on its address.

FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates iptables \
&& curl -fsSL https://tailscale.com/install.sh | sh && rm -rf /var/lib/apt/lists/*
COPY alto /usr/local/bin/alto
COPY start.sh /start.sh
CMD ["sh", "/start.sh"]
Terminal window
# start.sh: Tailscale with the key given at run time, then Alto.
tailscaled --state=mem: &
tailscale up --authkey="$TS_AUTHKEY" --advertise-tags=tag:agents --hostname="${NAME:-agent-box}"
exec alto setup --trust "*@example.com" --foreground
Terminal window
docker run -d --cap-add=NET_ADMIN --device /dev/net/tun -e TS_AUTHKEY=tskey-auth-… my-agent-box

With --state=mem: and an ephemeral key, the device leaves the tailnet when the container stops. Sessions run inside the container, so they end with it; keep their work in a mounted folder. Checked 2026-11-08: such a container on a real tailnet was reached from a Mac trusting its tag, and setup --foreground runs the server as process 1.

Replace the alto file; the server notices within seconds and hands over to the new build, its sessions running on. In a container, rebuild the image instead: replacing the file inside works too, but is lost with the container.