Tools: K0S

Tools: K0S

What Is k0s?

Prerequisites

Docker Compose Configuration

Initial Setup

Configuration

Multi-Node Setup (Docker)

Binary Installation (Recommended for Production)

Key Ports

Reverse Proxy

Backup

Troubleshooting

CoreDNS Pods Stuck in Pending

"Cannot run nested containers"

etcd Performance Warnings

Worker Node Not Joining

Disk Pressure Eviction

Verdict

Related k0s is a lightweight, CNCF-certified Kubernetes distribution packaged as a single binary with zero external dependencies. Created by Mirantis, it bundles everything — etcd, CoreDNS, kube-proxy, Metrics Server, and Konnectivity — into one process. It replaces managed Kubernetes services like EKS, GKE, and AKS, giving you a production-grade cluster on your own hardware for free. Official site k0s is a Kubernetes distribution, not a typical web app. Running it in Docker is best suited for testing, development, and single-node homelabs. For production multi-node clusters, the binary installation method (covered below) is recommended. Create a docker-compose.yml file: The --single flag runs both controller and worker in one container — ideal for homelabs. After the container starts, wait 30–60 seconds for all internal components to initialize. Get your kubeconfig to interact with the cluster: You should see one node in Ready state. Check that system pods are running: Expected output includes pods for CoreDNS, kube-proxy, kube-router, and metrics-server. k0s uses a YAML config file at /etc/k0s/k0s.yaml inside the container. Generate and customize it: Key configuration sections: For a controller + separate worker setup: After starting, generate a join token and connect the worker: For production self-hosting, install k0s as a native service: This runs k0s under systemd with automatic restarts and proper resource management. If you want to expose the Kubernetes API externally behind a reverse proxy, use TCP passthrough (not HTTP termination) since the API uses mTLS: For most homelab setups, direct port exposure is simpler. See Reverse Proxy Setup for general guidance. Back up the k0s data directory: For the binary install, back up /var/lib/k0s/. See Backup Strategy for automated approaches. Symptom: kubectl get pods -A shows CoreDNS pods in Pending state.

Fix: This happens when using Docker's custom networks. Use the default bridge network (remove any networks: configuration) or use host networking. Symptom: Pods fail to start with permission errors.Fix: Ensure privileged: true is set in the Docker Compose service. k0s needs elevated privileges to run containerd inside Docker. Symptom: Slow cluster responses, etcd logs show "took too long" warnings.Fix: Use SSD-backed storage for the k0s-data volume. etcd is extremely sensitive to disk latency. Network-attached storage is not suitable. Symptom: Worker starts but doesn't appear in kubectl get nodes.Fix: Ensure port 8132 (Konnectivity) is accessible from worker to controller. Regenerate the join token — tokens expire after 24 hours by default. Symptom: Pods get evicted with "DiskPressure" condition.

Fix: kubelet evicts pods when disk usage exceeds 85%. Ensure at least 15% free disk space on the volume backing /var/lib/k0s. k0s is the best Kubernetes distribution for self-hosters who want a genuine, CNCF-certified cluster without the complexity of kubeadm. It's lighter than k3s in some respects (zero host dependencies, single binary), though k3s has a larger community and more third-party guides. If you want to learn Kubernetes on real hardware or run production workloads at home, k0s is an excellent choice. If you just need to run containers without Kubernetes complexity, Docker Compose or Docker Swarm are simpler. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ services: k0s-controller: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-controller hostname: k0s-controller privileged: true # Required for kubelet to manage pods -weight: 500;">restart: unless-stopped volumes: - k0s-data:/var/lib/k0s # Cluster state and etcd data - pods-log:/var/log/pods # Pod logs tmpfs: - /run # Runtime data ports: - "6443:6443" # Kubernetes API server command: ["k0s", "controller", "--single"] # Single-node mode (controller + worker) volumes: k0s-data: pods-log: services: k0s-controller: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-controller hostname: k0s-controller privileged: true # Required for kubelet to manage pods -weight: 500;">restart: unless-stopped volumes: - k0s-data:/var/lib/k0s # Cluster state and etcd data - pods-log:/var/log/pods # Pod logs tmpfs: - /run # Runtime data ports: - "6443:6443" # Kubernetes API server command: ["k0s", "controller", "--single"] # Single-node mode (controller + worker) volumes: k0s-data: pods-log: services: k0s-controller: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-controller hostname: k0s-controller privileged: true # Required for kubelet to manage pods -weight: 500;">restart: unless-stopped volumes: - k0s-data:/var/lib/k0s # Cluster state and etcd data - pods-log:/var/log/pods # Pod logs tmpfs: - /run # Runtime data ports: - "6443:6443" # Kubernetes API server command: ["k0s", "controller", "--single"] # Single-node mode (controller + worker) volumes: k0s-data: pods-log: -weight: 500;">docker compose up -d -weight: 500;">docker compose up -d -weight: 500;">docker compose up -d # Extract kubeconfig from the running container -weight: 500;">docker exec k0s-controller k0s kubeconfig admin > ~/.kube/config # Verify the cluster is running -weight: 500;">kubectl get nodes # Extract kubeconfig from the running container -weight: 500;">docker exec k0s-controller k0s kubeconfig admin > ~/.kube/config # Verify the cluster is running -weight: 500;">kubectl get nodes # Extract kubeconfig from the running container -weight: 500;">docker exec k0s-controller k0s kubeconfig admin > ~/.kube/config # Verify the cluster is running -weight: 500;">kubectl get nodes -weight: 500;">kubectl get pods -A -weight: 500;">kubectl get pods -A -weight: 500;">kubectl get pods -A # Generate default config -weight: 500;">docker exec k0s-controller k0s config create > k0s.yaml # Edit the config, then mount it # Add to -weight: 500;">docker-compose.yml volumes: # - ./k0s.yaml:/etc/k0s/k0s.yaml:ro # Generate default config -weight: 500;">docker exec k0s-controller k0s config create > k0s.yaml # Edit the config, then mount it # Add to -weight: 500;">docker-compose.yml volumes: # - ./k0s.yaml:/etc/k0s/k0s.yaml:ro # Generate default config -weight: 500;">docker exec k0s-controller k0s config create > k0s.yaml # Edit the config, then mount it # Add to -weight: 500;">docker-compose.yml volumes: # - ./k0s.yaml:/etc/k0s/k0s.yaml:ro services: k0s-controller: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-controller hostname: k0s-controller privileged: true -weight: 500;">restart: unless-stopped volumes: - k0s-controller-data:/var/lib/k0s tmpfs: - /run ports: - "6443:6443" # Kubernetes API - "9443:9443" # k0s join API - "8132:8132" # Konnectivity (controller-worker tunnel) command: ["k0s", "controller"] k0s-worker: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-worker hostname: k0s-worker privileged: true -weight: 500;">restart: unless-stopped volumes: - k0s-worker-data:/var/lib/k0s - pods-log:/var/log/pods tmpfs: - /run depends_on: - k0s-controller volumes: k0s-controller-data: k0s-worker-data: pods-log: services: k0s-controller: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-controller hostname: k0s-controller privileged: true -weight: 500;">restart: unless-stopped volumes: - k0s-controller-data:/var/lib/k0s tmpfs: - /run ports: - "6443:6443" # Kubernetes API - "9443:9443" # k0s join API - "8132:8132" # Konnectivity (controller-worker tunnel) command: ["k0s", "controller"] k0s-worker: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-worker hostname: k0s-worker privileged: true -weight: 500;">restart: unless-stopped volumes: - k0s-worker-data:/var/lib/k0s - pods-log:/var/log/pods tmpfs: - /run depends_on: - k0s-controller volumes: k0s-controller-data: k0s-worker-data: pods-log: services: k0s-controller: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-controller hostname: k0s-controller privileged: true -weight: 500;">restart: unless-stopped volumes: - k0s-controller-data:/var/lib/k0s tmpfs: - /run ports: - "6443:6443" # Kubernetes API - "9443:9443" # k0s join API - "8132:8132" # Konnectivity (controller-worker tunnel) command: ["k0s", "controller"] k0s-worker: image: -weight: 500;">docker.io/k0sproject/k0s:v1.35.1-k0s.1 container_name: k0s-worker hostname: k0s-worker privileged: true -weight: 500;">restart: unless-stopped volumes: - k0s-worker-data:/var/lib/k0s - pods-log:/var/log/pods tmpfs: - /run depends_on: - k0s-controller volumes: k0s-controller-data: k0s-worker-data: pods-log: # Generate worker join token TOKEN=$(-weight: 500;">docker exec k0s-controller k0s token create --role=worker) # Join the worker to the cluster -weight: 500;">docker exec k0s-worker k0s worker $TOKEN # Generate worker join token TOKEN=$(-weight: 500;">docker exec k0s-controller k0s token create --role=worker) # Join the worker to the cluster -weight: 500;">docker exec k0s-worker k0s worker $TOKEN # Generate worker join token TOKEN=$(-weight: 500;">docker exec k0s-controller k0s token create --role=worker) # Join the worker to the cluster -weight: 500;">docker exec k0s-worker k0s worker $TOKEN # Download and -weight: 500;">install -weight: 500;">curl -sSLf https://get.k0s.sh | -weight: 600;">sudo sh # Install as single-node (controller + worker) -weight: 600;">sudo k0s -weight: 500;">install controller --single # Start the -weight: 500;">service -weight: 600;">sudo k0s -weight: 500;">start # Check -weight: 500;">status -weight: 600;">sudo k0s -weight: 500;">status # Get kubeconfig -weight: 600;">sudo k0s kubeconfig admin > ~/.kube/config # Download and -weight: 500;">install -weight: 500;">curl -sSLf https://get.k0s.sh | -weight: 600;">sudo sh # Install as single-node (controller + worker) -weight: 600;">sudo k0s -weight: 500;">install controller --single # Start the -weight: 500;">service -weight: 600;">sudo k0s -weight: 500;">start # Check -weight: 500;">status -weight: 600;">sudo k0s -weight: 500;">status # Get kubeconfig -weight: 600;">sudo k0s kubeconfig admin > ~/.kube/config # Download and -weight: 500;">install -weight: 500;">curl -sSLf https://get.k0s.sh | -weight: 600;">sudo sh # Install as single-node (controller + worker) -weight: 600;">sudo k0s -weight: 500;">install controller --single # Start the -weight: 500;">service -weight: 600;">sudo k0s -weight: 500;">start # Check -weight: 500;">status -weight: 600;">sudo k0s -weight: 500;">status # Get kubeconfig -weight: 600;">sudo k0s kubeconfig admin > ~/.kube/config # Nginx stream block (not http block) stream { server { listen 6443; proxy_pass k0s-controller:6443; } } # Nginx stream block (not http block) stream { server { listen 6443; proxy_pass k0s-controller:6443; } } # Nginx stream block (not http block) stream { server { listen 6443; proxy_pass k0s-controller:6443; } } # Stop the cluster (if possible) for consistent backup -weight: 500;">docker compose -weight: 500;">stop k0s-controller # Back up the named volume -weight: 500;">docker run --rm -v k0s-data:/source -v $(pwd):/backup alpine \ tar czf /backup/k0s-backup-$(date +%Y%m%d).tar.gz -C /source . # Restart -weight: 500;">docker compose -weight: 500;">start k0s-controller # Stop the cluster (if possible) for consistent backup -weight: 500;">docker compose -weight: 500;">stop k0s-controller # Back up the named volume -weight: 500;">docker run --rm -v k0s-data:/source -v $(pwd):/backup alpine \ tar czf /backup/k0s-backup-$(date +%Y%m%d).tar.gz -C /source . # Restart -weight: 500;">docker compose -weight: 500;">start k0s-controller # Stop the cluster (if possible) for consistent backup -weight: 500;">docker compose -weight: 500;">stop k0s-controller # Back up the named volume -weight: 500;">docker run --rm -v k0s-data:/source -v $(pwd):/backup alpine \ tar czf /backup/k0s-backup-$(date +%Y%m%d).tar.gz -C /source . # Restart -weight: 500;">docker compose -weight: 500;">start k0s-controller - A Linux server (Ubuntu 22.04+ recommended) - Docker and Docker Compose installed (guide) - 1 GB of free RAM minimum (2 GB+ recommended for controller + worker) - 2 GB of free disk space - Root/-weight: 600;">sudo access (privileged mode required for nested containers) - Controller only: 1 vCPU, 1 GB RAM, 500 MB disk - Controller + worker (single-node): 2 vCPU, 2 GB RAM, 2 GB disk - Per additional worker: 0.5 vCPU, 512 MB RAM baseline (plus workload resources) - Storage: SSD recommended for etcd performance - k3s vs k0s: Which Lightweight Kubernetes? - Best Self-Hosted Container Orchestration - How to Self-Host k3s - k3s vs Kubernetes - Docker Swarm vs Kubernetes - How to Self-Host MicroK8s - How to Self-Host Rancher - Docker Compose Basics - Replace Managed Kubernetes