## Adding a container to your tailnet
Tailscale offers an official [Docker container](https://tailscale.com/kb/1282/docker) which provides several parameters we'll need to configure via environment variables. A full list of those variables is provided in the [documentation](https://tailscale.com/kb/1282/docker#parameters).
There are two primary methods to add a container to your tailnet. Option 1 is to use an auth key, and option 2 is using OAuth secrets—we'll cover both here as they can be useful in different scenarios.
### Should I use Auth Keys or OAuth secrets?
Before we dig into how to use them, let's figure out which authentication method is right for you. Both methods support a lot of the same things and both will work for many of you. However, there are subtle differences between these two authentication methods which may make or break your use case.
|Auth keys|OAuth clients|
|---|---|
|✅ Grant full API access|✅ Limits API access via scopes e.g. `dns:read`|
|✅ Maximum lifespan of 90 days|✅ Never expires|
|✅ Doesn't require tags|✅ Requires tags for created nodes|
|✅ Authenticates the machine as the user who generated the key|✅ Authenticates nodes via `tagOwner` tag|
In order to better understand some of the nuance here, let's examine node ownership and tags. When a node is authenticated to your tailnet via `tailscale up`, the user who logged into the admin console and clicked the generate button owns that node. That ownership manifests as a tag, which is why we refer to them in ACLs as `tagOwners`. Each node on a tailnet must have an owner, whether it's a user or a tag.
When you use an auth key, the node is added to your tailnet as the user who generated the key. With an OAuth client the node is owned by the tag assigned at secret creation time.
Another crucial difference is token / key expiry. On the face of it, it _appears_ that an auth key will be useless after the maximum 90 day expiry window. However, when an auth key expires it doesn't automatically mean any machine which used it to authenticate to your tailnet is suddenly expired as well—it simply means you can't use that key to add any _new_ nodes, existing ones continue to function until their [node keys expire](https://tailscale.com/kb/1028/key-expiry) (default is 180 days). Nodes added with an OAuth client never expire.
Now we know some of the nuances involved, let's dig into added a container to your tailnet using these methods.
### Using Auth Keys
To use an auth key we need to generate one using the admin console. Navigate to [https://login.tailscale.com/admin/settings/keys](https://login.tailscale.com/admin/settings/keys) and select **Generate auth key**. The most up to date information on generating auth keys and the available parameters can be found [in this kb article](https://tailscale.com/kb/1085/auth-keys#about-auth-keys). The values you select for your auth key will be highly specific to your environment, but for our purposes here let's just use the following to get you started:
- Description: `docker-testing`
- Reusable: `yes`
- Expiration: `7 days`
- Ephemeral: `No`
- Tags: `tag:container`
Note that in order to use a tag, it must first be defined in your [Access control policy](https://tailscale.com/kb/1018/acls) like so:
```
"tagOwners": {
"tag:container": ["autogroup:admin"],
},
```
If you're feeling adventurous or programmatic is your middle name, Tailscale also provides a utility named `get-authkey` to [automate this process](https://tailscale.com/kb/1215/oauth-clients#get-authkey-utility).
Once you have an auth key (do not share or lose this, treat it like a password), we want to plug it into our container using `TS_AUTHKEY=tskey-auth-123abc...` as an environment variable (see below).
It's vitally important that the container is able to persist the Tailscale session state across reboots and recreation events. To do this we need to map `/var/lib/tailscale` to a volume (see below for an example), and also explicitly configure the path the container will use to store state using the environment variable `TS_STATE_DIR=/var/lib/tailscale`.
One last point on auth keys. There is a common misconception that auth keys aren't useful for long running workloads due to the fact that they have a maximum life span of 90 days. However, if a node comes up tagged then key expiry is automatically disabled. Furthermore, so long as the container is able to persist state, removing the auth key completely (or it expiring) has no impact once the node has joined your tailnet. What will be affected after the auth key expires is the ability to join _new_ nodes to your tailnet without rotating the key.
### Using OAuth
OAuth clients provide a framework for delegated and scoped access to the [Tailscale API](https://tailscale.com/kb/1101/api). In other words, unlike an auth key which is fully scoped (i.e., if you have this key you can do anything), OAuth clients allow fine-grained control on the access granted to client. You can get a better idea of the scopes offered in the [documentation](https://tailscale.com/kb/1215/oauth-clients#scopes) but we're going to focus on the `devices:write` scope. This limits us to device related actions and we cannot, for example, modify ACLs or DNS on clients authenticated with this token. If the word auditor is in regular use in your vocabulary, then OAuth should be on your radar.
But how does one go about using an OAuth client token with Docker? Instead of using `TS_AUTHKEY=tskey-auth-blah` we replace that value with our OAuth client secret, and define another environment variable stating our desired tag like `TS_EXTRA_ARGS=--advertise-tags=tag:container`. Behind the scenes, the Tailscale container will execute `tailscale up`, recognize this is an OAuth client secret and use that mechanism to automatically generate an auth key for you. The outcome is a container on your tailnet all the same.
To generate an OAuth client token:
- visit [https://login.tailscale.com/admin/settings/oauth](https://login.tailscale.com/admin/settings/oauth)
- click **Generate OAuth client**
- fill out the description
- select `Devices: Write`
- (note `Devices: Read` will be automatically selected too)
- select an appropriate tag
- in our example we created the tag `container`
- as in the auth key section you must have already defined an ACL tag before this step in your ACLs
6. then it's time to **Generate Client**
The `Client ID` is not important for our purposes, nor is it particularly sensitive. The `Client secret` however, should be treated like a password - it is a secret after all. This key is only shown once and cannot be rotated. To rotate a OAuth client secret you must revoke and recreate a new one.
Those of you paying attention might notice that by default an OAuth client authenticated node is marked as `ephemeral`. In other words as soon as you stop the container (or very soon after), it is removed from your tailnet altogether. This might be really handy if it's a temporary thing like a CI build but for other services we want more permanence. To this end, append the argument `?ephemeral=false` to `TS_AUTHKEY` in the form `TS_AUTHKEY=tskey-client-kXGGbs6CNTRL-wXGXnotarealsecret1U3aeeaj?ephemeral=false`. Tailscale documentation includes information about [the other arguments available](https://tailscale.com/kb/1215/oauth-clients#registering-new-nodes-using-oauth-credentials).
## Examples
### Code-server
```yaml
---
services:
code-server-ts:
image: tailscale/tailscale:latest
container_name: code-server-ts
hostname: code-server
environment:
- TS_AUTHKEY=tskey-client-kcRHZB3GxG11CNTRL-example?ephemeral=false
- "TS_EXTRA_ARGS=--advertise-tags=tag:container --reset"
- TS_SERVE_CONFIG=/config/code-server.json
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
volumes:
- /docker/code-server/tailscale/config:/config
- /docker/code-server/tailscale/var/lib/tailscale:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
restart: unless-stopped
code-server:
image: lscr.io/linuxserver/code-server:latest
container_name: code-server
network_mode: service:code-server-ts
depends_on:
- code-server-ts
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- PASSWORD=password
- SUDO_PASSWORD=password
- PROXY_DOMAIN=
- DEFAULT_WORKSPACE=/config/workspace #optional
volumes:
- /docker/code-server/config:/config
- /docker/code-server/custom-cont-init.d:/custom-cont-init.d:ro
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
```
```json
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"${TS_CERT_DOMAIN}:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:8443"
}
}
}
},
"AllowFunnel": {
"${TS_CERT_DOMAIN}:443": false
}
}
```
## Sources
[Contain your excitement: A deep dive into using Tailscale with Docker](https://tailscale.com/blog/docker-tailscale-guide)
[Using Tailscale with Docker · Tailscale Docs](https://tailscale.com/kb/1282/docker)
[docker-guide-code-examples/06-quickstart-vid/02-stirlingpdf/docker-compose.yaml at main · tailscale-dev/docker-guide-code-examples · GitHub](https://github.com/tailscale-dev/docker-guide-code-examples/blob/main/06-quickstart-vid/02-stirlingpdf/docker-compose.yaml)
[docker-guide-code-examples/06-quickstart-vid/02-stirlingpdf/stirling.json at main · tailscale-dev/docker-guide-code-examples · GitHub](https://github.com/tailscale-dev/docker-guide-code-examples/blob/main/06-quickstart-vid/02-stirlingpdf/stirling.json)