MicroK8s is a lightweight, conformant Kubernetes distribution from Canonical, delivered as a single snap package. Extra components such as ingress, storage, a container registry or observability are switched on as addons with one command. In this tutorial you will install MicroK8s on Ubuntu 24.04, use it without sudo, enable the addons most workloads need, deploy a test application behind the ingress controller and, optionally, join more nodes to form a cluster.

Prerequisites

To follow this tutorial, you will need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 2 vCPU, 4 GB of RAM and 20 GB of free disk space.
  • A non-root user with sudo privileges.
  • snapd, which is installed by default on Ubuntu 24.04.
  • Optionally, one or two more Ubuntu 24.04 servers if you want to build a multi-node cluster in Step 6.

Step 1 - Installing MicroK8s

MicroK8s publishes one snap channel per Kubernetes minor version. List the available channels to see which versions you can install:

snap info microk8s | sed -n '/channels:/,$p' | head -n 12

Install MicroK8s from the default stable channel. The --classic flag is required because MicroK8s needs full access to the host to run containers:

sudo snap install microk8s --classic
microk8s (1.xx/stable) v1.xx.x from Canonical✓ installed

Wait until all core services report ready:

sudo microk8s status --wait-ready
microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    dns                  # (core) CoreDNS
    ha-cluster           # (core) Configure high availability on the current node
    helm                 # (core) Helm - the package manager for Kubernetes
    helm3                # (core) Helm 3 - the package manager for Kubernetes
  disabled:
    ...

Recent releases enable the dns addon by default. If it shows under disabled, you will enable it in Step 3.

Step 2 - Running MicroK8s without sudo

MicroK8s creates a microk8s group whose members can run its commands without sudo. Add your user to it and create the directory that kubectl uses for its cache:

sudo usermod -a -G microk8s "$USER"
mkdir -p ~/.kube
chmod 0700 ~/.kube

Group changes apply to new login sessions. Start a shell with the new group, or log out and back in:

newgrp microk8s

Verify access by listing the node:

microk8s kubectl get nodes
NAME        STATUS   ROLES    AGE   VERSION
microk8s1   Ready    <none>   3m    v1.xx.x

Typing microk8s kubectl gets tedious. If you have no other kubectl installed, create a snap alias so kubectl points to the bundled client:

sudo snap alias microk8s.kubectl kubectl

The rest of this tutorial uses kubectl.

Step 3 - Enabling the core addons

A fresh MicroK8s node has DNS but no storage provisioner or ingress controller. Enable the three addons most applications rely on:

microk8s enable dns
microk8s enable hostpath-storage
microk8s enable ingress
  • dns deploys CoreDNS so pods can resolve Services by name.
  • hostpath-storage creates the default StorageClass microk8s-hostpath, which stores volumes in a directory on the node.
  • ingress deploys the NGINX ingress controller, which listens on ports 80 and 443 of every node.

Check that the addons are running:

kubectl get pods -A
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
ingress       nginx-ingress-microk8s-controller-xxxxx      1/1     Running   0          60s
kube-system   calico-kube-controllers-xxxxxxxxxx-xxxxx     1/1     Running   0          6m
kube-system   calico-node-xxxxx                            1/1     Running   0          6m
kube-system   coredns-xxxxxxxxxx-xxxxx                     1/1     Running   0          6m
kube-system   hostpath-provisioner-xxxxxxxxxx-xxxxx        1/1     Running   0          90s

Confirm the StorageClass exists:

kubectl get storageclass
NAME                          PROVISIONER            RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
microk8s-hostpath (default)   microk8s.io/hostpath   Delete          WaitForFirstConsumer   false                  2m

Run microk8s status at any time to see the full list of enabled and available addons. Other useful ones are metrics-server (enables kubectl top), registry (a private image registry on port 32000), cert-manager and observability (Prometheus, Grafana and Loki).

Step 4 - Allowing traffic through the firewall

If UFW is enabled, MicroK8s pods need routed traffic to be allowed, and the ingress controller needs HTTP and HTTPS:

sudo ufw allow OpenSSH
sudo ufw default allow routed
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

If you want to use kubectl from your workstation, also allow the API server port, ideally only from your own IP. Replace your_admin_ip with it:

sudo ufw allow from your_admin_ip to any port 16443 proto tcp

Verify the rules:

sudo ufw status verbose
Status: active
Default: deny (incoming), allow (outgoing), allow (routed)
...

Step 5 - Deploying a test application

Deploy Nginx with two replicas, expose it as a Service and publish it through the ingress controller. This checks scheduling, cluster DNS, Service networking and ingress in one go.

kubectl create deployment hello --image=nginx:stable --replicas=2
kubectl expose deployment hello --port=80

Create the Ingress manifest:

nano hello-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello
spec:
  ingressClassName: public
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello
                port:
                  number: 80

MicroK8s registers its NGINX controller under the IngressClass public (with nginx as an alias class). Check the class name on your install and apply the manifest:

kubectl get ingressclass
kubectl apply -f hello-ingress.yaml

Test it from any machine, replacing your_server_ip with the public IP of the node:

curl -I http://your_server_ip
HTTP/1.1 200 OK
Content-Type: text/html
...

To test persistent storage, create a PersistentVolumeClaim:

nano data-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
kubectl apply -f data-pvc.yaml
kubectl get pvc data
NAME   STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
data   Pending                                      microk8s-hostpath   5s

Pending is expected when the StorageClass uses the WaitForFirstConsumer binding mode shown in Step 3: the volume is created only when a pod first uses the claim, and the status then changes to Bound.

Clean up the test resources:

kubectl delete -f hello-ingress.yaml -f data-pvc.yaml
kubectl delete service,deployment hello

Step 6 - Joining additional nodes (optional)

MicroK8s forms a cluster with a join token generated on an existing node. With three or more full nodes, the ha-cluster addon automatically replicates the datastore for high availability.

First, install MicroK8s on each new node as in Step 1, using the same channel. Between the nodes, allow the cluster ports. Replace node_ip with the IP of each peer node and run these on every node:

sudo ufw allow from node_ip to any port 16443 proto tcp
sudo ufw allow from node_ip to any port 25000 proto tcp
sudo ufw allow from node_ip to any port 19001 proto tcp
sudo ufw allow from node_ip to any port 10250 proto tcp
sudo ufw allow from node_ip to any port 4789 proto udp

On the first node, generate a join command:

microk8s add-node
From the node you wish to join to this cluster, run the following:
microk8s join 203.0.113.10:25000/2f1c3e.../a8b9c0...

Use the '--worker' flag to join a node as a worker not running the control plane, eg:
microk8s join 203.0.113.10:25000/2f1c3e.../a8b9c0... --worker
...

Each token is single use. On the new node, run the printed command. Add --worker if the node should only run workloads:

sudo microk8s join 203.0.113.10:25000/2f1c3e.../a8b9c0... --worker

Back on the first node, confirm the new node is Ready:

kubectl get nodes
NAME        STATUS   ROLES    AGE   VERSION
microk8s1   Ready    <none>   40m   v1.xx.x
microk8s2   Ready    <none>   50s   v1.xx.x

To remove a node later, run sudo microk8s leave on that node and then microk8s remove-node node_name on a remaining node.

Step 7 - Accessing the cluster from your workstation

MicroK8s can print a kubeconfig with admin credentials for the cluster:

microk8s config > microk8s-kubeconfig

Copy the file to your workstation (for example with scp), make sure its server: line points to https://your_server_ip:16443, and use it:

KUBECONFIG=./microk8s-kubeconfig kubectl get nodes

Troubleshooting

microk8s status says it is not running. Run sudo microk8s inspect. It checks the services, collects logs into a tarball and prints warnings such as missing IP forwarding or firewall issues.

Insufficient permissions to access MicroK8s. Your shell does not have the microk8s group yet. Run newgrp microk8s or log in again.

The ingress returns connection refused. Check that the ingress pod is Running in the ingress namespace and that ports 80 and 443 are allowed in UFW and in any provider firewall.

Pods cannot resolve DNS names. Make sure the dns addon is enabled and that sudo ufw default allow routed is set.

Removing MicroK8s. sudo snap remove microk8s --purge removes the snap and all cluster data.

Conclusion

You installed MicroK8s on Ubuntu 24.04, configured non-root access, enabled DNS, storage and ingress, verified them with a test application and learned how to grow the node into a cluster. Next, you can enable the cert-manager addon to issue TLS certificates for your ingresses, enable observability for metrics and logs, or deploy applications with the bundled Helm using microk8s helm.