# Tailscale connections (https://tenki.cloud/docs/runners/tailscale)

> For the complete documentation index, see [llms.txt](https://tenki.cloud/llms.txt)

Connect your tailnet once in workspace settings, then let runners and sandboxes join it without auth keys.

A Tailscale connection lets Tenki runners and sandboxes join your tailnet as tagged devices.
You add it once under **Settings > Networking**.
After that, Tenki asks Tailscale for a short-lived auth key each time a workload starts, using OpenID Connect.
Tenki stores no Tailscale auth key or OAuth secret.

With a connection in place you can:

* make it the default for new sandboxes, new GitHub runner jobs, or both
* add a dedicated runner label, so only jobs that use that label join your tailnet
* attach it to one sandbox from the CLI

<Callout type="info" title="Preview">
  Tailscale connections are in preview and enabled per workspace. Contact Tenki support to turn them on for yours. The
  Networking tab is visible only to members who can edit workspace settings.
</Callout>

This video sets up two connections.
`company-staging` becomes the default for sandboxes and for runner jobs in two repositories.
`company-prod` gets a dedicated runner label that only protected-branch jobs in `acme/infra` can use.

<AutoPlayVideo autoPlay="false" preload="metadata" height="auto" poster="getAsset(&#x22;/videos/tailscale-connections-poster.jpg&#x22;)">
  <source src="getAsset(&#x22;/videos/tailscale-connections.mp4&#x22;)" type="video/mp4" />
</AutoPlayVideo>

## How it works

1. A sandbox or runner job starts.
2. Tenki signs an OpenID Connect token whose subject names your workspace, the connection, and the session.
3. Tailscale checks the token against the trust credential you created and returns an API token limited to its scopes and tags.
4. Tenki uses that token to create an auth key, and the workload joins your tailnet with your tags.
5. When the workload ends, Tenki removes the device.

Your tailnet policy decides what those tagged devices can reach.
Write grants or ACLs for the tags you allow, the same way you would for any other tagged device.

## Add a connection

Before you start, make sure every tag you plan to use is listed under `tagOwners` in your tailnet policy file.

**Step 1:**
Open **Settings > Networking** and click **Add connection**.

**Step 2:**
Under **Set up trust**, enter a **Connection name** and the **Allowed tags**.

The name uses lowercase letters, numbers, and dashes, and starts with a letter, for example `company-staging`.
Tags are comma-separated, such as `tag:ci, tag:staging`, with up to 16 per connection.

**Step 3:**
Keep the Tenki dialog open and click **Open Tailscale** to go to the [Trust credentials](https://login.tailscale.com/admin/settings/trust-credentials) page.
Click **Credential**, choose **OpenID Connect**, and fill it in with the values from the Tenki dialog:

| Tailscale field | Value                                                                                                 |
| --------------- | ----------------------------------------------------------------------------------------------------- |
| Issuer          | **Custom issuer**, with the **Issuer** URL from the dialog, `https://tailscale.tenki.cloud`           |
| Subject         | The **Subject pattern** from the dialog, `workspace:<workspace-id>:tailnet:<connection-id>:session:*` |
| Scopes          | **Auth Keys** write, limited to the same tags, and **Devices Core** read                              |

Click a value in the Tenki dialog to copy it.
Then click **Generate credential** in Tailscale and copy the **Client ID** and **Audience**.

The connection ID in the subject is created when the dialog opens.
If you close the dialog and start again, the subject changes, and you need to update the trust credential to match.

**Step 4:**
Back in Tenki, click **Continue**, paste the **Client ID** and **Audience**, and click **Save connection**.

The connection appears in the **Tailscale Connections** table as **Enabled**.
The **Default for** column stays empty until you [set a default](#set-workspace-defaults).

## Test a connection

Click **Test** on the connection's row, then **Run test**.
Tenki joins your tailnet with a short-lived device and removes it when the test finishes.

To check that a service is reachable too, select **Also check a private endpoint** and enter its URL, for example `https://internal-api.example.ts.net/health`.
The test then checks DNS and HTTP access to that URL from inside your tailnet.

| Result                                               | What to check                                                                      |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
| Joined your tailnet                                  | Nothing. The connection works.                                                     |
| Couldn't join your tailnet                           | The subject, scopes, and tags in the Tailscale trust credential.                   |
| Joined your tailnet, but couldn't reach the endpoint | Private DNS, the grants or ACLs for your tags, and whether the service is running. |

## Set workspace defaults

The **Dedicated Tailscale Runner Labels** card has one row for sandboxes and one for GitHub runners.
Each row shows **Not set** until you pick a connection.
Click **Set default**, choose a connection, and click **Save default**.
To clear a default, click **Change** and choose **None**.

The two defaults are independent, so sandboxes and runners can use different connections.

### Sandboxes

Every new sandbox in the workspace joins the default connection, whether you create it with the CLI, an SDK, or a template in the dashboard.
Create waits until the sandbox's device is online on your tailnet.

To skip the default for one sandbox, use `--no-tailnet` with Tenki CLI `2.8.0` or later:

```bash
tenki sandbox create --no-tailnet
```

When you launch a template from the dashboard, the **Tailscale connection** field offers the workspace default, **No Tailscale connection**, or any other enabled connection.

### GitHub runners

The runner default applies to Linux jobs on [standard labels](https://tenki.cloud/docs/runners/sizes.md), such as `tenki-standard-medium-4c-8g`.
When you set it, you also choose:

* **Default runner repository allowlist**: the repositories whose jobs can use the connection.
  Only repositories connected to the workspace are listed.
  If none appear, connect GitHub on the Runners page first.
* **Default runner job access**: which jobs in those repositories can join. See [job access](#job-access).

<Callout type="warn" title="Jobs outside the policy are rejected">
  While a runner default is set, every Linux job on a standard label must pass its policy. A job from a repository
  outside the allowlist, or one that its job access does not allow, is rejected instead of running without Tailscale. If
  only some repositories need your tailnet, use a [dedicated runner](#add-a-dedicated-runner) instead.
</Callout>

## Add a dedicated runner

A dedicated runner is a runner label tied to one connection.
Only jobs that ask for that label join your tailnet, so the rest of your CI keeps running as before.

**Step 1:**
In the 

**Dedicated Runners**

 card, click 

**Add dedicated runner**

.

**Step 2:**
Choose the 

**Tailscale connection**

 and a Linux 

**Runner size**

.

**Step 3:**
Select the repositories in the 

**Repository allowlist**

, up to 100.

**Step 4:**
Choose the 

[job access](#job-access)

 and click 

**Save dedicated runner**

.

Tenki creates a label that starts with `tenki-tailnet-`.
Click the label in the table to see a workflow example and the runners that started on it recently.
Use the label in `runs-on`:

```yaml
jobs:
  integration:
    runs-on: tenki-tailnet-<id>
    steps:
      - uses: actions/checkout@v4
      - run: curl --fail https://internal-api.example.ts.net/health
```

The job joins your tailnet before any step runs.
You don't need a Tailscale step or a GitHub secret in the workflow.

Each entry under **Recent runners** shows the runner's device name, Tailscale IP, and state: `pending`, `joining`, `online`, or `error`.
**Job access denied** means the job's repository, branch, or event didn't match the policy.

To stop new jobs on a label, open **Edit dedicated runner** from the row's menu and select **Disable new jobs on this label**.

## Job access

Runner defaults and dedicated runners both use one of three job access levels.
All three apply only to repositories in the allowlist.

| Job access                        | Jobs that can join your tailnet                                                                                  |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Protected branches only (default) | `push`, `schedule`, and `workflow_dispatch` runs on protected branches. Pull requests can't join.                |
| Same-repository jobs              | Any job from a branch in the repository, including pull requests from that repository. Fork pull requests can't. |
| All jobs, including fork PRs      | Every job, including pull requests from forks.                                                                   |

Choose **All jobs, including fork PRs** only for repositories where you trust everyone who can open a pull request.
Anyone who can run a job on the label can reach whatever your tags allow on your tailnet.

## Use a connection with one sandbox

Click a connection's name, or choose **Use connection** from its menu, to see its ID and a ready-to-copy command:

```bash
tenki sandbox create --tailnet-profile-id <connection-id>
```

This needs Tenki CLI `2.8.0` or later.
It works whether or not the connection is a default, and you don't need an auth key.
To attach a sandbox with your own Tailscale auth key instead, see [Tailscale for sandboxes](https://tenki.cloud/docs/sandbox/tailscale.md).

## Edit or disable a connection

Use the menu at the end of a connection's row.

* **Edit connection** changes the name, tags, client ID, or audience.
  Changes apply to new workloads only.
  If you change the tags, update the trust credential in Tailscale to match.
* **Disable connection** stops new workloads from using it.
  Sandboxes and jobs that are already running keep their connection.

A disabled connection that is still a default makes new sandboxes, or new runner jobs on standard labels, fail.
The **Dedicated Tailscale Runner Labels** card warns about this.
Change or clear the default when you disable its connection.