# Running k0s with Private Worker Nodes (Ingress on Control Plane, Workloads on Workers)

Whenever using **AKS**, **EKS**, **k3s**, or even full Kubernetes before, you have probably seen clusters where **every node gets a public IP**. That works, but it’s not ideal, especially if you care about security or you are running a homelab / small cloud setup.

Recently, I experimented with **k0s**, a lightweight Kubernetes distribution. During the setup, I wanted something specific:

* **Control Plane node**: Public IP, runs API Server + Ingress
    
* **Worker nodes**: **No public IP**: completely private
    
* Applications run only on workers
    
* Everything still reachable through Ingress
    

It turns out: **k0s makes this surprisingly clean**.

###   
What is k0s, and how is it different from k3s?

For **k3s**, you know the model:

* `k3s server` = control plane + worker components bundled
    
* `k3s agent` = worker node
    

Everything is tightly packaged. Convenient, but sometimes too tightly.

  
**k0s works differently**:

| Thing | k0s | k3s |
| --- | --- | --- |
| Control Plane | Runs only on **controller nodes** | Runs on `server` nodes (server = controller + worker unless told otherwise) |
| Worker Nodes | Run **only workload components** | `agent` nodes run workloads |
| etcd | Embedded or external | Also embedded by default |
| Network | kube-router or calico | flannel by default |
| Binary | Single `k0s` binary | Single `k3s` binary but with patched components |

**The key difference**:  
With **k0s**, the **control plane is cleanly separated**.  
Controller nodes *do not* run pods unless you explicitly tell them to.

This is perfect for a setup where the **control plane is accessible** and **worker nodes stay private**.

## Goal Setup

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761329808010/be2c7804-a4e7-4436-8472-4e92a5bc82e6.png align="center")

The worker nodes only need to be able to **reach the controller’s private IP** on port `6443`.  
No public exposure at all.

## Installing the Controller Node

SSH into the controller VM:

```plaintext
curl -sSLf https://get.k0s.sh | sudo sh
sudo k0s install controller
sudo systemctl start k0scontroller
```

Get kubeconfig so you can run `kubectl`:

```plaintext
sudo k0s kubeconfig admin > kubeconfig
export KUBECONFIG=$(pwd)/kubeconfig
```

Generate a join token for workers:

```plaintext
sudo k0s token create --role=worker > mytoken
```

## Adding a Worker Node (Before Removing Public IP)

SSH into the worker node:

```plaintext
curl -sSLf https://get.k0s.sh | sudo sh
sudo k0s install worker --token-file mytoken (We copy the token from controller to here)
sudo systemctl start k0sworker
```

Check status from controller:

```plaintext
k0s kubectl get nodes
```

If you see the worker as `Ready`, you are set!

## Now, Remove the Worker Node Public IP

Because the worker already knows how to reach the control plane, it **doesn’t need to be publicly reachable**.

Remove public IP (depends on your cloud/hypervisor).  
As long as **private-link connectivity stays**, Kubernetes continues working.

### Why does this still work?

Because:

* **Pods talk inside a cluster network (CNI)**
    
* **Ingress runs at the control plane**
    
* Traffic **enters the cluster through the control plane**, not worker nodes
    

### Traffic Flow

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761330289260/f4480bf9-0c04-4ab2-aa10-187e0884cf87.png align="center")

Worker node never needs to accept any public connections.  
Nice and clean.

We can test it out by deploying a sample application, which we can talk about in a different article!

## Why This Setup Is Useful

| Benefit | Why it matters |
| --- | --- |
| **Better security** | Worker nodes are never exposed |
| **Lower cloud cost** | Fewer public IPs |
| **Predictable traffic path** | Ingress is the single entry point |
| **Production-like architecture** | This is how real clusters are designed |

Even cloud-managed Kubernetes hides workers behind private networking, and here we are learning how it is done!

## Final Thoughts

I have used k3s a lot, but k0s seems like the next big thing in small package!  
The control plane feels like a "brain" again, instead of everything being bundled into one binary running everywhere.

If your goal is:

* A secure small cluster
    
* Proper control/data plane separation
    
* Workers without public exposure
    

**k0s is a great fit.**
