Read when:
- adding or debugging tailnet reachability for a lease;
- deciding whether a host is provider-owned or only network-reachable;
- changing SSH, VNC, or coordinator bootstrap behavior over a tailnet.
Tailscale is an optional reachability layer, not a provider. Providers still own the machines (Hetzner, AWS, Azure, Google Cloud, Proxmox, static SSH hosts, and the rest). Tailscale only changes which host Crabbox dials when it runs SSH-backed work against a lease.
- Managed Linux leases can join a tailnet at creation time with
--tailscale. Tailscale provisioning is offered by the providers that declare the capability: Hetzner, AWS, Azure, and Google Cloud. - Static (
provider=ssh) hosts are not joined by Crabbox; pointstatic.hostat a MagicDNS name or100.xaddress and assert reachability with--network tailscale. --tailscaleis rejected for non-Linux targets, for Blacksmith Testbox (Blacksmith owns connectivity), for static hosts, and for sandbox providers such as Sprites that expose SSH through their own proxy.
Create a managed Linux lease that joins the configured tailnet:
crabbox warmup --tailscale
crabbox run --tailscale -- pnpm test
crabbox run --tailscale --desktop --browser -- pnpm test:e2eChoose the connection path when acting on an existing lease (ssh, vnc,
screenshot, webvnc, status, inspect, and reused run --id leases):
crabbox ssh --id swift-crab --network auto
crabbox ssh --id swift-crab --network tailscale
crabbox vnc --id swift-crab --network tailscale --open
crabbox run --id swift-crab --network public -- pnpm test--network (config key network, default auto) selects how the SSH endpoint is
resolved:
auto: prefer the tailnet host when the lease carries Tailscale metadata and SSH is reachable there; otherwise fall back to the provider/public host.tailscale: require a tailnet host and fail clearly when this client cannot reach it over SSH.public: force the provider/public host (useful for debugging).
When auto falls back to the public host, Crabbox prints network fallback tailscale_unreachable and reports the selected path in ready/status output
(ready ssh=… network=public …) instead of silently switching.
network: auto
tailscale:
enabled: true
tags:
- tag:crabbox
hostnameTemplate: crabbox-{slug}
authKeyEnv: CRABBOX_TAILSCALE_AUTH_KEY
exitNode: build-host.example.ts.net
exitNodeAllowLanAccess: truetailscale.enabled (or --tailscale) requests a tailnet join for newly created
managed Linux leases. tailscale.network is an alias that sets the top-level
network mode used for target resolution on SSH-backed commands. Hostname templates
support {id}, {slug}, and {provider}; the rendered value is sanitized to a DNS
label. When enabled is set, tags must contain at least one valid tag: value and
hostnameTemplate must be non-empty.
Environment overrides:
CRABBOX_TAILSCALE=1
CRABBOX_NETWORK=auto|tailscale|public
CRABBOX_TAILSCALE_TAGS=tag:crabbox,tag:ci
CRABBOX_TAILSCALE_HOSTNAME_TEMPLATE=crabbox-{slug}
CRABBOX_TAILSCALE_AUTH_KEY_ENV=CRABBOX_TAILSCALE_AUTH_KEY
CRABBOX_TAILSCALE_AUTH_KEY=<direct-provider only>
CRABBOX_TAILSCALE_EXIT_NODE=build-host.example.ts.net
CRABBOX_TAILSCALE_EXIT_NODE_ALLOW_LAN_ACCESS=1
Flag equivalents: --tailscale, --network, --tailscale-tags,
--tailscale-hostname-template, --tailscale-auth-key-env, --tailscale-exit-node,
and --tailscale-exit-node-allow-lan-access.
In direct-provider mode the auth key is read from the variable named by
tailscale.authKeyEnv (default CRABBOX_TAILSCALE_AUTH_KEY). Managed VM
providers normally use a one-off key. Islo requires a reusable, ephemeral key
because its snapshot-safe memory-only node identity must re-enroll after daemon
loss. Brokered mode does not need a local key; the coordinator mints one per lease
(see below).
Exit-node egress is opt-in per lease. The lease routes its outbound internet through an approved tailnet exit node after it joins Tailscale:
crabbox warmup --tailscale --tailscale-exit-node build-host.example.ts.net --tailscale-exit-node-allow-lan-access
crabbox run --tailscale --tailscale-exit-node 100.100.100.100 -- curl -4 https://ifconfig.meexitNodeAllowLanAccess maps to Tailscale's LAN-access flag and requires exitNode.
The exit node must already advertise exit-node capability and be approved in the
Tailscale admin console, and the tailnet policy must grant the lease's tags (for
example tag:crabbox) access to autogroup:internet through exit nodes.
When an exit node is selected, network: auto prefers the tailnet host for bootstrap
because the provider/public SSH path can become asymmetric once the lease routes
outbound traffic through the exit node. Before sync or the remote command, Crabbox
verifies on the box that an exit node is selected in tailscale prefs and that the
node can reach the public internet. If that check fails, the run stops and reports the
exit-node egress failure (tailscale exit node … joined but remote internet egress failed), which usually means the exit node is not approved, the policy does not grant
autogroup:internet to the lease tag, or the exit-node machine is not forwarding.
When a lease is created through the coordinator, it mints a fresh auth key per lease using Tailscale OAuth. Secrets live in coordinator configuration:
CRABBOX_TAILSCALE_CLIENT_ID
CRABBOX_TAILSCALE_CLIENT_SECRET
CRABBOX_TAILSCALE_TAILNET optional, defaults to "-"
CRABBOX_TAILSCALE_TAGS default/allowed comma-separated tags (default tag:crabbox)
CRABBOX_TAILSCALE_ENABLED set 0 to force-disable, 1 to force-enable
CRABBOX_TAILSCALE_INSTALL_MODE package or pinned (default package)
CRABBOX_TAILSCALE_VERSION pinned static build version
CRABBOX_TAILSCALE_SHA256_AMD64 pinned amd64 archive checksum
CRABBOX_TAILSCALE_SHA256_ARM64 pinned arm64 archive checksum
The OAuth client's tags and each requested lease tag must also satisfy Tailscale's ownership rules:
- Single tag: give the OAuth client
tag:crabboxand setCRABBOX_TAILSCALE_TAGS=tag:crabbox. The tag sets match exactly. - Multi-tag exact match: an OAuth client with
tag:ci,tag:stagingcan mint a key requesting both tags together without extra ownership rules. - Multi-tag subsets: requesting only
tag:cifrom that two-tag client requirestag:cito own itself intagOwners; do the same for every subset tag. - Deployment-owner pattern: preferably give the OAuth client one narrow owner tag,
such as
tag:crabbox-deployer, and make that tag an owner of each workload tag that Crabbox may request.
Example deployment-owner policy:
{
"tagOwners": {
"tag:crabbox-deployer": ["autogroup:admin"],
"tag:ci": ["tag:crabbox-deployer"],
"tag:staging": ["tag:crabbox-deployer"]
}
}Then assign only tag:crabbox-deployer to the OAuth client and set
CRABBOX_TAILSCALE_TAGS=tag:ci,tag:staging. This keeps the client least-privilege
without requiring it to carry every workload tag. Do not broaden the OAuth client to
unrelated tags just to resolve an ownership error.
Flow:
- The CLI sends the requested Tailscale settings (enabled, tags, hostname, optional
exit node) in
CreateLease. - The coordinator validates the requested tags against
CRABBOX_TAILSCALE_TAGSand rejects any tag outside that set. - The coordinator exchanges the OAuth client for a token and mints a one-off auth key that is non-reusable, ephemeral, pre-approved, tagged, and expires in 10 minutes.
- The key is injected only into the runner's cloud-init user-data.
- The runner installs Tailscale, runs
tailscale upwith the requested hostname and tags, and writes non-secret metadata under/var/lib/crabbox(tailscale-ipv4,-hostname,-fqdn,-version,-device-id, and exit-node markers). - After SSH readiness, the CLI reads that metadata and posts it back to the coordinator so the lease record carries the tailnet address.
The auth key is never stored in lease records, provider labels, run logs, or local config. The short-lived key can still appear in user-data at the provider, so the Worker only mints one-off ephemeral keys — never long-lived reusable keys.
The default package mode configures Tailscale's signed stable APT repository,
verifies and scopes its pinned keyring with signed-by, installs the current
package, and records the client version. Set
CRABBOX_TAILSCALE_INSTALL_MODE=pinned to download a static
archive, verify the configured SHA-256 checksum, and install the tailscale and
tailscaled binaries. Neither mode pipes a downloaded installer script into a
root shell.
On release, crabbox stop attempts a best-effort remote tailscale logout before
provider cleanup when SSH is still reachable. Coordinator cleanup does not delete a
device by an ID posted from the lease client; that metadata is diagnostic, not
trusted authorization for a privileged tailnet operation. One-off nodes are
ephemeral and age out in Tailscale if remote logout is unavailable.
If Tailscale rejects a requested subset or unowned tag, the coordinator returns
invalid_tailscale_tags with exact-match/tagOwners guidance and preserves the raw
Tailscale HTTP status and response body for diagnosis.
Preflight the coordinator without leasing a machine:
CRABBOX_TAILSCALE_ENABLED=0 scripts/live-tailscale-smoke.sh --json
CRABBOX_LIVE=1 CRABBOX_COORDINATOR=https://broker.example.com CRABBOX_ADMIN_TOKEN=replace-me scripts/live-tailscale-smoke.sh --jsonCrabbox continues to use OpenSSH with per-lease keys; Tailscale SSH is not used. Tailscale only changes the SSH endpoint from the public/provider host to the tailnet host.
Managed VNC remains loopback-bound and tunneled over SSH:
local localhost:5901 -> SSH -> remote 127.0.0.1:5900
Crabbox does not bind managed VNC to 100.x addresses and does not use Tailscale
Serve, Funnel, or noVNC for managed leases.
Static hosts are operator-managed. Point static.host at a MagicDNS name or 100.x
address and use --network tailscale to assert reachability:
provider: ssh
target: macos
static:
host: build-host.example.ts.net
user: alice
port: "22"
workRoot: /Users/alice/crabboxFor static hosts, --network tailscale only asserts reachability and probes SSH;
Crabbox does not install or join Tailscale on the host.