Personal Cloud Lab - Hetzner + K3s
DigitalOcean DOKS is production-grade, but at ~$24+/month minimum, it's expensive for learning and experimentation. Sometimes you want a personal Kubernetes cluster where you can break things, try experimental configurations, and learn without worrying about costs.
Hetzner Cloud offers the most cost-effective path: real cloud servers at ~$5/month for a functional K3s cluster. K3s is a lightweight Kubernetes distribution that runs on smaller instances while remaining fully Kubernetes-compatible. Your kubectl commands, Helm charts, and Dapr configurations work identically.
Why Hetzner + K3s for a Personal Lab?
Before diving into setup, understand why this combination works for budget-conscious learning.
The Cost Comparison
| Provider | Minimum Setup | Monthly Cost | What You Get |
|---|---|---|---|
| DigitalOcean DOKS | 2 nodes | ~$24 | Managed control plane, automatic updates |
| Hetzner + K3s | 1 master + 2 workers | ~$5-10 | Self-managed, full control, real servers |
| AWS EKS | 2 nodes + control plane | ~$75+ | Enterprise features, managed |
| Docker Desktop K8s | Local only | $0 | Not accessible externally |
Hetzner's CPX11 instances (2 vCPU, 2GB RAM) cost approximately $5.59/month (EUR 4.99). A minimal K3s cluster (1 master + 2 workers) runs around $17/month, or a single-node setup for learning costs under $6/month.
What is K3s?
K3s is Kubernetes, stripped down to essentials:
Standard Kubernetes (K8s) K3s (Lightweight)
┌──────────────────────┐ ┌──────────────────────┐
│ kube-apiserver │ │ │
│ kube-controller │ │ Single binary │
│ kube-scheduler │ → │ (k3s server) │
│ etcd (cluster) │ │ + sqlite/etcd │
│ kubelet │ │ + containerd │
│ Multiple binaries │ │ │
└──────────────────────┘ └──────────────────────┘
~1GB+ RAM overhead ~512MB RAM overhead
K3s achieves this by:
- Single binary: All components compiled into one executable
- SQLite by default: No separate etcd cluster needed (optional etcd for HA)
- Containerd only: No Docker dependency
- Reduced features: Removes legacy/alpha features rarely used
- ARM support: Runs on Raspberry Pi and ARM servers
The key insight: K3s is a certified Kubernetes distribution. Your kubectl commands, Helm charts, and YAML manifests work exactly the same. You're learning real Kubernetes, not a simulation.
Prerequisites
Before starting, verify you have:
| Requirement | How to Check |
|---|---|
| SSH key pair | ls ~/.ssh/id_rsa.pub (must exist) |
| Go installed (for CLI) | go version (or use binary download) |
| kubectl installed | kubectl version --client |
| Terminal access | You're reading this, so you have one |
Generate SSH Key (If Missing)
If you don't have an SSH key:
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"
Output:
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/you/.ssh/id_rsa): [press Enter]
Enter passphrase (empty for no passphrase): [optional]
Your public key has been saved in /Users/you/.ssh/id_rsa.pub.
Accept defaults. This creates ~/.ssh/id_rsa (private) and ~/.ssh/id_rsa.pub (public).
Step 1: Create Hetzner Cloud Account
Hetzner is a German cloud provider known for competitive pricing and reliable infrastructure.
1.1 Sign Up
- Visit console.hetzner.cloud
- Click "Register" and create an account
- Verify your email address
- Complete identity verification (Hetzner requires this for new accounts)
Note: Hetzner's verification process may take 24-48 hours. They manually review accounts to prevent abuse. Plan ahead if you need the cluster immediately.
1.2 Create a Project
After verification:
- Log into Hetzner Cloud Console
- Click "New Project"
- Name it something descriptive (e.g., "k3s-lab")
- Click "Create Project"
1.3 Generate API Token
The hetzner-k3s CLI needs an API token to provision resources:
- Select your project in Hetzner Console
- Click "Security" in the left sidebar
- Click "API Tokens" tab
- Click "Generate API Token"
- Name:
hetzner-k3s-cli - Permissions: Read & Write
- Click "Generate API Token"
- Copy the token immediately (it won't be shown again)
Security warning: This token has full access to create and destroy resources. Store it securely.
1.4 Export Token as Environment Variable
export HCLOUD_TOKEN="your-api-token-here"
Add to your shell profile (~/.zshrc or ~/.bashrc) for persistence:
echo 'export HCLOUD_TOKEN="your-api-token-here"' >> ~/.zshrc
source ~/.zshrc
Step 2: Install hetzner-k3s CLI
The hetzner-k3s CLI automates K3s cluster provisioning on Hetzner Cloud. It handles server creation, K3s installation, and kubeconfig generation.
Option A: Install with Go (Recommended)
If you have Go installed:
go install github.com/vitobotta/hetzner-k3s@latest
Output:
go: downloading github.com/vitobotta/hetzner-k3s v2.x.x
Verify installation:
hetzner-k3s version
Output:
hetzner-k3s version 2.0.x
Option B: Download Binary
If you don't have Go, download the binary directly:
macOS (Apple Silicon):
curl -L https://github.com/vitobotta/hetzner-k3s/releases/latest/download/hetzner-k3s-darwin-arm64 -o hetzner-k3s
chmod +x hetzner-k3s
sudo mv hetzner-k3s /usr/local/bin/
macOS (Intel):
curl -L https://github.com/vitobotta/hetzner-k3s/releases/latest/download/hetzner-k3s-darwin-amd64 -o hetzner-k3s
chmod +x hetzner-k3s
sudo mv hetzner-k3s /usr/local/bin/
Linux (AMD64):
curl -L https://github.com/vitobotta/hetzner-k3s/releases/latest/download/hetzner-k3s-linux-amd64 -o hetzner-k3s
chmod +x hetzner-k3s
sudo mv hetzner-k3s /usr/local/bin/
Verify:
hetzner-k3s version
Step 3: Write cluster.yaml Configuration
The hetzner-k3s CLI uses a YAML configuration file to define your cluster. Create a file named cluster.yaml:
---
cluster_name: task-api-lab
kubeconfig_path: ./kubeconfig
k3s_version: v1.31.4+k3s1
networking:
ssh:
port: 22
use_agent: false
public_key_path: ~/.ssh/id_rsa.pub
private_key_path: ~/.ssh/id_rsa
masters_pool:
instance_type: cpx11
instance_count: 1
location: fsn1
worker_pools:
- name: default
instance_type: cpx11
instance_count: 2
location: fsn1
Understanding the Configuration
| Field | Value | Purpose |
|---|---|---|
cluster_name | task-api-lab | Name for your cluster and server prefix |
kubeconfig_path | ./kubeconfig | Where to save cluster credentials |
k3s_version | v1.31.4+k3s1 | K3s version to install |
masters_pool.instance_type | cpx11 | Hetzner instance type (2 vCPU, 2GB RAM) |
masters_pool.instance_count | 1 | Number of control plane nodes |
masters_pool.location | fsn1 | Hetzner datacenter (Falkenstein, Germany) |
worker_pools[0].instance_count | 2 | Number of worker nodes |
Hetzner Instance Types
| Type | vCPU | RAM | Price/Month |
|---|---|---|---|
| cpx11 | 2 | 2 GB | ~$5.59 |
| cpx21 | 3 | 4 GB | ~$8.50 |
| cpx31 | 4 | 8 GB | ~$14.50 |
| cpx41 | 8 | 16 GB | ~$27.00 |
For a learning lab, cpx11 is sufficient. Your Task API and supporting services fit comfortably in 2GB per node.
Hetzner Locations
| Code | Location | Region |
|---|---|---|
| fsn1 | Falkenstein, Germany | EU |
| nbg1 | Nuremberg, Germany | EU |
| hel1 | Helsinki, Finland | EU |
| ash | Ashburn, Virginia | US East |
| hil | Hillsboro, Oregon | US West |
Choose the location closest to you for lower latency.
Step 4: Provision the Cluster
With configuration ready, provision your cluster:
hetzner-k3s create --config cluster.yaml
Output:
Creating master server...
Master server created: task-api-lab-master-1 (10.0.0.2)
Installing K3s on master...
K3s installed successfully
Creating worker servers...
Worker server created: task-api-lab-worker-1 (10.0.0.3)
Worker server created: task-api-lab-worker-2 (10.0.0.4)
Installing K3s on workers...
All workers joined the cluster
Kubeconfig saved to ./kubeconfig
This process takes 3-5 minutes. The CLI:
- Creates Hetzner Cloud servers for master and workers
- Installs K3s on the master node
- Joins worker nodes to the cluster
- Downloads kubeconfig to your local machine
Step 5: Connect to Your Cluster
Set the KUBECONFIG environment variable to use your new cluster:
export KUBECONFIG=./kubeconfig
Verify connectivity:
kubectl get nodes
Output:
NAME STATUS ROLES AGE VERSION
task-api-lab-master-1 Ready control-plane,master 2m v1.31.4+k3s1
task-api-lab-worker-1 Ready <none> 1m v1.31.4+k3s1
task-api-lab-worker-2 Ready <none> 1m v1.31.4+k3s1
All nodes show Ready status. Your K3s cluster is operational.
Verify Cluster Health
Check that system pods are running:
kubectl get pods -n kube-system
Output:
NAME READY STATUS RESTARTS AGE
coredns-7b98449c4-x7vqd 1/1 Running 0 3m
local-path-provisioner-6c79684f77-8vhqj 1/1 Running 0 3m
metrics-server-5f9f776df5-k8xvz 1/1 Running 0 3m
traefik-6b84f7cbc-9gflw 1/1 Running 0 3m
K3s includes:
- CoreDNS: Cluster DNS
- Traefik: Ingress controller (built-in)
- Local Path Provisioner: Default storage class
- Metrics Server: Resource metrics for kubectl top
Step 6: Understanding Hetzner Cloud Controller Manager
When you create LoadBalancer services on Hetzner, the cluster needs the Hetzner Cloud Controller Manager (CCM) to provision Hetzner Load Balancers automatically.
The hetzner-k3s CLI can install CCM automatically. Add to your cluster.yaml:
# ... existing configuration ...
additional_packages:
- hetzner-ccm
If you already created your cluster, install CCM manually:
# Create secret with Hetzner API token
kubectl -n kube-system create secret generic hcloud \
--from-literal=token=$HCLOUD_TOKEN \
--from-literal=network=default
# Deploy CCM
kubectl apply -f https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases/latest/download/ccm.yaml
Output:
secret/hcloud created
serviceaccount/cloud-controller-manager created
deployment.apps/hcloud-cloud-controller-manager created
With CCM installed, creating a type: LoadBalancer service automatically provisions a Hetzner Load Balancer (~$6/month additional).
Step 7: Deploy a Test Application
Verify your cluster works by deploying a simple application:
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80 --type=NodePort
Output:
deployment.apps/nginx created
service/nginx exposed
Check the deployment:
kubectl get pods,svc
Output:
NAME READY STATUS RESTARTS AGE
pod/nginx-76d6c9b8c-7xvqd 1/1 Running 0 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 5m
service/nginx NodePort 10.43.45.123 <none> 80:31234/TCP 15s
Access nginx through any node's public IP:
# Get node external IPs
kubectl get nodes -o wide
# Access nginx (replace with your node IP and NodePort)
curl http://<node-external-ip>:31234
Output:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
Clean up the test:
kubectl delete deployment nginx
kubectl delete service nginx
Cost Breakdown: What You're Paying
With the configuration in this lesson:
| Resource | Count | Monthly Cost |
|---|---|---|
| Master (cpx11) | 1 | ~$5.59 |
| Workers (cpx11) | 2 | ~$11.18 |
| Total | - | ~$16.77 |
For a minimal single-node learning cluster:
masters_pool:
instance_type: cpx11
instance_count: 1
location: fsn1
worker_pools: [] # No workers, master runs workloads
This costs ~$5.59/month. K3s can run workloads on the control plane node.
Comparison with DOKS
| Setup | Hetzner K3s | DigitalOcean DOKS |
|---|---|---|
| Single-node learning | ~$6/month | N/A (2 nodes minimum) |
| 1 master + 2 workers | ~$17/month | ~$24/month |
| Managed control plane | No | Yes |
| Auto-updates | No | Yes |
| Support SLA | Community | Business |
When to choose Hetzner K3s:
- Learning and experimentation
- Budget-conscious projects
- Full control needed
- Non-production workloads
When to choose DOKS:
- Production workloads
- Team environments
- Need managed upgrades
- SLA requirements
Cleanup: Destroying Your Cluster
When you're done experimenting, destroy the cluster to stop charges:
hetzner-k3s delete --config cluster.yaml
Output:
Deleting worker servers...
Deleted: task-api-lab-worker-1
Deleted: task-api-lab-worker-2
Deleting master servers...
Deleted: task-api-lab-master-1
Cluster task-api-lab deleted
Important: This permanently destroys all servers and data. Export any important configurations first.
Try With AI
Now that you have a personal cloud lab, explore K3s and Hetzner further with your AI companion.
Prompt 1: Understand K3s Architecture
I just provisioned a K3s cluster on Hetzner Cloud. K3s runs as a single binary
instead of multiple components like standard Kubernetes. Help me understand:
1. What components are compiled into the k3s binary?
2. How does K3s handle storage without etcd in single-server mode?
3. What are the tradeoffs of using K3s vs full Kubernetes for production?
I want to understand when K3s is appropriate and when I should choose managed
Kubernetes like DOKS or EKS.
What you're learning: Understanding K3s architecture helps you make informed decisions about when lightweight Kubernetes is appropriate versus when you need full enterprise features.
Prompt 2: Compare Cloud Provider Costs
I'm running a personal K3s lab on Hetzner at ~$15/month. Compare the costs if
I wanted to run similar workloads on:
1. DigitalOcean (DOKS or droplets + K3s)
2. AWS (EKS or EC2 + K3s)
3. Google Cloud (GKE or Compute Engine + K3s)
Include hidden costs like egress bandwidth, load balancers, and persistent
storage. I want to understand the true cost of cloud Kubernetes.
What you're learning: Cloud pricing is complex. Understanding all cost components helps you budget accurately and choose providers based on your actual workload patterns.
Prompt 3: Plan Production Migration
I've been learning on my Hetzner K3s cluster. Now I want to migrate my Task API
to production. Help me plan the migration:
1. What would I need to change in my Helm charts for managed Kubernetes?
2. How do I handle the transition from Traefik (K3s default) to other ingress controllers?
3. What configurations are K3s-specific vs portable to any Kubernetes?
I want to ensure my learning transfers to production without major rewrites.
What you're learning: Skill portability is the goal. Understanding what's K3s-specific versus universal Kubernetes ensures your learning applies to any cluster.
Safety Note
Hetzner servers have public IPs by default. Your cluster is internet-accessible. Before deploying sensitive workloads, implement network policies, firewall rules, and authentication. Never expose sensitive services without TLS and proper access controls.
Reflect on Your Skill
You built a multi-cloud-deployer skill in Lesson 0. Test and improve it based on what you learned about Hetzner and K3s.
Test Your Skill
Using my multi-cloud-deployer skill, generate the hetzner-k3s configuration
for a minimal learning cluster (1 node, cpx11, fsn1 location).
Does your skill produce valid cluster.yaml? Does it explain K3s versus full Kubernetes tradeoffs?
Identify Gaps
Ask yourself:
- Does my skill include Hetzner-specific patterns (API token, instance types, locations)?
- Can it generate both single-node and multi-node configurations?
- Does it explain K3s architecture and when to choose it?
Improve Your Skill
If you found gaps:
My multi-cloud-deployer skill is missing Hetzner K3s patterns. Update it to include:
- Hetzner Cloud API token configuration
- hetzner-k3s CLI installation and usage
- cluster.yaml templates for single-node and multi-node setups
- K3s vs full Kubernetes comparison for decision-making