Setting Up Tailscale in Your Homelab: A Painless Adventure

Intro

Ahoy, Homelabbers! Ever wanted to create a private WireGuard VPN between multiple devices without the headache of configuring firewalls or exposing ports publicly? Look no further! Tailscale is here to make your dreams come true.

In this tutorial, we’ll be using k3s (but feel free to use any Kubernetes distribution) to host our Tailscale subrouter and exit node. This setup will grant us:

In this tutorial, we’ll be using k3s (but feel free to use any Kubernetes distribution) to host our Tailscale subrouter and exit node. This setup will grant us:

  • A reliable Tailscale hosting on Kubernetes, thanks to its health checks
  • Subnets that provide access to our private network or Kubernetes cluster from an outside network
  • An exit node that gives us a full WireGuard VPN experience from anywhere on the globe

Ready to get started? Let’s go!

Getting a Tailscale Auth Key

We’ll need an ephemeral key for this. Ephemeral keys are perfect for temporary or non-human-managed machines. To generate an auth key, simply log in to Tailscale’s web console and follow these steps: Settings > Keys > Auth keys > Generate auth key. In the modal window, make sure to set the following:

  • Reusable: true
  • Expiration: 90 days
  • Ephemeral: true
  • Tags (optional): any applicable to your setup, but you can leave this blank for now

Once generated, you’ll get a key looking like this: tskey-auth-xxXXxxxXX-xxxxx

Deploying our manifests

Next, create a tailscale.yaml file with the following manifest and replace the secret stringData with the API key you just got. Save the file, and run kubectl apply -f tailscale.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: tailscale

---

apiVersion: v1
kind: Secret
metadata:
  name: tailscale-auth
  namespace: tailscale
stringData:
  AUTH_KEY: tskey-auth-xxXXxxxXX-xxxxx

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: tailscale
  namespace: tailscale

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tailscale
  namespace: tailscale
subjects:
- kind: ServiceAccount
  name: "tailscale"
roleRef:
  kind: Role
  name: tailscale
  apiGroup: rbac.authorization.k8s.io

---

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: tailscale
  namespace: tailscale
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create"]
- apiGroups: [""]
  resourceNames: ["tailscale-auth"]
  resources: ["secrets"]
  verbs: ["get", "update", "patch"]

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tailscale
  namespace: tailscale
spec:
  selector:
    matchLabels:
      app: tailscale
  strategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
  template: 
    metadata:
      labels:
        app: tailscale
    spec:
      serviceAccountName: "tailscale"
      containers:
      - name: tailscale
        image: ghcr.io/tailscale/tailscale:latest
        imagePullPolicy: Always
        env:
        - name: TZ
          value: "Australia/Sydney"
        - name: TS_KUBE_SECRET
          value: "tailscale-auth"
        - name: TS_USERSPACE
          value: "true"
        - name: TS_EXTRA_ARGS
          value: "--advertise-exit-node --hostname=k8s-node"
        - name: TS_ROUTES
          value: "192.168.0.0/24"
        - name: TS_AUTH_KEY
          valueFrom:
            secretKeyRef:
              name: tailscale-auth
              key: AUTH_KEY
              optional: false
        livenessProbe:
          exec:
            command:
            - tailscale
            - --socket=/tmp/tailscaled.sock
            - status || exit 1
          initialDelaySeconds: 30
          periodSeconds: 15
          failureThreshold: 2
        startupProbe:
          exec:
            command:
            - tailscale
            - --socket=/tmp/tailscaled.sock
            - status || exit 1
          initialDelaySeconds: 15
          periodSeconds: 30
          failureThreshold: 2

Breakdown

TS_EXTRA_ARGS: These are used inside the Tailscale image to pass additional command line arguments into the startup command. In our example, we’re setting a hostname (to identify the pod to Tailscale) and specifying that we want this pod to act as an exit node. Feel free to modify these as needed.

TS_ROUTES: Update this to any CIDR block appropriate for your network requirements. In this example, we’re advertising routes typically found in home networks.

Confirming Everything is Running

Run kubectl get pod -n tailscale and check if it shows 1/1 as Running. If not, consult the logs with kubectl logs -l app=tailscale -n tailscale for more information. It’s likely the manifest wasn’t set up correctly, or the auth key needs verification.

Setting Up Our New Node in Tailscale Console

With our new node connected to Tailscale and ready for use, it’s time for some housekeeping:

  • Head over to Tailscale and go to the “Machines” page
  • Verify it’s listed as “Connected”
  • Hover over this new machine, click the 3 dots menu on the right, and select “Edit Route Settings”
  • Enable any “Subnet Routes” and “Exit Node” use as desired
  • (Optional) To disable key expiry, hover over the new machine, click the 3 dots menu on the right, and select “Disable Key Expiry” (this prevents the need to rotate the key in the next 90 days)

Summing up

With Tailscale up and running in your homelab, you’ve now unlocked the power to access anything available on the exposed subnet. This includes privately hosted web apps that were once out of reach on another network. When using subnets we can use our devices main internet connection for most outbound requests, but route private connections to our homelab.

But that’s not all! When you’re traveling and feeling a little homesick, you can use the exit node feature on any other Tailscale device. This nifty trick provides a secure WireGuard VPN back to your home internet service, making your devices believe you’re still at home sweet home. Want to watch your local Netflix library while globetrotting? No problem! Tailscale has your back.

Updates

  • April 2023 - Updating manifest for new tailscale image and recommendations, small text changes