Build a home Kubernetes cluster: part 1, k3s server
This guide installs the control plane for a small home Kubernetes cluster on an x86_64 Ubuntu NAS. Raspberry Pi 5 machines will join it as worker nodes in the next part of the series. The NAS remains the only k3s server and does not run ordinary application workloads.
Target environment: Ubuntu Server 26.04 LTS on x86_64 and k3s v1.36.2+k3s1. Validate the procedure on the NAS before changing this note to Tested on.
This is deliberately a single-control-plane design. It is small and easy to operate, but the Kubernetes API and all cluster state become unavailable when the NAS is offline. Existing containers on worker nodes may continue running, but Kubernetes cannot schedule or reconcile workloads until the NAS returns.
Architecture
The finished platform will keep three different kinds of storage separate:
| Storage | Location | Purpose |
|---|---|---|
| k3s state | NAS system SSD | Kubernetes API state and configuration |
| Garage | NAS storage pool | S3-compatible object storage for backups, logs, traces, and artifacts |
| NFS | NAS storage pool | Persistent volumes for Kubernetes applications |
Garage is not a Kubernetes persistent-volume backend. Applications that need an S3 API use Garage directly over the LAN; applications that need a mounted filesystem request an NFS-backed persistent volume.
The first installation keeps only the k3s components that are needed now:
- SQLite is the embedded datastore for the single server;
- Flannel VXLAN provides the pod network;
- CoreDNS provides cluster DNS;
- metrics-server provides basic resource metrics;
- Traefik and ServiceLB remain disabled until ingress is designed;
- local-path provisioner remains disabled because persistent volumes will come from the NAS over NFS.
The server node is tainted with NoSchedule. System components can still run
there when they have the appropriate toleration, but ordinary workloads will
be placed on the Raspberry Pi workers.
Prerequisites
The examples assume:
- the NAS has a static address, represented by
NAS_IP; - the local network is represented by
LAN_CIDR, for example192.168.50.0/24; k3s.home.arparesolves toNAS_IPin local DNS;- the default pod and service networks,
10.42.0.0/16and10.43.0.0/16, do not overlap the LAN, VPN, or Docker networks; - the system SSD has persistent space under
/var/lib/rancher/k3s; - all cluster machines have unique hostnames and synchronized clocks.
Do not place the k3s data directory on NFS. The embedded datastore belongs on local reliable storage, preferably an SSD. The NAS storage pool can still hold backups of that state.
Check the host before installing anything:
|
|
The architecture must be x86_64, the NAS address must be stable, and time
synchronization must be active.
Install the small set of tools used by this guide:
|
|
Prepare the network
With the default Flannel VXLAN backend, every cluster node must be able to
reach every other cluster node on UDP port 8472. Worker nodes also need TCP
port 6443 on the NAS for the Kubernetes API. These ports must never be
exposed to the Internet.
| Traffic | Protocol and port | Source | Destination |
|---|---|---|---|
| Kubernetes API | TCP 6443 | Cluster nodes and administrator LAN | NAS |
| Flannel VXLAN | UDP 8472 | Every cluster node | Every cluster node |
If a host firewall is active, create equivalent rules limited to LAN_CIDR.
Also allow traffic from the pod and service CIDRs according to the firewall’s
forwarding model. Do not blindly enable a new firewall over a remote SSH
session: verify that its SSH rule is present first.
Confirm that the chosen address and local DNS record agree:
|
|
After the server is installed, the API port can be checked from another LAN machine with:
|
|
Create the k3s configuration
k3s loads /etc/rancher/k3s/config.yaml on every start. Keeping durable
settings in this file is easier to audit than hiding them in a long installer
command.
Create the directory and configuration, replacing NAS_IP with the real
static address:
|
|
Replace 192.0.2.10 in the sed command with the NAS address. The example
address belongs to the documentation-only TEST-NET-1 range and will not work
on a LAN.
tls-san allows administrators and workers to use either the stable address
or the local DNS name without a certificate-name error. Secret encryption
protects Kubernetes Secret values in the datastore if its files are copied or
backed up without authorization.
Install a pinned k3s release
Pinning the release makes the initial deployment reproducible. Upgrades should be a separate, deliberate operation after the current release notes and backup have been checked.
Download the official installer and inspect its source before running it:
|
|
Install k3s v1.36.2+k3s1 as a systemd service:
|
|
The installer downloads the matching binary and checksum, creates the k3s
service, enables it at boot, and starts it. It also installs kubectl,
crictl, and ctr command links when they do not conflict with existing
commands.
Verify the control plane
Wait for the API and the packaged components to become ready:
|
|
Expected results:
k3s-serverisReadyand has thecontrol-planeandmasterroles;- CoreDNS and metrics-server become
Running; - there are no Traefik or ServiceLB pods;
- no default StorageClass exists yet.
Confirm the pinned versions and node taint:
|
|
The taint must include:
|
|
Check the service log if a component does not become ready:
|
|
Configure kubectl on an administrator machine
The server kubeconfig contains both administrator credentials and an endpoint that initially points to localhost. Treat it as a secret.
On the NAS, make a temporary user-readable copy:
|
|
Transfer it over an authenticated channel to
~/.kube/home-k3s.yaml on the administrator machine, then delete the temporary
copy from the NAS:
|
|
On the administrator machine:
|
|
Anyone who has this file has full administrator access to the cluster. Never commit it to a repository or store it in an unencrypted note.
Protect the cluster state
The default datastore for a single k3s server is SQLite. Its database, server
token, certificates, and encryption configuration live below
/var/lib/rancher/k3s/server. A file-level copy of a live SQLite database is
not a reliable backup, so stop k3s briefly before making the initial archive:
|
|
Store the archive outside the NAS and protect it like the kubeconfig: it contains credentials that control the cluster. A later part of the series will add recurring workload backups with Velero, but Velero does not replace a backup of the k3s server state.
Retrieve the worker join token
The next guide will use the agent token to attach Raspberry Pi workers. Display it only when needed:
|
|
Do not publish or commit this value. A machine with network access to the API and a valid token can join the cluster.
At this point the NAS provides a minimal, persistent k3s control plane. The next step is to prepare Raspberry Pi 5 nodes, install the same k3s release as agents, and verify scheduling and pod networking across architectures.
Recovery and removal
Restart k3s after changing /etc/rancher/k3s/config.yaml:
|
|
The installer creates an uninstall script. Running it deletes the local k3s service, containers, datastore, and cluster configuration from this server. Use it only when intentionally rebuilding the control plane:
|
|
Uninstalling the only server destroys the Kubernetes cluster state. Preserve a verified backup before using this command.