Home · About · Services · Industries · Projects · Products · Process · Blog · FAQ · Contact
k3s for small production clusters: when it fits and how to run it When k3s is the right Kubernetes for a small production cluster, the HA layouts that work, install and backup config, and the cases where we pick something…
A team has outgrown a single Docker Compose server. They now run a dozen services, want rolling deploys without hand-written scripts, and need a node failure to stop being an outage. They do not want to pay for a managed control plane or learn kubeadm. Someone suggests k3s: is a lightweight distribution safe for production, or only for home labs?
Short answer: k3s fits small production clusters well when you run three to roughly a dozen nodes you control, on VPS, bare metal or edge hardware, and a small team operates it. It is certified, upstream-compatible Kubernetes in one binary. It fits poorly when you need managed upgrades, heavy stateful workloads or deep cloud integration.
Below: what k3s changes, the layouts we trust in production, install and backup config, and when we pick something else. If you are still deciding whether you need Kubernetes at all, read our post on when we don't recommend Kubernetes first.
What k3s is, and what it leaves out k3s is a CNCF-certified Kubernetes distribution, originally built by Rancher and now a CNCF project. Your manifests, Helm charts and kubectl commands work unchanged. The difference is packaging: the API server, scheduler, controller manager, kubelet and kube-proxy run inside one process, with containerd as the runtime. It removes in-tree cloud provider and storage drivers, which most teams replace with external ones anyway.
It also ships with defaults that a bare cluster would otherwise need you to choose and install:
Flannel for pod networking (VXLAN by default) and an embedded network policy controller. CoreDNS, metrics-server and a Helm controller that applies charts dropped into a manifests folder. Traefik as the ingress controller. ServiceLB, which publishes LoadBalancer services on every node's own IP using host ports. local-path-provisioner for persistent volumes on the node's own disk. Kine, a shim that stores cluster state in SQLite (the single-server default), PostgreSQL or MySQL, plus embedded etcd for HA. The published minimums are modest: 2 CPU cores and 2 GB RAM for a server node, 1 core and 512 MB for an agent. Treat those as the floor for the cluster itself. Real headroom for your workloads comes on top.
Production layouts that actually work The datastore choice decides most of your reliability. These are the layouts we consider sensible for small production use:
Single server with SQLite, plus agents. Simple and cheap, but the control plane is a single point of failure. Running pods keep serving if the server dies, but nothing can be rescheduled or deployed until it returns. Acceptable for internal tools with a tested restore. Three servers with embedded etcd. Our default for production. etcd needs a quorum, so use an odd number: three servers tolerate one failure. Servers can also run workloads in a small cluster. Two or more servers with an external PostgreSQL or MySQL datastore. Useful if you already run a well-managed, backed-up database. The cluster is then only as available as that database. Two servers with embedded etcd is a trap: losing either one loses quorum. Use one or three. Keep the servers in the same region with low latency between them, because etcd is sensitive to slow disks and slow links. SSD or NVMe storage is not optional for etcd.
Put a fixed registration address in front of the servers, either a small TCP load balancer or a DNS name, and add it to the API certificate with tls-san. Agents and kubectl then survive a server replacement.
Installing a three-server cluster We keep configuration in /etc/rancher/k3s/config.yaml rather than long install flags, so it lives in version control and survives reinstalls. Pin the version: the install script defaults to the stable channel, which moves over time.
# /etc/rancher/k3s/config.yaml on the first server
cluster-init: true
token: "<long random secret>"
tls-san:
- k8s.example.com
node-ip: 10.0.0.11
flannel-backend: wireguard-native
secrets-encryption: true
write-kubeconfig-mode: "0600"
etcd-snapshot-schedule-cron: "0 */6 * * *"
etcd-snapshot-retention: 20
disable:
- servicelb# first server
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="<pinned version>" sh -s - server
# second and third servers: same file with their own node-ip,
# and cluster-init replaced by
# server: https://k8s.example.com:6443
# agents
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="<pinned version>" \
K3S_URL=https://k8s.example.com:6443 K3S_TOKEN="<token>" sh -s - agent
sudo kubectl get nodes -o wideA few of those choices deserve explanation. node-ip makes flannel use the private network rather than the public interface. wireguard-native encrypts pod traffic between nodes, which matters when your VPS provider's private network is shared or nodes sit in different locations. secrets-encryption encrypts Secrets at rest in the datastore. We disable ServiceLB when a provider load balancer or MetalLB will handle external traffic, and leave it on for very small clusters.
At the firewall, servers need 6443/tcp for the API (agents use it too) and 2379-2380/tcp between servers for etcd. Every node needs 10250/tcp for the kubelet and 51820/udp for WireGuard (8472/udp if you stay on VXLAN). Restrict all of these to the private network. Only 80 and 443 should face the internet, and 6443 only from your admin network or VPN.
Backups and restores With embedded etcd, k3s takes snapshots automatically: every 12 hours by default, keeping five, under /var/lib/rancher/k3s/server/db/snapshots. That local copy disappears with the disk, so we ship snapshots off the node with the built-in S3 support, which works with MinIO or any S3-compatible store.
# extra lines in config.yaml
etcd-s3: true
etcd-s3-endpoint: s3.example.com
etcd-s3-bucket: k3s-snapshots
etcd-s3-folder: prod
# credentials via etcd-s3-access-key / etcd-s3-secret-key
# on-demand snapshot before an upgrade
k3s etcd-snapshot save --name pre-upgrade
# restore: stop k3s on every server, then on the first one
k3s server --cluster-reset \
--cluster-reset-restore-path=<snapshot file>
# then start k3s there; on the other servers delete
# /var/lib/rancher/k3s/server/db and start them to rejoinTwo things catch people out. First, a snapshot restore needs the original server token, so store it with your secrets, not only on the node. Second, etcd snapshots hold cluster state only. Persistent volume data needs its own backups, done at the application level with tools like pg_dump or with a volume backup tool such as Velero. Test a full restore onto fresh machines at least once before you call the cluster production.
Storage, ingress and upgrades local-path-provisioner is the default storage class, and it is the default that most often hurts. A volume lives on one node's disk. If that node dies, the data is gone and the pod cannot move. That is fine for caches and scratch space. For databases we usually recommend a managed database, or a database on its own server outside the cluster, rather than replicated block storage like Longhorn on a three-node cluster.
The bundled Traefik is a reasonable ingress controller and pairs well with cert-manager for Let's Encrypt certificates. You can customise it with a HelmChartConfig resource rather than editing its manifest, which k3s would overwrite. If your team already knows ingress-nginx, note that the upstream project has been retired, so check the current state of any controller before standardising on it.
Upgrades are where small clusters drift. Kubernetes minor versions get about 14 months of upstream patch support, and you should move one minor version at a time. k3s supports automated upgrades through the system-upgrade-controller, which applies Plan resources node by node: servers first, then agents. We pin to a specific release in the plan rather than following a channel, upgrade staging first, and take a snapshot before every change.
When k3s is the wrong choice k3s removes installation work, not the operational load of Kubernetes. Pick something else when:
You are already on AWS or Azure and want the provider to run the control plane. EKS or AKS cost more per month but take upgrades and etcd off your hands, and integrate with IAM and load balancers. Your workload fits on one or two servers. Docker Compose, or a platform like Dokploy, is simpler to run and debug. Nobody on the team will own the cluster. Unpatched k3s is still unpatched Kubernetes, with all the exposure that brings. You need large node counts or strict multi-tenancy. At that size a managed service or a vendor-supported distribution is usually worth it. Most of your workloads are stateful and you have no plan for volumes beyond local-path. k3s suits the middle ground: a few VPS or on-premises machines, a campus or office server room, edge sites, staging environments that should match production, or a self-hosted setup where cloud managed Kubernetes is not an option or not worth the cost.
A production readiness checklist Before we put real traffic on a k3s cluster, we check these:
One or three servers with embedded etcd (never two), on SSD or NVMe, with a fixed registration address in tls-san. Version pinned, config in /etc/rancher/k3s/config.yaml and in version control, token stored in a secrets manager. Cluster ports restricted to the private network, API reachable only from admin networks or VPN. etcd snapshots shipped off-node to S3-compatible storage, and a full restore tested on clean machines. Application data backed up separately from cluster state, with databases outside the cluster where practical. Secrets encryption on, kubeconfig file permissions tight, RBAC set up so humans and CI do not share an admin credential. Monitoring for node resources, etcd health, certificate expiry and pod restarts, with alerts someone actually reads. An upgrade plan using system-upgrade-controller, tested on staging, one minor version at a time. If you can tick every item, k3s is a sound base for a small production cluster. If several are blank and nobody has time to fill them, that is a sign to choose a managed service, or to stay on simpler infrastructure for a while longer.
Infigini IT Services · 11 Kanwali Road, Dehradun 248001, India · info@infigini.com · +91 95684 87789