K8s single node setup for CoCo
How to setup a single node k8s cluster using kubeadm and containerd on a Linux machine for testing and developing with confidentail containers.
This tutorial sets up a single machine as both control-plane + worker, using kubeadm (the standard Kubernetes bootstrapping tool) and containerd (recommended runtime). It’s the closest thing to a “real” DIY cluster you’ll run on one box.
Assumptions:
- Ubuntu/Debian-like Linux (examples use
apt)- You have
sudoaccess- You want a single-node cluster that can run workloads (not just control plane)
0. Prep the machine
Set a stable hostname (recommended)
sudo hostnamectl set-hostname k8s-singleDisable swap (required by kubelet)
sudo swapoff -a
sudo sed -i.bak '/\sswap\s/s/^/#/' /etc/fstabLoad kernel modules + sysctl for Kubernetes networking
cat <<'EOF' | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<'EOF' | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system1. Install containerd (container runtime)
Kubernetes needs a container runtime; containerd is a common choice.
Install + enable
sudo apt-get update
sudo apt-get install -y containerd
sudo systemctl enable --now containerdConfigure containerd to use systemd cgroups (recommended)
Kubeadm clusters work best when the runtime cgroup driver matches kubelet (often systemd).
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
# Set SystemdCgroup = true
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd2. Install kubeadm / kubelet / kubectl
Follow the official “Installing kubeadm” instructions for your distro/version.
On Ubuntu/Debian this typically means:
-
add the Kubernetes package repo for your target version
-
install:
-
kubelet (node agent)
-
kubeadm (cluster bootstrap)
-
kubectl (CLI)
-
After installing, verify:
kubeadm version
kubectl version --clientThe output should be similar to:
$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"34", EmulationMajor:"", EmulationMinor:"", MinCompatibilityMajor:"", MinCompatibilityMinor:"", GitVersion:"v1.34.1", GitCommit:"93248f9ae092f571eb870b7664c534bfc7d00f03", GitTreeState:"clean", BuildDate:"2025-09-09T19:43:15Z", GoVersion:"go1.24.6", Compiler:"gc", Platform:"linux/amd64"}
$ kubectl version --client
Client Version: v1.34.1
Kustomize Version: v5.7.13. Bootstrap the single-node cluster (control plane)
Pick a Pod network CIDR that matches your CNI choice.
- For Calico, a common choice is 192.168.0.0/16.
Initialize
sudo kubeadm init --pod-network-cidr=192.168.0.0/16
kubeadm init creates the control plane node.Configure kubectl for your user
Run the exact commands printed at the end of kubeadm init, typically:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/configCheck node status:
kubectl get nodes4. Install a CNI (pod network) — example: Calico
Kubernetes won’t schedule pods normally until a CNI is installed.
Install Calico (manifest method)
curl -LO https://raw.githubusercontent.com/projectcalico/calico/v3.31.3/manifests/calico.yaml
kubectl apply -f calico.yamlCalico’s docs note that if you use the default 192.168.0.0/16, you usually don’t need to edit the manifest.
Wait for pods:
kubectl get pods -A --watch5. Make it a true single-node cluster (schedule workloads on control plane)
By default, control-plane nodes repel normal workloads via a taint. You remove it for single-node setups.
kubectl taint nodes --all node-role.kubernetes.io/control-plane-Confirm:
kubectl get nodes -o wideIf everything was completed, run a quick test.