Home Server Part 3: Cluster Setup — Ubuntu Server, K3s, and Traefik
Part 3 of the Home Server Series. Follows Part 2: Requirements and Decisions.
The foundation: installing the OS, Kubernetes, storage, and ingress routing. After this post, you have a working cluster ready to deploy services.
TL;DR
Ubuntu Server 24.04 LTS with LVM (leave 20-30% unallocated for growth). K3s installed with --data-dir /data/k3s. Traefik (bundled) configured for host-based routing via IngressRoute CRDs. DNS via /etc/hosts on client machines.
1. Ubuntu Server Installation
1.1 Create Bootable USB
Download Ubuntu Server 24.04 LTS from ubuntu.com. Flash to USB:
# On macOS
diskutil list # find your USB (e.g., /dev/disk4)
diskutil unmountDisk /dev/disk4
sudo dd if=ubuntu-24.04-live-server-amd64.iso of=/dev/rdisk4 bs=4M
Or use balenaEtcher for a GUI.
1.2 Install
Boot from USB. During installation:
- Language/keyboard: your preference
- Network: configure a static IP (or set a DHCP reservation on your router later — you want a stable IP)
- Storage layout: choose “Use an entire disk” → enable LVM group
- LVM customisation (edit before confirming):
| Logical Volume | Mount | Size | Purpose |
|---|---|---|---|
ubuntu-lv (root) | / | 50 GB | OS, packages, K3s binaries |
data-lv | /data | 60-70% of remaining | K3s storage, app data, media |
| swap | swap | 4-8 GB | Swap space |
| unallocated | — | 20-30% of VG | Future growth |
Why leave space unallocated? LVM lets you grow volumes live:
sudo lvextend -L +100G /dev/ubuntu-vg/data-lv && sudo resize2fs /dev/ubuntu-vg/data-lv. You can’t easily shrink, so start conservative.
- SSH: enable OpenSSH server
- User: create your admin user
- Snaps: skip all optional snaps
1.3 Post-Install
SSH into your server:
ssh youruser@SERVER_IP
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git htop net-tools
Create the data directory structure:
sudo mkdir -p /data/{k3s-storage,media,photos,databases,registry}
sudo chown -R $USER:$USER /data
1.4 Two-Disk Setup (if applicable)
If you have two SSDs, use the second one entirely for /data:
lsblk # identify the second disk
sudo pvcreate /dev/sdb
sudo vgcreate data-vg /dev/sdb
sudo lvcreate -l 80%FREE -n data-lv data-vg
sudo mkfs.ext4 /dev/data-vg/data-lv
sudo mkdir -p /data
echo '/dev/data-vg/data-lv /data ext4 defaults 0 2' | sudo tee -a /etc/fstab
sudo mount -a
Leave 20% of the VG free for snapshots and future growth.
2. K3s Installation
2.1 Install
curl -sfL https://get.k3s.io | sh -s - \
--data-dir /data/k3s \
--write-kubeconfig-mode 644
--data-dir /data/k3s— stores containerd images and etcd on your data volume--write-kubeconfig-mode 644— lets your non-root user read the kubeconfig
Verify:
kubectl get nodes
# Should show your node as Ready
kubectl get pods -A
# Should show traefik, coredns, local-path-provisioner, etc.
2.2 kubectl Setup
echo 'export KUBECONFIG=/etc/rancher/k3s/k3s.yaml' >> ~/.bashrc
source ~/.bashrc
kubectl cluster-info
2.3 Configure Storage
Redirect the local-path-provisioner to your data volume:
kubectl edit configmap local-path-config -n kube-system
Change config.json to:
{
"nodePathMap": [{
"node": "DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths": ["/data/k3s-storage"]
}]
}
2.4 Custom StorageClasses
For directing workloads to specific paths:
# storage-classes.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-media
provisioner: rancher.io/local-path
parameters:
nodePath: /data/media
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-databases
provisioner: rancher.io/local-path
parameters:
nodePath: /data/databases
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-photos
provisioner: rancher.io/local-path
parameters:
nodePath: /data/photos
kubectl apply -f storage-classes.yaml
2.5 Resource Overhead
| Component | RAM | CPU |
|---|---|---|
| K3s control plane | ~500-750 MB | ~5-6% of one core |
| Traefik | ~50-100 MB | minimal |
| CoreDNS | ~20-30 MB | minimal |
| local-path-provisioner | ~10-20 MB | minimal |
| Total baseline | ~600-900 MB | ~6-8% |
3. Traefik Ingress Configuration
K3s ships Traefik v3 as the default ingress controller. It’s already running.
3.1 Customise Traefik
# traefik-config.yaml
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
api:
dashboard: true
insecure: false
logs:
general:
level: INFO
ports:
websecure:
tls:
enabled: true
sudo cp traefik-config.yaml /var/lib/rancher/k3s/server/manifests/
# K3s auto-applies manifests placed here
3.2 Local DNS Setup
For host-based routing (jellyfin.home, grafana.home), add entries on your client machines:
# /etc/hosts on your laptop/desktop
SERVER_IP jellyfin.home grafana.home immich.home traefik.home torrent.home photos.home myapp.home
Or configure your router’s DNS for wildcard resolution if supported.
3.3 Traefik Dashboard
# traefik-dashboard.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: dashboard-auth
namespace: kube-system
spec:
basicAuth:
secret: dashboard-auth-secret
---
apiVersion: v1
kind: Secret
metadata:
name: dashboard-auth-secret
namespace: kube-system
type: kubernetes.io/basic-auth
stringData:
username: admin
password: CHANGE_THIS_PASSWORD
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: kube-system
spec:
entryPoints:
- websecure
routes:
- match: Host(`traefik.home`)
kind: Rule
middlewares:
- name: dashboard-auth
services:
- name: api@internal
kind: TraefikService
tls: {}
kubectl apply -f traefik-dashboard.yaml
Visit https://traefik.home to see all routes and services.
4. Verify Everything Works
# Node status
kubectl get nodes
# All system pods running
kubectl get pods -A
# Storage classes available
kubectl get storageclass
# Traefik listening
kubectl get svc -n kube-system traefik
# Data directory structure
ls -la /data/
The cluster is ready. Next posts deploy services into it:
- Part 4: App Hosting — custom projects with API + frontend + DB
- Part 5: Monitoring — Grafana + Prometheus
- Part 6: Photos — Immich
- Part 7: Media Server — Jellyfin
- Part 8: Torrent Service — qBittorrent + VPN