Connect a sandbox to Tailscale
Join a sandbox to your tailnet with a Tailscale auth key.
Pass a Tailscale auth key when you create a sandbox, and the sandbox joins your tailnet as its own device. Your tailnet devices can reach it by its Tailscale IP or MagicDNS name, and the sandbox can reach them the same way.
Tailscale attachment is in preview and is enabled per workspace. Contact Tenki support to turn it on for yours.
You need the Tenki CLI 2.3.0 or later, or SDK version 1.4.0 or later.
Create an auth key
In the Tailscale admin console, open Keys and generate an auth key with these settings:
- Reusable, so one key can join many sandboxes.
A single-use key joins only the first sandbox; later sandboxes stay in
CREATINGuntil the wait times out. - Ephemeral, so Tailscale removes the device when the sandbox is terminated.
- Pre-approved, if your tailnet requires device approval.
- Tags, if your access controls grant access by tag.
- A short expiration, such as one day, since a reusable key can enroll any number of devices until it expires. Revoke it when you no longer need it.
Save the key to a file that only you can read. Paste the key, press Enter, then press Ctrl-D:
umask 077
cat > ~/tailscale-auth-keyDelete the file when you are done, or skip it and pass the key on stdin with --tailnet-auth-key-file -.
Tenki reads the key once at create, never returns it, and clears it when the sandbox is terminated. The sandbox itself never sees the key.
Create a sandbox on your tailnet
With wait for online, create returns after the sandbox is RUNNING and its Tailscale device is online, usually within a few seconds.
The SDKs wrap the key in TailnetAuthKey, which prints as [REDACTED] in logs and debug output.
tenki sandbox create makes the new sandbox the CLI's current session, so the get, exec, and terminate commands on this page act on it.
A sandbox created with an SDK does not become the CLI's current session.
Run tenki sandbox set <session-id> first, or pass --session <session-id> to each command, so they act on the right sandbox.
Check the attachment at any time:
tenki sandbox getThe output includes tailnet_state and tailnet_fqdn, for example my-sandbox.your-tailnet.ts.net.
Add --json to see the device's Tailscale IPs under tailnet_status.ips.
Reach the sandbox and your tailnet
From a device allowed by your tailnet's ACLs or grants, use the returned tailnet_fqdn:
tailscale ping my-sandbox.your-tailnet.ts.netConnect by tailnet_fqdn or Tailscale IP rather than the hostname you requested.
If that hostname is already taken on your tailnet, Tailscale assigns a different one, such as my-sandbox-1, and the requested name still points at the other device.
Connections from your tailnet into the sandbox reach only the ports you expose at create.
For example, add --tailnet-expose-port 8080 to allow TCP connections to a service listening on 0.0.0.0:8080 in the sandbox; other ports stay closed on the tailnet path.
From inside the sandbox, reach tailnet devices by Tailscale IP or MagicDNS name:
tenki sandbox exec -- curl -fsS http://my-laptop.your-tailnet.ts.net:8080/Preview URLs and tailnet access
A Tailscale-attached sandbox can serve the same application through two independent inbound paths:
| Access path | Route | Enable it with |
|---|---|---|
| Tailnet TCP | Allowed tailnet peer to the sandbox's Tailscale IP or MagicDNS name and TCP port | --tailnet-expose-port 8080 at create time |
| Tenki preview URL | Browser to Tenki's public HTTPS gateway, then to the sandbox's application port | tenki sandbox preview-url create |
Attaching Tailscale does not make a Tenki preview URL tailnet-only or apply your tailnet's ACLs to the preview gateway. Preview visitors do not need a Tailscale client or tailnet membership.
With your application listening on port 8080, create a preview URL:
tenki sandbox preview-url create --slug my-app --session <session-id> --port 8080Open the returned URL in a browser.
The slug is part of the public hostname, and anyone with the URL can reach the port.
Tenki serves HTTPS at the gateway, so the application can serve plain HTTP on port 8080 inside the sandbox.
This path works without --tailnet-expose-port 8080.
Conversely, --tailnet-expose-port 8080 enables private tailnet forwarding and does not create a Tenki preview URL.
For tailnet-only access, use the returned Tailscale IP or FQDN and leave the application unexposed through Tenki's preview gateway. If you already created a preview URL for that port, delete it:
tenki sandbox preview-url list --session <session-id>
tenki sandbox preview-url delete <preview-url-id>Unbinding or unexposing only takes the URL offline; it stays in the workspace and counts toward your preview URL limit until you delete it.
See port exposure and networking for the SDK equivalents.
Restrict public HTTPS requests
By default, the sandbox's public internet traffic goes through Tenki's egress policy, as it does without Tailscale.
Use a saved HTTPS policy to allow registry downloads and GitHub clone/fetch requests while restricting other HTTP methods and paths.
Create the policy in the same workspace, then add --egress-policy registries to the CLI create command above.
In Go, combine tenkisandbox.WithEgressPolicies("registries") with tenkisandbox.WithTailnet(...); WithEgressPolicies requires the Go SDK installed from main.
Egress policies apply to public internet traffic only. Traffic from the sandbox to devices on your tailnet is not checked against the allowed-domain list, so restrict it with your tailnet's ACLs or grants. Egress policies also do not control inbound preview access or expose a guest port to the tailnet.
Terminate the sandbox
tenki sandbox terminate --session <session-id>Tenki logs the device out of your tailnet. With an ephemeral key, Tailscale removes the device within seconds, and it no longer appears on the Machines page. With a non-ephemeral key, the device stays on the Machines page as expired until you remove it there.
Options
| Option | Description |
|---|---|
--tailnet-providerSDK: provider | Attachment provider. Only tailscale is supported. |
--tailnet-auth-key-fileSDK: authKey | File containing the auth key, or - to read it from stdin. |
--tailnet-hostnameSDK: hostname | Device name on your tailnet. |
--tailnet-tagSDK: tags | Tag to request for the device. Repeat for more than one. |
--tailnet-ephemeralSDK: ephemeral | Request an ephemeral device. Use an ephemeral auth key as well. |
--tailnet-wait-for-onlineSDK: waitForOnline | Return only after the device is online on your tailnet. |
--tailnet-expose-portSDK: exposePorts | Sandbox TCP port reachable from your tailnet. Repeat for more than one. |
--tailnet-accept-routesSDK: acceptRoutes | Accept subnet routes advertised on your tailnet. |
SDK names are for TypeScript. Python uses the same names in snake case, for example wait_for_online.
Go uses the same names in Pascal case, for example WaitForOnline.
Troubleshooting
Create fails or times out before the attachment is online.
A timeout alone does not mean the auth key was rejected.
Inspect the allocated session with tenki sandbox get --session <session-id> --json, checking tailnet_status.state, tailnet_status.error, and terminal_error when present.
If the error identifies a rejected enrollment credential, the key may be expired, revoked, or a single-use key that already joined another sandbox; generate a new reusable key and try again.
Other failures can involve device approval, provider availability, or sandbox provisioning and readiness.
Without wait for online, create can return a RUNNING sandbox before enrollment finishes.
A later enrollment failure can leave the attachment offline or in error while the sandbox remains running.
If enrollment fails before creation completes, create can fail instead.
Terminated sandboxes still appear on the Machines page. The auth key is not ephemeral. Remove the devices on the Machines page, and use an ephemeral key for new sandboxes.