Tagging lets you group devices and use Tailscale’s powerful access control features.
Runway specifically supports a TS_TAG
variable, but before you get started, you need to run through these steps:
Go on your Tailscale admin console and create a tag. Unless you have previously created a policy, Tailscale’s access control policy editor will contain defaults.
In order to create a tag (e.g. runway
), add the following to your policy:
{
"tagOwners": {
"tag:runway": ["email@example.org"],
}
}
The email is your account/email address on Tailscale.
If you consider using this tag with your friends or colleagues, then a group might be the better choice. This is, so it’s not just yourself who can apply the tag to (new) Runway applications — but if it’s single-player all the way, then go ahead with the first example.
{
"groups": {
"group:collab": ["email@example.org", "friend@example.com"],
"group:admin": ["email@example.org"],
},
"tagOwners": {
"tag:runway": ["group:collab"],
}
}
Use TS_TAG=tag-name
. For example, for a tag:runway
do:
runway app config set TS_AUTHKEY=tskey-... TS_TAG=runway
Follow up with a runway app restart
and your app (“device”/“node” in Tailscale) will now be tagged.
Make sure the tag you are using actually exists in Tailscale. Do not
include the tag:
prefix in the TS_TAG
value. If the app doesn’t re-connect
to Tailscale after you set the TS_TAG
, check your runway app logs
(which include
Tailscale’s error messages).
Optionally, you can also create an authorization key and select the tag with it. This encodes the tag into the key and will ensure that everything authenticated with that key has the tag.
You will still need to set TS_TAG
to the tag in your Runway app in this case.
Tags have the advantage that we can build powerful access controls. All that without having to remember an IP address ever again - this is what Tailscale does for us. Check out the example below!
Imagine you have a local database setup and some LLMs running — all tagged with homelab
. You want to grant access to these services to your friends (and Runway!):
{
"groups": {
"group:me": ["till"],
"group:friends": ["dennis", "richard"],
},
"tagOwners": {
"tag:private": ["group:me"],
"tag:homelab": ["group:me"],
"tag:runway": ["group:friends"],
},
"grants": [
{"src": ["tag:homelab"], "dst": ["tag:runway"], "ip": ["*"]},
{"src": ["tag:runway"], "dst": ["tag:homelab"], "ip": ["*"]},
{"src": ["group:friends"], "dst": ["tag:homelab", "tag:runway"], "ip": ["*"]},
{"src": ["group:me"], "dst": ["*"], "ip": ["*"]}
],
"tests": [
{
"src": "group:me",
"accept": ["tag:runway:5000", "tag:homelab:8080"],
},
{
"src": "group:friends",
"deny": ["tag:private:1337"],
},
{
"src": "tag:runway",
"deny": ["tag:private:1337"],
}
],
}
homelab
and private
.runway
is owned by all my friends.homelab
can talk to anything on Runway.homelab
.homelab
and Runway!private
, and neither can my friends!If you noticed, we also included tests
which validate our assumptions about the grants prior to saving the policy file on Tailscale.
There are plenty of other examples in the official documentation.