kubeadm is the official tool for bootstrapping a standard (“vanilla”) Kubernetes cluster. It’s the best way to learn how Kubernetes really fits together, and it produces a conformant cluster you can run production workloads on. This guide builds a cluster with one control-plane node and one or more workers on Ubuntu VPS servers.
If you want a single-node or small cluster with minimal effort, k3s is simpler. And for one server running a few apps, Docker Compose is often all you need.
Requirements
- 2+ Ubuntu 22.04/24.04 servers (KVM — containers like OpenVZ won’t work).
- Control plane: 2 vCPU and 2 GB RAM minimum (4 GB recommended). Workers: depends on your workloads.
- Network connectivity between nodes, ideally on a private network or VPN, with unique hostnames and MAC addresses.
- Ports: control plane needs 6443 (API), 2379-2380 (etcd), 10250-10259; workers 10250 and 30000-32767 (NodePorts). Allow them between nodes only.
Kubernetes moves fast — check the current version and adjust v1.33 below to the release you want.
1. Prepare every node
Disable swap (the kubelet requires it off by default):
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
Kernel modules and sysctl settings for container 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 --system
2. Install containerd (every node)
sudo apt update && sudo apt install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
SystemdCgroup = true makes containerd use the same cgroup driver as the kubelet — a common source of instability if mismatched. (The containerd from Docker’s repository, containerd.io, works too.)
3. Install kubeadm, kubelet and kubectl (every node)
sudo apt install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
Holding the packages prevents unplanned upgrades — Kubernetes upgrades follow a specific procedure.
4. Initialise the control plane
On the control-plane node (use its private IP if nodes talk over a private network):
sudo kubeadm init --apiserver-advertise-address=10.0.0.10 --pod-network-cidr=192.168.0.0/16
When it finishes, set up kubectl for your user:
mkdir -p $HOME/.kube
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodes
The node shows NotReady until a network plugin is installed. Save the kubeadm join command printed at the end.
5. Install a CNI network plugin
Pods need a network add-on. Calico is a popular choice (its default pod CIDR matches the one above):
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.0/manifests/calico.yaml
kubectl get pods -n kube-system -w
Check the Calico docs for the current version. Alternatives: Cilium, Flannel. Once the CNI pods are running, the node turns Ready.
6. Join worker nodes
On each worker, run the join command from step 4:
sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:…
Lost it or the token expired (24 h)? Generate a new one on the control plane:
kubeadm token create --print-join-command
Back on the control plane:
kubectl get nodes -o wide
7. Deploy something
kubectl create deployment hello --image=nginx --replicas=2
kubectl expose deployment hello --port=80 --type=NodePort
kubectl get svc hello
curl http://<worker-ip>:<nodeport>
For real HTTP traffic you’ll want an ingress controller (ingress-nginx) and cert-manager for TLS — see ingress-nginx and cert-manager. Persistent storage needs a storage class — see persistent volumes.
Production considerations
- High availability: a single control plane is a single point of failure. HA needs three control-plane nodes and a load balancer for the API.
- Backups: back up etcd (
etcdctl snapshot save) and your manifests (GitOps). - Upgrades: one minor version at a time with
kubeadm upgrade plan/apply. - Security: keep the API server off the public internet or restrict it by IP; use RBAC and network policies.
Learn the everyday commands with the kubectl cheat sheet. Our KVM VPS plans support everything kubeadm needs — kernel modules, containerd and full root access.