Running tailscale golink on kubernetes (Headscale edition)
As much as I love tailscale, rolling my own internal VPN with headscale has proven to be great fun and and good challenge. tailscale recently (read: a while ago, I'm just late to the party) introduced golink - an open source private URL shortener service for your tailnet. You can read more about this on the tailscale blog
For some context, I run my infrastructure on a self-managed Hetzner Cloud setup using their ARM boxes, with istio & authentik providing my ingress proxy and security needs. FluxCD is my GitOps provider of choice.
I use headscale for my internal routing to my cluster, and also to tools running at home and in other locations. Keeping track of different links is challenging, plus I don't really use 1 browser between my devices (Arc on MacOS, Safari on iOS, etc.) so syncing bookmarks isn't so easy to just do.
So, using tailscale's GoLink project, and my Headscale setup, I now run my own private go/
service, and here's how I did it.
Part 1 - Infrastructure/backing#
I'll be running golink on my k8s cluster as it's where headscale lives, and that's good enough for me. I'll be running this in my networking namespace however you can totally create a new namespace for this. Do this using the following:
kubectl create namespace networking
You don't need to configure istio's automatic injection for this namespace because of how headscale/tailscale works.
Part 2 - The setup#
Firstly, you'll need to create a PersistentVolumeClaim - an example of this is below:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: golink-pvc
namespace: networking
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
From there, you'll need to create a Deployment - replacing YOUR_AUTH_KEY
with your Tailnet key, and YOUR_HEADSCALE_URL
with your headscale controlplane URL:
apiVersion: apps/v1
kind: Deployment
metadata:
name: golink
namespace: headscale
spec:
selector:
matchLabels:
app.kubernetes.io/name: golink
app.kubernetes.io/instance: main
template:
metadata:
labels:
app.kubernetes.io/name: golink
app.kubernetes.io/instance: main
sidecar.istio.io/inject: "false"
spec:
securityContext:
fsGroup: 1000
containers:
- image: ghcr.io/tailscale/golink:main
name: golink
command:
- "/golink"
args:
- "-sqlitedb"
- "/home/nonroot/golink.db"
- "-verbose"
- "-control-url"
- "YOUR_HEADSCALE_URL"
env:
- name: TS_AUTHKEY
value: YOUR_AUTH_KEY
volumeMounts:
- name: data
mountPath: /home/nonroot
resources: {}
volumes:
- name: data
persistentVolumeClaim:
claimName: golink-pvc
It's important to mention that you can also use a secret for your environment variable (And I'd probably advice it) - this example just sets the key directly in the Deployment for ease
Lastly, we'll need to apply these to our Cluster - do this by running (Assuming you named your files golink-pvc.yaml
& golink-deployment.yaml
):
kubectl apply -f golink-pvc.yaml -f golink-deployment.yaml
If you're using Flux, commit the files to Git & Flux's source controller will do the rest
And... that's kinda it! Wait for the service to come online in your cluster and you can get started creating internal short-links!
Products/projects mentioned: