sed in progress / thoughts are provided "as is", without warranty of any kind

k3d, FreshRSS, Tailscale, and an M1 Mac

2023-06-04

Background

I’m all about RSS.

I’ve been running a self-hosted instance of FreshRSS for years, now, and even migrated it from docker-compose to k3s across Digital Ocean and Hetzner. At the same time, I’ve been testing Tailscale with a few devices to get a feel for how usable it is (spoiler: very) across my devices.

As my home network and local storage was being consolidated, it was time to bring FreshRSS in-house, to be served through the tailnet. There was no reason for having my single user instance be available to the internet at large.

Desired State

I wanted this to be a seamless transition from the UX perspective. Navigate to the same URL, and be presented with my RSS feeds.

The requirements are that any device accessing the system needs to be in the tailnet, and that I needed to update the A record for my DNS entry to point to the machine IP provided by Tailscale.

Minor Details

k3s on M1 and Tailscale

Looking at the k3s requirements, it appeared that only Linux distros were supported. With no intention to install Asahi, enter k3d or k3s in Docker.

There were a few hiccups where the default install didn’t meet my needs; all resolved through a single config.yml that can be used when specified via k3d cluster create -d /path/to/config.yml run.

That all results in the following k3d config.

apiVersion: k3d.io/v1alpha5
kind: Simple
metadata:
  name: ${K3D_CLUSTER_NAME}
servers: 1
agents: 1
ports:
  - port: 80:80
    nodeFilters:
      - loadbalancer
  - port: 443:443
    nodeFilters:
      - loadbalancer
volumes:
  - volume: ${HOST_DIR_PATH}:${K3D_DOCKER_DIR_PATH}
    nodeFilters:
      - server:0
      - agent:*
options:
  k3s:
    extraArgs:
      - arg: --tls-san=${TAILSCALE_MACHINE_IP}
        nodeFilters:
          - server:*

Private IPs and Lets Encrypt

When using a private IP address, Lets Encrypt requires a dns01 challenge rather than the http01 that I’ve been able to get away with in the past. When visiting the Lets Debug page, I got the following:

Test result for ${URL} using http-01

ReservedAddress - FATAL

A private, inaccessible, IANA/IETF-reserved IP address was found for ${URL}. Let’s Encrypt will always fail HTTP validation for any domain that is pointing to an address that is not routable on the internet. You should either remove this address and replace it with a public one or use the DNS validation method instead.

Makes sense.

The resolution was simply adding and referencing a ClusterIssuer that used the dns01 solver. This worked out as my DNS entries are managed by a supported provider. Then it was only a matter of adding that in a separate values.yaml that overrides the default issuerRef in my helm chart.

What’s Next

Two possibilities are:

  1. Performance monitoring. I use PostgreSQL for the database, but might try one of the other supported options to experiment with.
  2. Adding more applications. This will be gated by whether an arm64 container is available.