Alvin Wang e07a5e1dfa Initial commit: k3s homelab infrastructure
Helm charts for media stack (Plex, Sonarr, Radarr, etc.), dashboards
(Glance, Homepage), paperless-ngx, mealie, traefik ingress, MetalLB,
and utilities. Includes SOPS-encrypted secrets and bootstrap script.
2026-04-19 19:22:22 -04:00

Homelab — k3s Cluster

2-node k3s cluster (1 manager, 1 worker) running a self-hosted homelab stack.

Architecture

Manager Node                Worker Node
┌──────────────┐            ┌──────────────┐
│  k3s server  │────────────│  k3s agent   │
│  Traefik     │            │              │
│  Longhorn    │            │  Longhorn    │
└──────┬───────┘            └──────┬───────┘
       │                           │
       └───────── NFS ─────────────┘
              /dogstore

Storage strategy:

  • /dogstore (NFS) — mounted on both nodes, used via hostPath for large media/data volumes
  • Longhorn — replicated block storage for small config/state volumes

Ingress: k3s built-in Traefik with Let's Encrypt via Cloudflare DNS challenge.

Secrets: SOPS + age encryption. Secrets live in secrets/secrets.enc.yaml, encrypted at rest.

Services

Chart Namespace Services
traefik-config kube-system Traefik HelmChartConfig overlay
media media Plex, Sonarr, Radarr, Bazarr, Prowlarr, qBittorrent, unpackerr
paperless paperless Paperless-ngx, Redis, PostgreSQL
mealie apps Mealie
dashboards apps Homepage, Glance
utils apps Zerobyte, Seerr

Prerequisites

  • Two Linux machines with NFS /dogstore mounted on both
  • curl, helm, kubectl, sops, age installed

Bootstrap

1. Install k3s server (manager node)

./scripts/bootstrap.sh server

This prints the worker join command at the end.

2. Install k3s agent (worker node)

K3S_URL="https://<manager-ip>:6443" K3S_TOKEN="<token>" ./scripts/bootstrap.sh agent

3. Install Longhorn

./scripts/bootstrap.sh longhorn

4. Set up SOPS encryption

Generate an age keypair (run on each node):

./scripts/bootstrap.sh sops-keygen

Copy the public key into .sops.yaml, replacing the placeholder. Then encrypt your secrets:

# Edit secrets/secrets.enc.yaml — replace REPLACE_WITH_* placeholders with real values
sops -e -i secrets/secrets.enc.yaml

5. Apply secrets

./scripts/bootstrap.sh apply-secrets

6. Deploy all charts

./scripts/bootstrap.sh deploy

Or deploy individually:

kubectl create namespace media
helm upgrade --install media charts/media -n media

kubectl create namespace paperless
helm upgrade --install paperless charts/paperless -n paperless

kubectl create namespace apps
helm upgrade --install mealie charts/mealie -n apps
helm upgrade --install dashboards charts/dashboards -n apps
helm upgrade --install utils charts/utils -n apps

# Traefik config goes in kube-system (managed by k3s)
helm upgrade --install traefik-config charts/traefik-config -n kube-system

Verifying

# Check all pods
kubectl get pods -A

# Check ingress routes
kubectl get ingress -A

# Test a specific service
curl -I https://mealie.ratboo.me

Secret Rotation

  1. Decrypt: sops secrets/secrets.enc.yaml (opens in $EDITOR)
  2. Change the values
  3. Save and close (SOPS re-encrypts automatically)
  4. Apply: ./scripts/bootstrap.sh apply-secrets
  5. Restart affected pods: kubectl rollout restart deployment/<name> -n <namespace>

Repo Structure

homelab/
├── README.md
├── .sops.yaml
├── scripts/
│   └── bootstrap.sh
├── charts/
│   ├── traefik-config/
│   ├── media/
│   ├── paperless/
│   ├── mealie/
│   ├── dashboards/
│   └── utils/
└── secrets/
    └── secrets.enc.yaml
Description
No description provided
Readme 119 KiB
Languages
Shell 94%
Smarty 6%