DEV Community

David Bond
David Bond

Posted on • Originally published at on

Homelab: Accessing my k3s cluster securely from anywhere with Tailscale


At home, I run my own k3s cluster on 4 Raspberry Pi 4Bs. In order to access the
services I run from anywhere without exposing my cluster to the open internet I use Tailscale,
a service designed to make a private VPN really easy to set up. I run a bunch of services, including (but not
limited to) a password manager, Google Photos alternative, finance management tools etc.

This post aims to describe how my cluster is set up to use Tailscale, allowing me to resolve DNS via Cloudflare restricting
access solely to me (or anyone I share my Tailscale machines with). This allows me to go to
on any device I have connected to my Tailscale network and access my own password manager instance.

Cluster Setup

My k3s cluster consists of four nodes. Each one is a Raspberry Pi 4B+,
the 8GB model. I've been pleasantly surprised with how much you can run on these small machines, every year they seem to
pack more and more power into a credit card's worth of space. The GitHub repository
has a full overview of the setup you can view for yourself.

Physical Cluster

Installing Tailscale

Each node in the cluster is running Ubuntu for Raspberry Pi, so installing Tailscale is
as simple as following the instructions for ubuntu.

  1. Add Tailscale’s package signing key and repository
curl -fsSL | sudo apt-key add -
curl -fsSL | sudo tee /etc/apt/sources.list.d/Tailscale.list
Enter fullscreen mode Exit fullscreen mode

Personally, I use the unstable repository instead, because I like to be bleeding edge. It's worth adding that I keep
regular backups of everything on my cluster, just in case my bleeding-edge tendencies end up with me breaking my cluster
or losing access to things I need.

  1. Install Tailscale
sudo apt-get update
sudo apt-get install Tailscale
Enter fullscreen mode Exit fullscreen mode
  1. Authenticate and connect your machine to your Tailscale network
sudo Tailscale up
Enter fullscreen mode Exit fullscreen mode

I did this for each node, you could use a terminal multiplexer (like tmux) to speed things
up a bit.

Installing K3S

Next, we need to install k3s on each node to get a cluster running. The documentation
is the authoritative source for this, but I'm also going to outline the quickstart steps here.

  1. Install the k3s server for the control plane node. Including setting the node's advertise address as the Tailscale IP, rather than the local network IP, this is done via the --bind-address flag. This is optional, but saves you having to set up a static IP address for your machine. It also means the cluster nodes will communicate via the Tailscale network.
curl -sfL | sh -s - --bind-address <TAILSCALE_IP>
Enter fullscreen mode Exit fullscreen mode

Rancher have provided a simple script to get things up and running quickly. I'd advise you take a look at it first rather
than just running some script off the internet. Once complete, grab the token required for your agent nodes to join the
cluster. This is stored at /var/lib/rancher/k3s/server/node-token.

  1. Install the k3s agent on all the other nodes

Here, we set up each agent node in the cluster. Once again, rancher have provided a simple script:

curl -sfL | K3S_URL=https://<SERVER_TAILSCALE_IP>:6443 K3S_TOKEN=<NODE_TOKEN> sh -
Enter fullscreen mode Exit fullscreen mode

SERVER_TAILSCALE_IP is the Tailscale IP address of your control-plane node. NODE_TOKEN is the token mentioned in the
previous step. After following these steps, you've now got a k3s cluster running, where all nodes communicate via
Tailscale. Go you!


By default, k3s comes with Traefik already deployed. Because I'm using a .dev domain, I also
needed to ensure everything I serve on my domain was using https. To do this, I've added cert-manager
to my cluster. Cert-manager allows me to generate TLS certificates for my ingresses automatically via letsencrypt.
All I have to do is add additional annotations to my Ingress resources.

If you also want to use cert-manager, it's easiest for you to follow their instructions,
as explaining it all here would be out of scope for this blog post, since you may not even care about using HTTPS at all.
In brief, my cert-manager deployment authenticates with cloudflare using an API key with limited permissions using a
DNS-01 challenge. You can read more about DNS-01 challenges here.
You can also see my cert-manager deployment here.

Here's my Ingress resource for my Bitwarden deployment:

kind: Ingress
  name: bitwarden
  annotations: traefik "true" https cloudflare
  - hosts:
    secretName: bitwarden-tls
  - host:
      - backend:
            name: bitwarden
              number: 80
        path: /
        pathType: Prefix
Enter fullscreen mode Exit fullscreen mode

Here I'm telling Traefik that any inbound requests for should route to a Service resource
named bitwarden, and that TLS certificates should be stored in a secret named bitwarden-tls, issued via cert-manager.

Cloudflare DNS

The last step is to set up appropriate DNS records to route requests to the cluster when connected to the Tailscale
network. For my use-case, I want any subdomain of to go straight to the cluster. This way, I don't
need DNS records for each individual application I want to expose.

I manage these records using Terraform, and the setup is fairly straightforward. To start
with, I needed to set up the cloudflare provider:

provider "cloudflare" {
  email   = var.cloudflare_email
  api_key = var.cloudflare_api_key
Enter fullscreen mode Exit fullscreen mode

All you need is to provide the email address you use for your cloudflare account, and the API key. Next, you need to
be able to grab the zone identifier for your domain. Since I have a single domain, I just created a simple
data source that returns all my cloudflare zones:

data "cloudflare_zones" "dsb_dev" {
  filter {}
Enter fullscreen mode Exit fullscreen mode

If you have multiple domains, you're going to want to modify that filter to return the one you care about. You can see
the documentation for that here.

Lastly, I needed to create a DNS record for each node in the cluster, using its Tailscale IP address. The name of each
record is *.homelab, which specifies that any requests to a subdomain of gets sent straight to the
cluster, providing you have access to the Tailscale network:

resource "cloudflare_record" "homelab_0" {
  zone_id = lookup(data.cloudflare_zones.dsb_dev.zones[0], "id")
  name    = "*.homelab"
  value   = var.homelab_0_ip
  type    = "A"
  ttl     = 3600

resource "cloudflare_record" "homelab_1" {
  zone_id = lookup(data.cloudflare_zones.dsb_dev.zones[0], "id")
  name    = "*.homelab"
  value   = var.homelab_1_ip
  type    = "A"
  ttl     = 3600

resource "cloudflare_record" "homelab_2" {
  zone_id = lookup(data.cloudflare_zones.dsb_dev.zones[0], "id")
  name    = "*.homelab"
  value   = var.homelab_2_ip
  type    = "A"
  ttl     = 3600

resource "cloudflare_record" "homelab_3" {
  zone_id = lookup(data.cloudflare_zones.dsb_dev.zones[0], "id")
  name    = "*.homelab"
  value   = var.homelab_3_ip
  type    = "A"
  ttl     = 3600
Enter fullscreen mode Exit fullscreen mode

You can view the full terraform configuration here.

I run Traefik as a DaemonSet in my cluster, meaning whichever node receives the request can route it to the appropriate
service regardless of the node its running on. This allows me to do some basic load balancing. The main caveat here, is for
each new node I add, I also need a new DNS record, but since this is my homelab, I'm not planning on increasing the node size
to a larger size where I'd need to automate this.

Wrapping up

The above setup allows me to access all the applications I have running on my home k3s cluster from anywhere providing
I have a connection to the Tailscale network. This works great for me, especially since Tailscale also has an android
app, which allows me to access my password manager and other applications on my phone, all without exposing my cluster
to the public internet!

Combining it with cert-manager also gives me the ability to secure everything with HTTPS and use a FQDN on a domain
that I own.


Top comments (0)