How to Install Kubernetes on Ubuntu 26.04 LTS

How to Install Kubernetes on Ubuntu 26.04 LTS
Published on Jul 28, 2026 Updated on Jul 29, 2026

For slightly over a decade, Kubernetes has been the undisputed king of software deployment. Before the Docker and Kubernetes era, applications were built as large, single-unit codebase systems. This monolithic architecture had multiple pain points, including inefficient scaling, low fault tolerance, and a higher risk of downtime in case of a single bug.

With Docker, applications are decoupled into multiple, atomized units running independently inside containers. Kubernetes provides a control layer over Docker by managing these containers.

Kubernetes aggregates multiple servers into a single cluster, allocating resources to the nodes to run the containers. It automates various aspects of container management, including auto-scaling, self-healing, and load-balancing, ensuring high availability and minimal downtime.

In this guide, you will learn how to set up a Kubernetes cluster and deploy a simple application to test the cluster’s functionality. All the nodes will be running Ubuntu 26.04 LTS.

#Prerequisites

For this lab, we have a 2-node setup comprising a master and a worker node. All nodes are running Ubuntu 26.04 LTS.

Master node k8s-master-node 10.190.104.1

Worker node 1 k8s-worker-node 10.190.104.39

Here are the minimum requirements for both nodes:

  • Minimum 2 CPU cores and 2 GB RAM (4 GB recommended)

  • SSH access to both nodes

  • A sudo user configured on all nodes

To follow along, you can create your lab by deploying cloud servers at Cherry Servers

Scalable VPS Hosting

Deploy fast, secure VPS for websites, apps, and dev environments. Scale on demand, manage with full root access, and get 24/7 expert support.

Now that the groundwork is laid, we'll walk through setting up and configuring Kubernetes on a two-node cluster: one master and one worker node. At the tail end, we will deploy a simple web app to test the cluster's functionality.

#Step 1: Disable swap (For all nodes)

Swap is discouraged because it degrades performance in clusters. Accessing memory from swap is significantly slower (up to 1000x slower) than from physical RAM, causing severe performance degradation. Also, Swap interferes with Kubernetes' QoS tiers. Applications expecting immediate, guaranteed resources might suddenly slow down if another process forces swapping.

The general rule of thumb is to disable swap to avoid performance bottlenecks.

Command Line
sudo swapoff -a

This works temporarily. To persist the swap removal across reboots, remove any swap entries from the/etc/fstab file.

Command Line
sudo sed -i '/ swap / s/^/#/' /etc/fstab

Then reboot the system for changes to take effect.

Command Line
sudo reboot

Confirm that swap has been fully disabled:

Command Line
free  -h

The Swap row in the output should show all zeros.

#Step 3: Configure /etc/hosts file (all nodes)

Kubernetes relies on hostnames to distinguish nodes. When a worker node comes online and joins the cluster, it announces itself using its hostname — so if hostname resolution breaks down, nodes lose the ability to locate and talk to one another.

With this in mind, update the /etc/hosts file. The IP and hostname entries give every node a reliable way to resolve and communicate with other nodes.

Command Line
sudo cat >> /etc/hosts << 'EOF'
10.190.104.1		            k8s-master-node
10.190.104.39		            k8s-worker-node
EOF

Try pinging the nodes from each other to ensure they can communicate seamlessly.

On the master node:

Command Line
ping  -c 4    k8s-worker-node

On the worker node:

Command Line
ping  -c 4    k8s-master-node

In both cases, you should get a positive ping reply indicating communication between the nodes.

#Step 4: Load Kernel Modules and Configure Sysctl (all nodes)

Kubernetes networking depends on two kernel modules: overlay (used by containerd for layered filesystems) and br_netfilter (required for bridge networking and iptables rules on bridged traffic). You need to load them and configure them to load automatically on boot.

To do this, first, create a configuration file and specify the overlay and br_netfilter modules.

Command Line
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf

overlay
br_netfilter

EOF

Next, enable the modules using the modprobe command.

Command Line
sudo modprobe overlay
sudo modprobe br_netfilter

Be sure to verify both modules are loaded:

Command Line
lsmod | grep -E "overlay|br_netfilter"

You should see both listed in the output:

Outputbr_netfilter           32768  0
bridge                425984  1 br_netfilter
overlay               233472  0

Next, configure the required sysctl parameters for bridge networking and IP forwarding:

Command Line
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

Then run the command:

Command Line
sudo sysctl --system

To verify all the parameters are active, simply run:

Command Line
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

All three values should return 1.

#Step 5: Install the Container Runtime (containerd)(all nodes)

Kubernetes requires a CRI-compatible container runtime. Containerd is the standard and recommended choice. It ships in Docker's apt repository with the latest stable builds.

But first things first, update the local package index and install the prerequisites.

Command Line
sudo apt update
sudo apt install  ca-certificates curl gnupg -y

The next step is to add Docker's GPG key. Create the keyrings directory into which the GPG key will go.

Command Line
sudo install -m 0755 -d /etc/apt/keyrings

Then download the GPG key and store it in the keyrings directory.

Command Line
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

Next, make the GPG key readable to all by altering its permissions.

Command Line
sudo chmod a+r /etc/apt/keyrings/docker.gpg

Next, add Docker’s apt repository to your sources directory:

Command Line
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Once added, install containerd as follows:

Command Line
sudo apt  update && sudo apt install -y containerd.io

Confirm the installed version:

Command Line
containerd --version

You should get output similar to what we have:

Outputcontainerd containerd.io v2.2.5 e53c7c1516c3b2bff98eb76f1f4117477e6f4e66

#Step 6: Configure containerd for cgroup v2 (all nodes)

cgroups (Control Groups) is a Linux kernel feature that limits, accounts for, and isolates resources, including memory, CPU, network bandwidth, and disk I/O.

Kubernetes relies on cgroups heavily to:

  • Enforce pod resource requests and limits
  • Prevent a single pod from consuming all node resources
  • Report resource usage via kubectl top
  • Enable the kubelet to manage the container lifecycle

cgroupv2 is the second generation of cgroups and comes with improved features such as enhanced memory management and full support for rootless containers.

Even though cgroupv2 is pre-configured, you need to ensure containerd and kubelet use the same cgroup driver (systemd is recommended). To achieve this, begin by generating the default containerd configuration file:

Command Line
sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null

Enable the systemd cgroup driver by changing SystemdCgroup from false to true:

Command Line
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

Confirm the change took effect:

Command Line
grep SystemdCgroup /etc/containerd/config.toml

The output should show:

OutputSystemdCgroup = true

Next, restart and enable containerd to apply the new configuration:

Command Line
sudo systemctl restart containerd
sudo systemctl enable containerd
sudo systemctl is-active containerd

#Step 7: Install kubeadm, kubelet, and kubectl (all nodes)

In this step, we will proceed to install the core Kubernetes packages: kubeadm, kubelet, and kubectl.

Let’s have a quick overview of what each component does:

  • kubeadm: bootstraps the Kubernetes cluster

  • kubelet: runs on every node and manages container workloads

  • kubectl: The command-line tool for interacting with the cluster

Now, download and store the Kubernetes GPG key:

Command Line
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | \
  sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

Next, add the Kubernetes apt repository to the sources directory.

Command Line
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

Once done, update the package index and install kubelet, kubedm and kubectl.

Command Line
sudo apt update
sudo apt install kubelet kubeadm kubectl -y

#Step 8: Initialize the Kubernetes cluster (on the master node)

With all prerequisites in place, bootstrap the control plane using kubeadm init command. The --pod-network-cidr flag sets the IP range for pod networking, which the CNI plugin will use:

Command Line
sudo kubeadm init --pod-network-cidr=10.200.50.0/16

This process pulls the required container images, generates TLS certificates, and starts the control plane components as static pods. It typically takes about one minute.

The output contains instructions on how you can start using your cluster. At the very end, you will see the command to run on worker nodes to join them to the cluster. This command will be different in your case. Copy it and paste it somewhere, as you will need it to join the worker node to the cluster.

Command Line
kubeadm join 84.32.83.177:6443 --token ape2tj.tobkivq4muq5h8e5 \
        --discovery-token-ca-cert-hash sha256:d052f0fe26c62520957f7d083bc8b82f20ff1928ad3980fe4130cba9edd6c659

To finish up, run the following commands to start using the cluster.

Command Line
mkdir -p $HOME/.kube

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

sudo chown $(id -u):$(id -g) $HOME/.kube/config

Be sure to verify the node status:

Command Line
kubectl get nodes

The node will show NotReady at this point. This is expected because no CNI network plugin has been installed yet:

Command Line
NAME         		 STATUS      ROLES           AGE         VERSION
k8s-master-node  	 NotReady   control-plane    4m11s       v1.33.13

#Step 9: Install the Calico CNI plugin (on master node)

CNI (Container Network Interface) is a standard that defines how networking is set up for containers in Kubernetes. Without a CNI plugin, pods cannot communicate with each other. Kubernetes deliberately ships without a built-in network implementation. It defines the rules but delegates the actual networking to a CNI plugin.

Calico is the most widely adopted CNI owing to its numerous benefits. It combines
true network policy enforcement, high-performance routing without overlays, and production-scale reliability making it the default choice for anything beyond basic local development.

To install Calico, begin by installing the Tigera operator from a YAML file

Command Line
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.0/manifests/tigera-operator.yaml

Next, download Calico’s custom-resources file. This is a Calico-specific manifest that defines the initial configuration of your Calico installation through Kubernetes Custom Resource Definitions (CRDs). It tells Calico how to set itself up after its core components are running.

Command Line
curl https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/custom-resources.yaml -O

Next, modify the CIDR of the manifest from the default 192.168.0.0/16 to match your cluster's network subnet.

Command Line
sed -i 's/cidr: 192\.168\.0\.0\/16/cidr: 10.200.50.0\/16/g' custom-resources.yaml

Finally, apply the YAML manifest file.

Command Line
kubectl create -f custom-resources.yaml

Check the node status again:

Command Line
kubectl get nodes

You can retrieve more detailed information using the -o wide option.

Command Line
kubectl get nodes -o wide

The node should now show Ready status:

Command Line
NAME         		 STATUS      ROLES               AGE          VERSION
k8s-master-node  	 Ready         control-plane       8m51s       v1.33.13

#Step 10: Add worker nodes to the cluster (On worker nodes)

To add a worker node to the cluster, paste the kubeadm command that you copied in step 7 when initializing the cluster.

Command Line
kubeadm join 84.32.83.177:6443 --token ape2tj.tobkivq4muq5h8e5 \
        --discovery-token-ca-cert-hash sha256:d052f0fe26c62520957f7d083bc8b82f20ff1928ad3980fe4130cba9edd6c659

You should see a flurry of output on your screen and finally, you’ll get a notification that the node is now part of the cluster.

Back to the master node, verify the worker node has joined the cluster:

Command Line
kubectl get nodes

This time around, you should see two nodes with a Ready status.

Command Line
NAME         		 STATUS      ROLES               AGE          VERSION
k8s-master-node  	 Ready         control-plane       20m           v1.33.13
k8s-worker-node	 Ready         <none>                23s            v1.33.13

The Cluster is now fully set up and ready for application deployment.

#Step 11: Testing the Cluster (on the master node)

In this step, we will deploy a simple Nginx web app to test the cluster’s functionality.

By default, the master/control-plane node is automatically tainted to prevent regular workload pods from being scheduled on it. This is done intentionally to prevent resource contention from additional workloads, which can starve the API server.

To untaint it, run:

Command Line
kubectl taint nodes k8s-master-node node-role.kubernetes.io/control-plane:NoSchedule-

Next, create a namespace in which you’ll deploy the web app. In this case, the namespace is called webtest.

Command Line
kubectl create namespace webtest
kubectl config set-context --current --namespace=webtest

Create a YAML file for the deployment resource. In this case, our file is named web-deployment.yaml.

Command Line
sudo nano web-deployment.yaml

Add the following lines of code to the file.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-app
  namespace: webtest
spec:
  replicas: 2                   # One pod per node
  selector:
    matchLabels:
      app: nginx-app
  template:
    metadata:
      labels:
        app: nginx-app
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: "64Mi"
              cpu: "250m"
            limits:
              memory: "128Mi"
              cpu: "500m"

Launch the deployment as shown.

Command Line
kubectl apply -f web-deployment.yaml

Ensure the pods are running in both the master and worker nodes by running:

Command Line
kubectl get pods -n webtest -o wide

Next, expose the application using a NodePort service. To do this, create a service YAML file.

Command Line
sudo nano web-service.yaml

Add the following lines of code.

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: webtest
spec:
  type: NodePort
  selector:
    app: nginx-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30080       # Access via this port on any node IP

Launch the service.

Command Line
kubectl apply -f web-service.yaml

Then confirm the service is running in the webtest namespace.

Command Line
kubectl get svc -n webtest
OutputNAME           	 TYPE       	CLUSTER-IP        EXTERNAL-IP   	PORT(S)        		AGE
nginx-service   NodePort         10.111.24.99      <none>        		80:30080/TCP  	 10s

Finally, confirm you can access the Nginx application via NodePort using the cURL command from either node.

Command Line
# Via master node
curl http://master-node-ip:30080

# Via worker node
curl http://worker-node-ip:30080

Similarly, you can access the application via a web browser:

Command Line
# Via master node
http://master-node-ip:30080

# Via worker node
http://worker-node-ip:30080

Both should return the Nginx welcome page. This confirms the cluster is running as expected.

#Conclusion

You have successfully set up a fully functional Kubernetes cluster on Ubuntu 26.04. This setup is ideal for learning Kubernetes concepts, developing applications, and testing deployments before moving to production environments.

For your next steps, consider using Helm charts to deploy containerized applications and adding worker nodes for a true multi-node cluster. Additionally, you can integrate open-source stacks like Prometheus and Grafana for cluster observability.

Cloud VPS Hosting

Starting at just $3.51 / month, get virtual servers with top-tier performance.

Share this article

Related Articles

Published on Apr 26, 2026 Updated on May 6, 2026

How to Configure Ingress SSL Certificates in Kubernetes

Kubernetes Ingress SSL guide: secure apps with NGINX Ingress, TLS certificates, and cert-manager. Install, configure, and automate Let’s Encrypt for production.

Read More
Published on Oct 10, 2025 Updated on Mar 9, 2026

OpenShift vs Kubernetes on Bare Metal: Which One to Choose

Compare Kubernetes vs OpenShift on bare metal. Learn how each platform performs, their setup, features, costs, and which suits your infrastructure best.

Read More
Published on Sep 26, 2025 Updated on Nov 7, 2025

How to Create a Kubernetes Cluster with Minikube and Kubeadm

Learn how to create a Kubernetes cluster using Minikube for local development and Kubeadm for production-ready setups with step-by-step guidance.

Read More
No results found for ""
Recent Searches
Navigate
Go
ESC
Exit