Kubernetes on WSL on Windows 11
This document describes how to prepare a clean WSL2-based Kubernetes workshop environment on Windows 11 and then provision different Kubernetes variants.
It does not replace a real production platform. It is intended for local labs, demos, automation, YAML exercises, GitOps experiments, Ingress tests, networking fundamentals, and fast reproducibility.
1. Purpose
This document describes a standardized Kubernetes practice environment on Windows 11 with WSL2.
You can use it to:
- prepare WSL2 with Ubuntu for Kubernetes labs,
- provision local clusters with
kind,minikube,k3d, Docker Desktop Kubernetes,k3s, or experimentally withkubeadm, - configure hostnames so endpoints are reachable from Microsoft Edge on the Windows host,
- provision the workshop files reproducibly from WSL,
- debug common issues at the boundary between Windows, WSL2, Docker, Ingress, and Kubernetes.
The file is intentionally detailed. It is not just an installation note; it is an operations and decision reference for the workshop.
2. Target State
At the end, your Windows 11 machine should have:
- an up-to-date WSL2 distribution, preferably Ubuntu LTS,
systemdenabled in WSL,- a consistent Linux filesystem layout for code, tools, and cluster artifacts,
- installed Kubernetes CLI tooling,
- at least one working local Kubernetes variant,
- a working path from the Windows browser to local Kubernetes endpoints,
- a documented cleanup and repeatability strategy.
2.1 Mental Model
WSL2 is not a traditional Linux VM, but it behaves similarly enough for many development tasks. Kubernetes labs work well as long as you understand the boundaries:
- WSL2 runs inside a Windows-managed utility VM.
- Windows and WSL2 have separate network stacks, although
localhostintegration is usually good depending on the network mode. - Windows drives are typically mounted in WSL under
/mnt/c,/mnt/d, and so on. - Linux files inside the WSL filesystem are usually much faster for Kubernetes and Docker workflows than project files under
/mnt/c. - Microsoft Edge runs on Windows, not inside WSL. Browser endpoints must therefore be reachable through Windows DNS, the Windows hosts file, or
localhost.
3. Decision Model
Choose the Kubernetes variant based on learning objective, reproducibility, and networking needs.
| Goal | Recommended variant | Rationale |
|---|---|---|
| Fast Kubernetes fundamentals | kind |
Very fast, reproducible, ideal for YAML, controllers, Ingress labs, and CI-like tests. |
| Developer-friendly single cluster | minikube with Docker driver |
Useful add-ons, simple service commands, good for beginners. |
| K3s-like local clusters | k3d |
K3s runs in Docker containers, good port mappings, fast cleanup. |
| Simple Docker Desktop context | Docker Desktop Kubernetes | Convenient, but less transparent and less aligned with real cluster bootstrap flows. |
| Bootstrap understanding with kubeadm | kubeadm on WSL, experimental only |
Technically useful, but WSL has kernel, networking, and service-specific behavior. A VM is better for serious kubeadm labs. |
| Lightweight single-node cluster | k3s directly in WSL |
Fast and resource-efficient, but requires clean service and networking handling. |
| Production-like operations | Native VM, bare metal, or managed Kubernetes | WSL is good for local labs, but not the reference environment for production operations. |
| Operate a remote cluster | WSL as client only | Good option when the cluster already runs in cloud, VM, or bare metal. |
3.1 Decision Tree
flowchart TD
Start[You need a Kubernetes environment on Windows 11] --> NeedProd{Production-like cluster?}
NeedProd -->|Yes| VM[Use VM, bare metal, or managed Kubernetes]
NeedProd -->|No| NeedFast{Fast and reproducible?}
NeedFast -->|Yes| Kind[kind on WSL]
NeedFast -->|No| NeedAddons{Many convenience add-ons?}
NeedAddons -->|Yes| Mini[minikube]
NeedAddons -->|No| NeedK3s{K3s-like behavior?}
NeedK3s -->|Yes| K3d[k3d or k3s]
NeedK3s -->|No| NeedBootstrap{kubeadm learning goal?}
NeedBootstrap -->|Yes| Kubeadm[Prefer kubeadm in a VM; WSL only experimentally]
NeedBootstrap -->|No| DockerDesktop[Docker Desktop Kubernetes or remote cluster]
4. Supported Variants
This document covers the following provisioning paths:
WSL as Kubernetes client
kubectl,helm,kustomize,stern,k9s,jq,yq- connection to remote clusters
kind in WSL
- Kubernetes nodes as Docker containers
- good port mappings for Ingress
- very suitable for workshops
minikube in WSL
- preferably with the Docker driver
- optional SSH or none driver for special cases
- add-ons such as Ingress, Metrics Server, and Dashboard
k3d in WSL
- K3s in Docker
- simple local load-balancer port exposure
- fast for K3s-oriented labs
Docker Desktop Kubernetes
- enable Kubernetes in Docker Desktop
- fast, but less controllable
kubeadm in WSL
- for experimental understanding only
- not recommended as the standard lab path
k3s directly in WSL
- lightweight
- requires attention to system services and networking
MicroK8s, k0s, and other distributions
- mentioned and classified as optional variants
5. Architecture Overview
5.1 Overall Architecture: Windows, WSL, and Kubernetes
flowchart LR
Edge[Microsoft Edge on Windows] --> Hosts[Windows hosts file / DNS]
Hosts --> Localhost[localhost / 127.0.0.1]
Localhost --> WSLNet[WSL2 network]
WSLNet --> Docker[Docker Engine or Docker Desktop WSL backend]
Docker --> Cluster[Kubernetes cluster]
Cluster --> Ingress[Ingress Controller / Gateway]
Ingress --> Service[Kubernetes Service]
Service --> Pods[Pods]
WSL[Ubuntu on WSL2] --> Kubectl[kubectl / helm / kustomize]
Kubectl --> API[Kubernetes API Server]
API --> Cluster
5.2 WSL as the Tooling Layer
flowchart TD
Repo[Workshop files in the WSL filesystem] --> Scripts[Provisioning scripts]
Scripts --> Tools[CLI tools]
Tools --> Kubectl[kubectl]
Tools --> Helm[helm]
Tools --> Kind[kind]
Tools --> Mini[minikube]
Tools --> K3d[k3d]
Kind --> Docker[Docker]
Mini --> Docker
K3d --> Docker
Docker --> K8s[Kubernetes cluster]
5.3 Browser Access from Windows
sequenceDiagram
participant Edge as Edge Browser on Windows
participant Hosts as Windows hosts file
participant WSL as WSL2 network
participant Ingress as Ingress Controller
participant Svc as Kubernetes Service
participant Pod as Pod
Edge->>Hosts: Resolve demo.local
Hosts-->>Edge: 127.0.0.1
Edge->>WSL: HTTP/HTTPS on localhost
WSL->>Ingress: Port mapping or tunnel
Ingress->>Svc: Host and path routing
Svc->>Pod: Forward to Pod IP
Pod-->>Edge: Response
6. Windows 11 Prerequisites
6.1 Windows Requirements
You need:
- Windows 11, preferably fully patched,
- virtualization enabled in BIOS or UEFI,
- WSL2,
- an Ubuntu LTS distribution,
- administrator rights for the initial installation and changes to the Windows hosts file,
- enough resources:
- at least 4 CPU cores recommended,
- at least 8 GB RAM for simple labs,
- 16 GB RAM or more for parallel clusters, Ingress, observability, and GitOps,
- at least 40 GB of free disk space.
6.2 Recommended Windows Components
Install or enable:
- Windows Subsystem for Linux,
- Virtual Machine Platform,
- optionally Hyper-V if you use additional VM-based workflows,
- Docker Desktop with WSL2 backend or native Docker Engine inside WSL,
- Microsoft Edge for browser labs,
- Visual Studio Code with the WSL extension if you want interactive development.
6.3 Assumptions to Avoid
Do not assume that:
- every Pod IP is directly reachable from the Windows host,
- every ClusterIP is reachable from the browser,
minikube ipwith the Docker driver is always directly reachable from the Windows browser,- changes to
/etc/hostsautomatically apply to Edge, - WSL behaves exactly like a native Ubuntu VM.
For browser labs, the rule is: Windows must be able to resolve the name, and Windows must be able to reach the port.
7. Install and Verify WSL2
7.1 Install WSL
Open PowerShell as Administrator.
wsl --install -d Ubuntu-24.04
If Ubuntu 24.04 is not available, list available distributions:
wsl --list --online
Then install a suitable Ubuntu LTS version:
wsl --install -d Ubuntu
7.2 Update WSL
wsl --update
wsl --version
7.3 Set WSL2 as Default
wsl --set-default-version 2
7.4 Check Installed Distributions
wsl --list --verbose
Expected example:
NAME STATE VERSION
* Ubuntu-24.04 Running 2
If your distribution still uses WSL1:
wsl --set-version Ubuntu-24.04 2
7.5 Start the Distribution
wsl -d Ubuntu-24.04
Or via the Start menu:
Ubuntu 24.04
7.6 Capture Windows Information
For later troubleshooting, record:
winver
wsl --version
wsl --status
wsl --list --verbose
8. Prepare Ubuntu in WSL
8.1 Update the System
Inside Ubuntu on WSL:
sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove -y
8.2 Install Base Tools
sudo apt install -y \
apt-transport-https \
bash-completion \
ca-certificates \
conntrack \
curl \
dnsutils \
file \
git \
gnupg \
htop \
iproute2 \
iptables \
iputils-ping \
jq \
less \
lsb-release \
make \
net-tools \
netcat-openbsd \
openssl \
procps \
python3 \
python3-pip \
rsync \
socat \
tar \
traceroute \
tree \
unzip \
vim \
wget \
yq \
zip
8.3 Check the User
whoami
id
sudo -v
Your user must be able to use sudo.
8.4 Set the Time Zone
timedatectl
sudo timedatectl set-timezone Europe/Berlin
If timedatectl does not work yet, first enable systemd as described in chapter 9.
8.5 Configure Git
git config --global user.name "Your Name"
git config --global user.email "your.name@example.com"
git config --global init.defaultBranch main
git config --global pull.rebase false
8.6 Create a Project Directory
Prefer the Linux filesystem inside WSL for code:
mkdir -p ~/workshops
cd ~/workshops
Not recommended for intensive Kubernetes and Docker workflows:
cd /mnt/c/Users/<your-user>/Documents
This works, but it is often noticeably slower with many small files and bind mounts.
9. WSL Configuration
WSL uses two central configuration files:
| File | Location | Effect |
|---|---|---|
.wslconfig |
Windows user profile, for example C:\Users\<you>\.wslconfig |
Global WSL2 VM settings such as RAM, CPU, swap, and network mode. |
wsl.conf |
Inside the distribution under /etc/wsl.conf |
Per-distribution settings such as systemd, automount, hosts generation, and interop. |
9.1 .wslconfig for the Workshop Standard
Open PowerShell, not WSL:
notepad $env:USERPROFILE\.wslconfig
Recommended default for many workshop machines:
[wsl2]
memory=8GB
processors=4
swap=4GB
localhostForwarding=true
dnsTunneling=true
autoProxy=true
[experimental]
autoMemoryReclaim=gradual
sparseVhd=true
Then fully restart WSL:
wsl --shutdown
wsl -d Ubuntu-24.04
9.2 Optional: Mirrored Networking
If you frequently run into VPN, firewall, browser-access, or bidirectional-connectivity issues, you can test the newer Windows 11 networking mode.
[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true
localhostForwarding=true
Then run:
wsl --shutdown
Notes:
mirroredis useful in many enterprise networks, but can conflict with firewall or endpoint-security rules in some setups.- For the workshop, NAT with
localhostForwarding=trueis often sufficient. - If you enable
mirrored, re-test Docker,kind,minikube, DNS, and Edge access.
9.3 Enable systemd in Ubuntu
Open in WSL:
sudo nano /etc/wsl.conf
Recommended workshop configuration:
[boot]
systemd=true
[automount]
enabled=true
root=/mnt/
mountFsTab=true
[network]
generateHosts=true
generateResolvConf=true
[interop]
enabled=true
appendWindowsPath=true
[user]
default=<your-linux-user>
Replace <your-linux-user> with your username.
Then in PowerShell:
wsl --shutdown
wsl -d Ubuntu-24.04
Check in WSL:
ps -p 1 -o comm=
systemctl is-system-running || true
systemctl list-unit-files --type=service | head
Expected output:
systemd
9.4 Check Windows Drives
ls -ld /mnt/c
ls -l /mnt/c/Windows/System32/drivers/etc/hosts
If /mnt/c is missing:
mount | grep drvfs || true
Then check wsl.conf and restart WSL.
9.5 Check Interop
powershell.exe -NoProfile -Command '$PSVersionTable.PSVersion'
cmd.exe /c ver
explorer.exe .
If these commands are not found, appendWindowsPath=false is set or Windows interop is disabled. Interop is useful for the workshop because it lets you start Windows tools from WSL.
10. Filesystem and Workshop Files
10.1 Recommended Layout
/home/<you>/workshops/
├── kubernetes-workshop/
├── clusters/
│ ├── kind/
│ ├── minikube/
│ └── k3d/
├── bin/
└── tmp/
Create it:
mkdir -p ~/workshops ~/clusters/{kind,minikube,k3d} ~/bin ~/tmp
Add ~/bin to the path:
grep -q 'export PATH="$HOME/bin:$PATH"' ~/.bashrc || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
10.2 Get the Workshop Material
The documentation is available in the Orthevs wiki. Download the practice examples you need as ZIP files from the Kubernetes workshop page and extract them into your local workshop directory.
10.3 Check the Workshop Files
find ~/workshops/kubernetes-workshop -maxdepth 3 -type f | sort | head -100
10.4 Avoid Secrets
Before every change:
grep -RInE '(password|passwd|secret|token|apikey|api_key|private_key)' . \
--exclude-dir=.git \
--exclude='*.md' || true
This is only a coarse check. Real secrets do not belong in the lab environment.
11. Windows Hosts File, DNS, and Edge Reachability
This chapter is central because Edge runs on Windows. Changes to /etc/hosts in WSL are not enough for Edge.
11.1 Which Hosts File Matters?
| Target | File |
|---|---|
| Name resolution in WSL | /etc/hosts inside Ubuntu |
| Name resolution in Microsoft Edge on Windows | C:\Windows\System32\drivers\etc\hosts |
| Access from WSL to the Windows file | /mnt/c/Windows/System32/drivers/etc/hosts |
For browser labs, you must adjust the Windows hosts file.
11.2 Show the Windows Hosts File
From WSL:
sudo cat /mnt/c/Windows/System32/drivers/etc/hosts | tail -40
Reading usually works. Writing requires Windows administrator rights.
11.3 Safely Open the Hosts File as Administrator
Recommended from an administrative PowerShell:
notepad $env:windir\System32\drivers\etc\hosts
For local Ingress labs, add for example:
# Kubernetes WSL Workshop
127.0.0.1 demo.local
127.0.0.1 api.demo.local
127.0.0.1 grafana.demo.local
127.0.0.1 argocd.demo.local
127.0.0.1 keycloak.demo.local
127.0.0.1 prometheus.demo.local
Then flush the DNS cache:
ipconfig /flushdns
11.4 Open the Hosts File from WSL via PowerShell
powershell.exe -NoProfile -Command 'Start-Process notepad -Verb RunAs -ArgumentList "$env:windir\System32\drivers\etc\hosts"'
When the UAC prompt appears, approve it. Then edit the file in Notepad.
11.5 Reusable PowerShell Script for Hosts Entries
Create a file in the repository:
mkdir -p scripts/windows
cat > scripts/windows/add-k8s-hosts.ps1 <<'EOF'
$ErrorActionPreference = "Stop"
$HostsPath = "$env:windir\System32\drivers\etc\hosts"
$MarkerStart = "# BEGIN Kubernetes WSL Workshop"
$MarkerEnd = "# END Kubernetes WSL Workshop"
$Block = @"
$MarkerStart
127.0.0.1 demo.local
127.0.0.1 api.demo.local
127.0.0.1 grafana.demo.local
127.0.0.1 argocd.demo.local
127.0.0.1 keycloak.demo.local
127.0.0.1 prometheus.demo.local
$MarkerEnd
"@
$Current = Get-Content -Path $HostsPath -Raw
$Pattern = [regex]::Escape($MarkerStart) + "(?s).*?" + [regex]::Escape($MarkerEnd)
if ($Current -match $Pattern) {
$New = [regex]::Replace($Current, $Pattern, $Block.Trim())
Set-Content -Path $HostsPath -Value $New -NoNewline
} else {
Add-Content -Path $HostsPath -Value "`r`n$Block"
}
ipconfig /flushdns | Out-Null
Write-Host "Kubernetes WSL hosts entries have been set."
EOF
Run it from an administrative PowerShell:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Path\to\Repository\scripts\windows\add-k8s-hosts.ps1
Or start it from WSL:
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command \
"Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File \"$(wslpath -w scripts/windows/add-k8s-hosts.ps1)\"'"
11.6 Remove Hosts Entries
cat > scripts/windows/remove-k8s-hosts.ps1 <<'EOF'
$ErrorActionPreference = "Stop"
$HostsPath = "$env:windir\System32\drivers\etc\hosts"
$MarkerStart = "# BEGIN Kubernetes WSL Workshop"
$MarkerEnd = "# END Kubernetes WSL Workshop"
$Current = Get-Content -Path $HostsPath -Raw
$Pattern = "(?ms)^" + [regex]::Escape($MarkerStart) + ".*?" + [regex]::Escape($MarkerEnd) + "\r?\n?"
$New = [regex]::Replace($Current, $Pattern, "")
Set-Content -Path $HostsPath -Value $New -NoNewline
ipconfig /flushdns | Out-Null
Write-Host "Kubernetes WSL hosts entries have been removed."
EOF
Administrative PowerShell:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Path\to\Repository\scripts\windows\remove-k8s-hosts.ps1
11.7 Check from Windows
In PowerShell:
Resolve-DnsName demo.local
Test-NetConnection demo.local -Port 80
In the browser:
http://demo.local
11.8 Check from WSL
getent hosts demo.local
curl -v http://demo.local || true
curl -v http://127.0.0.1 || true
11.9 Important Rule
For kind and k3d, browser labs usually use:
127.0.0.1 demo.local
For minikube, it depends on the driver and access path:
- with
kubectl port-forward:127.0.0.1, - with
minikube tunnel: usually127.0.0.1or a tunnel address, - with VM or SSH drivers: possibly the node IP,
- with the Docker driver under WSL: prefer port-forwarding or explicit port exposure.
12. Container Runtime and Docker Strategy
Most recommended WSL variants use Docker:
kindrequires Docker, Podman, or a compatible container runtime.k3drequires Docker.minikubewith the Docker driver requires Docker.- Docker Desktop Kubernetes runs directly inside the Docker Desktop stack.
12.1 Recommended Strategy for Workshops
For mixed participant groups, this is usually the most stable baseline:
Windows 11 + WSL2 + Ubuntu + Docker Desktop WSL Integration + kind
Advantages:
- fast installation,
- good Windows integration,
- relatively predictable networking,
- easy reset,
- fewer system-level changes inside WSL.
12.2 Docker Desktop with WSL Integration
In Docker Desktop:
- Open Settings.
- Select Resources.
- Open WSL Integration.
- Enable integration for your Ubuntu distribution.
- Restart Docker Desktop.
Check in WSL:
docker version
docker context ls
docker run --rm hello-world
12.3 Native Docker Engine in WSL
Alternatively, you can install Docker directly inside Ubuntu on WSL. This is more transparent, but more dependent on system service and networking settings.
Example for Ubuntu:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
. /etc/os-release
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu %s stable\n' \
"$(dpkg --print-architecture)" "$VERSION_CODENAME" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker "$USER"
newgrp docker
Check:
systemctl status docker --no-pager
docker info
docker run --rm hello-world
12.4 Docker Desktop or Native Docker Engine?
| Criterion | Docker Desktop | Native Docker Engine in WSL |
|---|---|---|
| Beginner friendliness | High | Medium |
| Enterprise policies | Check license and policy | Often more flexible, but manually maintained |
| WSL integration | Very good | Good, but more self-managed |
kind and k3d |
Very good | Very good |
| System transparency | Lower | Higher |
| Troubleshooting | Partly Docker Desktop-specific | Closer to Linux |
13. Install Base Tooling
13.1 Treat Kubernetes Versions Deliberately
Do not hard-code the Kubernetes version in every script. Use variables:
export K8S_MINOR="1.36"
export K8S_STABLE="$(curl -L -s https://dl.k8s.io/release/stable.txt)"
echo "$K8S_STABLE"
Notes:
- Package repositories often use the minor stream, for example
v1.36. - For individual binaries or cluster images, use a specific patch version.
- For workshops, freeze versions ahead of time so every participant uses the same baseline.
13.2 Install kubectl from the Kubernetes Package Repository
export K8S_MINOR="1.36"
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL "https://pkgs.k8s.io/core:/stable:/v${K8S_MINOR}/deb/Release.key" | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
printf 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v%s/deb/ /\n' "${K8S_MINOR}" | \
sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update
sudo apt install -y kubectl
kubectl version --client
13.3 kubectl Completion
echo 'source <(kubectl completion bash)' >> ~/.bashrc
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
source ~/.bashrc
13.4 Install Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
13.5 Check Kustomize
kubectl includes Kustomize functionality:
kubectl kustomize --help | head
Optional standalone kustomize:
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo install -m 0755 kustomize /usr/local/bin/kustomize
kustomize version
13.6 Useful Additional Tools
# stern for log tailing
curl -fsSL https://github.com/stern/stern/releases/latest/download/stern_linux_amd64.tar.gz -o /tmp/stern.tar.gz
mkdir -p /tmp/stern
sudo tar -xzf /tmp/stern.tar.gz -C /tmp/stern
sudo install -m 0755 /tmp/stern/stern /usr/local/bin/stern
# Install k9s via a GitHub release or package manager, depending on organizational policy.
# This example is intentionally not automated because version and architecture should be fixed explicitly.
13.7 Validate Tooling
for cmd in git curl jq yq docker kubectl helm; do
printf '%-12s' "$cmd"
command -v "$cmd" || true
done
14. Variant A: kind on WSL
kind stands for Kubernetes in Docker. It runs Kubernetes nodes as containers. For WSL workshops, kind is usually the best default variant.
14.1 When to Use kind
Use kind when you want to:
- create and delete clusters quickly,
- practice YAML, Deployments, Services, Ingress, and controllers,
- run reproducible labs,
- test several clusters in parallel,
- simulate CI-like clusters.
14.2 Limits of kind
Be aware:
- Nodes are containers, not real machines.
- Storage is local and lab-oriented.
- LoadBalancer behavior requires additional components or port mappings.
- Kernel and node operations differ from bare metal or VM clusters.
14.3 Install kind
Option via Go:
sudo apt install -y golang-go
go install sigs.k8s.io/kind@latest
mkdir -p ~/bin
ln -sf "$(go env GOPATH)/bin/kind" ~/bin/kind
kind version
Option via binary:
KIND_VERSION="v0.30.0"
curl -Lo /tmp/kind "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64"
chmod +x /tmp/kind
sudo install -m 0755 /tmp/kind /usr/local/bin/kind
kind version
Adjust KIND_VERSION to the desired version before the workshop.
14.4 Create a Simple Cluster
kind create cluster --name workshop
kubectl cluster-info --context kind-workshop
kubectl get nodes -o wide
14.5 Cluster Configuration with Ingress Port Mapping
Create a configuration:
mkdir -p clusters/kind
cat > clusters/kind/workshop.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: workshop
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- containerPort: 30080
hostPort: 30080
protocol: TCP
- role: worker
- role: worker
EOF
Create the cluster:
kind delete cluster --name workshop || true
kind create cluster --config clusters/kind/workshop.yaml
kubectl get nodes -o wide
14.6 When Port 80 or 443 Is Already in Use
If Windows, Docker Desktop, or another tool already uses port 80/443, use higher ports:
extraPortMappings:
- containerPort: 80
hostPort: 8080
protocol: TCP
- containerPort: 443
hostPort: 8443
protocol: TCP
Then use in the browser:
http://demo.local:8080
https://demo.local:8443
14.7 Install Ingress-NGINX
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=180s
14.8 Deploy a Demo Application
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web \
--image=nginx:1.27 \
--replicas=2 \
-n demo
kubectl expose deployment web \
--port=80 \
--target-port=80 \
-n demo
Ingress:
cat > /tmp/demo-ingress.yaml <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
spec:
ingressClassName: nginx
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
EOF
kubectl apply -f /tmp/demo-ingress.yaml
Windows hosts file:
127.0.0.1 demo.local
Test from WSL:
curl -H 'Host: demo.local' http://127.0.0.1
curl http://demo.local
Test in Microsoft Edge:
http://demo.local
14.9 kind with Local Registry
Start a local registry:
docker run -d --restart=always -p 127.0.0.1:5001:5000 --name kind-registry registry:2
kind configuration with registry mirror:
cat > clusters/kind/workshop-registry.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: workshop
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5001"]
endpoint = ["http://kind-registry:5000"]
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- role: worker
- role: worker
EOF
Create the cluster and connect the registry to the kind network:
kind delete cluster --name workshop || true
kind create cluster --config clusters/kind/workshop-registry.yaml
docker network connect kind kind-registry || true
Build and push an image:
docker build -t localhost:5001/demo-app:0.1.0 .
docker push localhost:5001/demo-app:0.1.0
In the manifest:
image: localhost:5001/demo-app:0.1.0
14.10 Delete the kind Cluster
kind delete cluster --name workshop
docker rm -f kind-registry || true
15. Variant B: minikube on WSL
minikube creates local Kubernetes clusters with different drivers. Under WSL, the Docker driver is usually the practical option.
15.1 When to Use minikube
Use minikube when you want:
- a beginner-friendly path with many built-in add-ons,
minikube addons,- quick enablement of
metrics-server, Dashboard, Ingress, or Registry, - exercises with service access through
minikube service.
15.2 Install minikube
curl -LO https://github.com/kubernetes/minikube/releases/latest/download/minikube-linux-amd64
sudo install -m 0755 minikube-linux-amd64 /usr/local/bin/minikube
rm -f minikube-linux-amd64
minikube version
15.3 Start a Cluster with the Docker Driver
minikube start \
--driver=docker \
--cpus=4 \
--memory=8192 \
--disk-size=30g
Check:
kubectl config current-context
kubectl get nodes -o wide
minikube status
15.4 Set a Kubernetes Version
K8S_STABLE="$(curl -L -s https://dl.k8s.io/release/stable.txt)"
minikube start \
--driver=docker \
--kubernetes-version="$K8S_STABLE" \
--cpus=4 \
--memory=8192
If you want to keep a workshop reproducible, pin a specific version:
minikube start --driver=docker --kubernetes-version=v1.36.1
Adjust the patch version before the workshop.
15.5 Enable Add-ons
minikube addons list
minikube addons enable ingress
minikube addons enable metrics-server
Check:
kubectl get pods -A
kubectl top nodes || true
15.6 Demo Application
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web --image=nginx:1.27 -n demo
kubectl expose deployment web --port=80 --target-port=80 -n demo
NodePort Service:
kubectl expose deployment web \
--name=web-nodeport \
--type=NodePort \
--port=80 \
--target-port=80 \
-n demo
kubectl get svc -n demo
minikube service web-nodeport -n demo --url
15.7 Access from Edge
For WSL and Windows, the most stable path is often port-forwarding:
kubectl port-forward -n demo --address 0.0.0.0 svc/web 8080:80
Then in Edge:
http://localhost:8080
Or with a hostname:
Windows hosts file:
127.0.0.1 demo.local
Browser:
http://demo.local:8080
15.8 Ingress with minikube
Enable Ingress:
minikube addons enable ingress
kubectl wait -n ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=180s
Ingress object:
cat > /tmp/minikube-ingress.yaml <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
spec:
ingressClassName: nginx
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
EOF
kubectl apply -f /tmp/minikube-ingress.yaml
Depending on the driver, you may reach Ingress through:
minikube ip
curl -H 'Host: demo.local' http://$(minikube ip)
If Windows cannot reach this IP, use port-forwarding or minikube tunnel.
15.9 minikube tunnel
minikube tunnel can make Services of type LoadBalancer reachable. Under Windows/WSL, this may require elevated privileges and routing changes.
minikube tunnel
In a second terminal:
kubectl create deployment lb-demo --image=nginx:1.27 -n demo
kubectl expose deployment lb-demo --type=LoadBalancer --port=80 -n demo
kubectl get svc -n demo -w
15.10 Delete minikube
minikube delete
16. Variant C: k3d on WSL
k3d starts K3s clusters in Docker. It is fast, resource-efficient, and useful for local labs.
16.1 When to Use k3d
Use k3d when you want to:
- test K3s behavior locally,
- start clusters very quickly,
- expose ports through a load-balancer container,
- use multiple local clusters.
16.2 Install k3d
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
k3d version
For stricter environments, download, inspect, and version the install script first.
16.3 Create a Cluster with Port Mapping
k3d cluster create workshop \
--servers 1 \
--agents 2 \
--api-port 127.0.0.1:6550 \
-p "80:80@loadbalancer" \
-p "443:443@loadbalancer"
Check:
kubectl cluster-info
kubectl get nodes -o wide
k3d cluster list
16.4 Demo Application with Ingress
K3s includes Traefik by default unless you disable it.
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web --image=nginx:1.27 -n demo
kubectl expose deployment web --port=80 --target-port=80 -n demo
Ingress:
cat > /tmp/k3d-ingress.yaml <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
spec:
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
EOF
kubectl apply -f /tmp/k3d-ingress.yaml
Windows hosts file:
127.0.0.1 demo.local
Test:
curl http://demo.local
Browser:
http://demo.local
16.5 k3d without Traefik and with Ingress-NGINX
k3d cluster delete workshop || true
k3d cluster create workshop \
--servers 1 \
--agents 2 \
--api-port 127.0.0.1:6550 \
-p "80:80@loadbalancer" \
-p "443:443@loadbalancer" \
--k3s-arg "--disable=traefik@server:*"
Install Ingress-NGINX:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
-n ingress-nginx \
--create-namespace \
--set controller.service.type=LoadBalancer
16.6 Delete k3d
k3d cluster delete workshop
17. Variant D: Docker Desktop Kubernetes
Docker Desktop can provide a local Kubernetes cluster.
17.1 When to Use Docker Desktop Kubernetes
Use this variant when you:
- need a local Kubernetes context very quickly,
- do not want to install an additional cluster CLI,
- already use Docker Desktop in the workshop,
- do not need deep bootstrap transparency.
17.2 Enable It
In Docker Desktop:
- Open Settings.
- Select Kubernetes.
- Enable Enable Kubernetes.
- Apply and wait.
Check in WSL:
kubectl config get-contexts
kubectl config use-context docker-desktop
kubectl get nodes -o wide
17.3 Demo
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web --image=nginx:1.27 -n demo
kubectl expose deployment web --port=80 --target-port=80 -n demo
kubectl port-forward -n demo --address 0.0.0.0 svc/web 8080:80
Browser:
http://localhost:8080
17.4 Limits
Docker Desktop Kubernetes is convenient, but:
- less transparent than
kind,minikube, orkubeadm, - dependent on Docker Desktop version and settings,
- not ideal for operations labs about the control plane, node lifecycle, or CNI,
- subject to licensing and enterprise policy.
18. Variant E: kubeadm Experimentally on WSL
kubeadm is the classic bootstrap path for Kubernetes clusters. On WSL, this variant is not the standard recommendation. For production-like labs, use a native VM or bare-metal environment.
Still, kubeadm on WSL is educational because it exposes the control plane, kubelet, containerd, CNI, certificates, and cluster bootstrap directly.
18.1 Prerequisites
You need:
- WSL2,
systemd=true,- a working
containerdruntime, - disabled swap or explicitly configured kubelet behavior,
- required kernel and networking features,
- willingness to troubleshoot.
18.2 Install containerd
sudo apt update
sudo apt install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
Set systemd cgroups:
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd
Check:
systemctl status containerd --no-pager
sudo ctr version
18.3 Kernel and Network Parameters
cat <<'EOF' | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay || true
sudo modprobe br_netfilter || true
cat <<'EOF' | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
If modprobe br_netfilter fails in WSL, that is a WSL kernel issue. For real kubeadm labs, switch to a VM.
18.4 Install kubeadm, kubelet, and kubectl
export K8S_MINOR="1.36"
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL "https://pkgs.k8s.io/core:/stable:/v${K8S_MINOR}/deb/Release.key" | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
printf 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v%s/deb/ /\n' "${K8S_MINOR}" | \
sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
18.5 Check Swap
swapon --show || true
free -h
If swap is active and kubelet should not use it:
sudo swapoff -a
WSL swap is controlled through .wslconfig. For real kubeadm labs, define this behavior deliberately.
18.6 kubeadm Configuration
mkdir -p clusters/kubeadm
cat > clusters/kubeadm/init.yaml <<'EOF'
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 127.0.0.1
bindPort: 6443
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
# Example version: verify and pin the patch version before the workshop.
kubernetesVersion: v1.36.1
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
apiServer:
certSANs:
- 127.0.0.1
- localhost
- kubeadm.local
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF
18.7 Initialize the Cluster
sudo kubeadm init --config clusters/kubeadm/init.yaml
Configure kubectl:
mkdir -p ~/.kube
sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config
sudo chown "$(id -u):$(id -g)" ~/.kube/config
kubectl get nodes
18.8 Install CNI
Example with Flannel:
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
Wait:
kubectl get pods -A -w
18.9 Allow Single-Node Scheduling
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
kubectl get nodes
18.10 Test Demo
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web --image=nginx:1.27 -n demo
kubectl expose deployment web --port=80 --target-port=80 -n demo
kubectl port-forward -n demo --address 0.0.0.0 svc/web 8080:80
Browser:
http://localhost:8080
18.11 Clean Up kubeadm
sudo kubeadm reset -f
sudo rm -rf ~/.kube /etc/cni/net.d /var/lib/cni /var/lib/kubelet /var/lib/etcd
sudo systemctl restart containerd
18.12 Technical Positioning
For the workshop:
kubeadmon WSL is an advanced demo, not the robust standard environment.- For kubeadm fundamentals, a native Ubuntu VM is better.
- For Kubernetes objects, Deployments, and Ingress,
kindis usually more appropriate.
19. Variant F: k3s Directly in WSL
k3s is a lightweight Kubernetes distribution. It can run in WSL if systemd and networking are cleanly configured.
19.1 When to Use k3s
Use k3s directly in WSL when you want to:
- test a lightweight single-node distribution,
- inspect K3s-specific behavior,
- practice system service operations,
- avoid Docker-container nodes as used by
k3d.
19.2 Installation
curl -sfL https://get.k3s.io | \
INSTALL_K3S_EXEC="--write-kubeconfig-mode=644" \
sh -
Check:
sudo systemctl status k3s --no-pager
kubectl --kubeconfig /etc/rancher/k3s/k3s.yaml get nodes
Use the kubeconfig:
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown "$(id -u):$(id -g)" ~/.kube/config
kubectl get nodes -o wide
19.3 Demo
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web --image=nginx:1.27 -n demo
kubectl expose deployment web --port=80 --target-port=80 -n demo
kubectl port-forward -n demo --address 0.0.0.0 svc/web 8080:80
Browser:
http://localhost:8080
19.4 Cleanup
sudo /usr/local/bin/k3s-uninstall.sh
rm -rf ~/.kube/config
20. Variant G: MicroK8s, k0s, and Other Options
20.1 MicroK8s
MicroK8s is a compact Kubernetes distribution from Canonical. On Windows, it is often installed through a dedicated path with WSL or Multipass.
Basic classification:
| Point | Assessment |
|---|---|
| Beginner friendliness | High |
| Snap dependency | Must fit the environment |
| Add-ons | Very convenient |
| Workshop focus | Good when MicroK8s is explicitly part of the course |
| Default for this document | No; kind is more general and reproducible |
20.2 k0s
k0s is another Kubernetes distribution with its own installation and operations model.
You can inspect it under WSL for specialized labs. For general workshop exercises, kind, minikube, or k3d is usually faster.
20.3 Podman-Based Variants
Some tools support Podman or rootless containers. This is possible under WSL, but more failure-prone for mixed workshops.
Recommendation:
- Docker Desktop WSL Integration for standard workshops,
- native Docker Engine in WSL for Linux-oriented workshops,
- Podman/rootless only as an advanced topic.
21. Managing Remote Clusters from WSL
WSL is very useful as a Linux CLI environment for remote clusters.
21.1 Store kubeconfig
mkdir -p ~/.kube
chmod 700 ~/.kube
cp /mnt/c/Users/<windows-user>/Downloads/kubeconfig ~/.kube/config
chmod 600 ~/.kube/config
21.2 Check Context
kubectl config get-contexts
kubectl config current-context
kubectl cluster-info
kubectl get nodes -o wide
21.3 Use Multiple kubeconfigs
mkdir -p ~/.kube/configs
cp kubeconfig-dev ~/.kube/configs/dev.yaml
cp kubeconfig-prod ~/.kube/configs/prod.yaml
export KUBECONFIG="$HOME/.kube/configs/dev.yaml:$HOME/.kube/configs/prod.yaml"
kubectl config view --flatten > ~/.kube/config.merged
mv ~/.kube/config.merged ~/.kube/config
chmod 600 ~/.kube/config
21.4 Security Rule
Do not share admin.conf, cloud kubeconfigs, or tokens in the workshop files. Kubeconfigs are credentials.
22. Ingress, Gateway, and Browser Access
22.1 Basic Rule
For Edge to reach a Kubernetes endpoint, three things must be true:
- The hostname must resolve on Windows.
- The target port must be reachable from Windows.
- The cluster must be able to route the hostname.
22.2 Standard for kind
Windows hosts file: 127.0.0.1 demo.local
kind extraPortMappings: hostPort 80 -> containerPort 80
Ingress: host demo.local
Browser: http://demo.local
22.3 Standard for k3d
Windows hosts file: 127.0.0.1 demo.local
k3d port mapping: 80:80@loadbalancer
Ingress: host demo.local
Browser: http://demo.local
22.4 Standard for minikube
For robust Windows/WSL workshops:
kubectl port-forward --address 0.0.0.0 svc/web 8080:80
Windows hosts file: 127.0.0.1 demo.local
Browser: http://demo.local:8080
Alternative:
minikube tunnel
Service type LoadBalancer
Browser depends on tunnel address and routing
22.5 Gateway API as Optional Lab
Gateway API can complement Ingress in modern workshops.
Example structure:
flowchart TD
Browser[Edge Browser] --> Hosts[Windows hosts file]
Hosts --> Gateway[Gateway Listener]
Gateway --> Route[HTTPRoute]
Route --> Service[Service]
Service --> Pod[Pod]
Example objects depend on the Gateway controller. For local labs, you can use Envoy Gateway, Contour, or Gateway support in your Ingress controller.
23. Certificates and Local TLS Development
23.1 Local TLS Target State
For TLS labs, you need:
- a local hostname,
- a certificate for that hostname,
- an Ingress or Gateway with TLS configuration,
- browser trust or an explicit exception.
23.2 Create a Self-Signed Certificate
mkdir -p certs
openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout certs/demo.local.key \
-out certs/demo.local.crt \
-subj "/CN=demo.local" \
-addext "subjectAltName=DNS:demo.local,DNS:api.demo.local,DNS:grafana.demo.local"
Kubernetes Secret:
kubectl create secret tls demo-local-tls \
--cert=certs/demo.local.crt \
--key=certs/demo.local.key \
-n demo \
--dry-run=client -o yaml | kubectl apply -f -
Ingress with TLS:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
spec:
ingressClassName: nginx
tls:
- hosts:
- demo.local
secretName: demo-local-tls
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
23.3 Trust the Certificate in Windows
For real browser trust chains, use a local CA such as mkcert.
Installation idea:
sudo apt install -y libnss3-tools
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-*-linux-amd64
sudo mv mkcert-*-linux-amd64 /usr/local/bin/mkcert
mkcert -install
mkcert demo.local api.demo.local grafana.demo.local
With WSL, note: trusting the CA in WSL does not automatically make it trusted in Windows Edge. For Edge, the CA must be installed in the Windows certificate store. The simplest path is to install mkcert natively on Windows as well or import the CA manually.
24. Registry, Images, and Build Flows
24.1 Load an Image Directly into kind
docker build -t demo-app:0.1.0 .
kind load docker-image demo-app:0.1.0 --name workshop
Manifest:
image: demo-app:0.1.0
imagePullPolicy: IfNotPresent
24.2 Build an Image Directly in minikube
eval "$(minikube docker-env)"
docker build -t demo-app:0.1.0 .
Then use it in the cluster:
image: demo-app:0.1.0
imagePullPolicy: IfNotPresent
Back to the normal Docker environment:
eval "$(minikube docker-env -u)"
24.3 Import an Image into k3d
docker build -t demo-app:0.1.0 .
k3d image import demo-app:0.1.0 -c workshop
24.4 Use a Local Registry
For longer workshops, a local registry is cleaner than repeated image loading.
docker run -d --restart=always \
--name local-registry \
-p 127.0.0.1:5001:5000 \
registry:2
Image:
docker build -t localhost:5001/demo-app:0.1.0 .
docker push localhost:5001/demo-app:0.1.0
Make sure the cluster can also reach the registry. With kind and k3d, you need appropriate container-network or registry configuration.
24.5 Diagnose ImagePull Issues
kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
kubectl get pod <pod> -n <namespace> -o jsonpath='{.status.containerStatuses[*].state.waiting.reason}'
Common causes:
- wrong image name,
- missing tag,
- private registry without Secret,
- cluster cannot reach local registry,
imagePullPolicy: Alwayseven though the image is only loaded locally.
25. Storage in WSL-Based Clusters
25.1 Principle
Local storage in WSL is suitable for labs, but it is not equivalent to production storage.
25.2 kind Storage
kind includes a local StorageClass mechanism by default.
kubectl get storageclass
kubectl get pv,pvc -A
PVC test:
kubectl create namespace storage-demo --dry-run=client -o yaml | kubectl apply -f -
cat > /tmp/pvc.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
namespace: storage-demo
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
EOF
kubectl apply -f /tmp/pvc.yaml
kubectl get pvc -n storage-demo
25.3 minikube Storage
minikube addons list | grep -i storage || true
kubectl get storageclass
25.4 WSL Files as Bind Mounts
For Docker-based clusters, you can mount host directories. Prefer Linux paths under /home, not /mnt/c.
Example for kind:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraMounts:
- hostPath: /home/<you>/workshops/data
containerPath: /data
25.5 Backup Rule
For workshop data:
- store manifests in Git,
- put generated data under
~/tmpor~/clusters, - do not use production secrets,
- keep clusters disposable,
- back up important notes outside cluster storage.
26. Network Model and Common Pitfalls
26.1 WSL NAT and localhost
In the default mode, WSL2 runs behind NAT. Applications listening on localhost in WSL are usually reachable from the Windows host through localhost. This is sufficient for kubectl port-forward and many Ingress port mappings.
26.2 Mirrored Networking
Mirrored networking reflects Windows network interfaces more directly into WSL. This can help with VPNs and LAN access, but it can also introduce new firewall questions.
26.3 Firewall
If Edge cannot reach the service:
Test-NetConnection localhost -Port 8080
Test-NetConnection demo.local -Port 80
Check:
- Windows Defender Firewall,
- enterprise endpoint security,
- Docker Desktop firewall hints,
- blocked ports,
- occupied ports.
26.4 Find Occupied Ports
PowerShell:
Get-NetTCPConnection -LocalPort 80 -ErrorAction SilentlyContinue
Get-Process -Id (Get-NetTCPConnection -LocalPort 80).OwningProcess
WSL:
sudo ss -tulpn | grep -E ':80|:443|:8080|:8443' || true
26.5 Distinguish DNS Problems
Windows:
Resolve-DnsName demo.local
WSL:
getent hosts demo.local
nslookup demo.local || true
Cluster:
kubectl run dns-debug --rm -it --restart=Never --image=busybox:1.36 -- nslookup kubernetes.default.svc.cluster.local
26.6 Distinguish Ingress Problems
kubectl get ingress -A
kubectl describe ingress -n demo web
kubectl get pods -n ingress-nginx -o wide
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=100
curl -v -H 'Host: demo.local' http://127.0.0.1
27. Provisioning Flow for the Workshop Files
27.1 Goal
Prepare the workshop environment reproducibly from WSL.
General flow:
flowchart TD
Material[Get workshop material] --> Check[Run preflight checks]
Check --> Tools[Install tools]
Tools --> Runtime[Check Docker or runtime]
Runtime --> Variant[Choose cluster variant]
Variant --> Cluster[Create cluster]
Cluster --> Addons[Install add-ons]
Addons --> Hosts[Set Windows hosts file]
Hosts --> Validate[Validate]
Validate --> Labs[Workshop labs]
27.2 Get the Workshop Material
Open the documentation in the Orthevs wiki and download the example ZIPs you need from the Kubernetes workshop page. Extract the packages into ~/workshops/kubernetes-workshop.
27.3 Use Dry Run
If you use a provisioning script in your own practice environment:
./scripts/provision.sh --dry-run
For a WSL kind variant, a target could look like this:
./scripts/provision.sh --target wsl-kind --dry-run
./scripts/provision.sh --target wsl-kind
For minikube:
./scripts/provision.sh --target wsl-minikube --dry-run
./scripts/provision.sh --target wsl-minikube
For k3d:
./scripts/provision.sh --target wsl-k3d --dry-run
./scripts/provision.sh --target wsl-k3d
If the repository does not contain these targets yet, use the following conventions.
27.4 Recommended Target Names
| Target | Meaning |
|---|---|
wsl-tools |
Installs CLI tools. |
wsl-docker-check |
Checks Docker availability. |
wsl-kind |
Creates a kind cluster with Ingress port mapping. |
wsl-minikube |
Creates a minikube cluster with Docker driver. |
wsl-k3d |
Creates a k3d cluster with port mappings. |
wsl-hosts |
Creates Windows hosts scripts or starts an admin prompt. |
wsl-validate |
Checks tools, cluster, DNS, Ingress, and browser paths. |
wsl-clean |
Tears down local clusters. |
27.5 Example: Makefile
.PHONY: wsl-tools wsl-kind wsl-minikube wsl-k3d wsl-validate wsl-clean
wsl-tools:
./scripts/wsl/install-tools.sh
wsl-kind:
kind create cluster --config clusters/kind/workshop.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
wsl-minikube:
minikube start --driver=docker --cpus=4 --memory=8192
minikube addons enable ingress
minikube addons enable metrics-server
wsl-k3d:
k3d cluster create workshop --servers 1 --agents 2 --api-port 127.0.0.1:6550 -p "80:80@loadbalancer" -p "443:443@loadbalancer"
wsl-validate:
./scripts/wsl/validate.sh
wsl-clean:
kind delete cluster --name workshop || true
minikube delete || true
k3d cluster delete workshop || true
27.6 Example: Preflight Script
mkdir -p scripts/wsl
cat > scripts/wsl/preflight.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
required=(git curl jq yq kubectl docker)
missing=0
for cmd in "${required[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "MISSING: $cmd"
missing=1
else
echo "OK: $cmd -> $(command -v "$cmd")"
fi
done
if ! docker info >/dev/null 2>&1; then
echo "ERROR: Docker is not reachable. Check Docker Desktop WSL Integration or native Docker Engine."
missing=1
fi
if ! grep -qi microsoft /proc/version; then
echo "WARNING: This environment does not look like WSL."
fi
if [ "$missing" -ne 0 ]; then
exit 1
fi
EOF
chmod +x scripts/wsl/preflight.sh
Run it:
./scripts/wsl/preflight.sh
28. Validation
28.1 WSL Baseline
uname -a
cat /etc/os-release
ps -p 1 -o comm=
mount | grep drvfs || true
Expected:
- Ubuntu LTS,
- WSL2 kernel,
systemdas PID 1,/mnt/cmounted.
28.2 Docker
docker version
docker info --format '{{.ServerVersion}}'
docker run --rm hello-world
28.3 Kubernetes CLI
kubectl version --client
kubectl config get-contexts
kubectl config current-context || true
28.4 Cluster
kubectl cluster-info
kubectl get nodes -o wide
kubectl get pods -A
kubectl get events -A --sort-by=.lastTimestamp | tail -50
28.5 DNS inside the Cluster
kubectl run dns-test --rm -it --restart=Never --image=busybox:1.36 -- \
nslookup kubernetes.default.svc.cluster.local
28.6 Service and Ingress
kubectl get svc -A
kubectl get ingress -A
curl -v http://localhost || true
curl -v http://demo.local || true
28.7 Windows Browser
PowerShell:
Resolve-DnsName demo.local
Test-NetConnection demo.local -Port 80
Edge:
http://demo.local
28.8 Validation Table
| Check | Command | Expected result |
|---|---|---|
| WSL2 active | wsl -l -v | Ubuntu runs with version 2. |
| systemd active | ps -p 1 -o comm= | systemd. |
| Docker reachable | docker run --rm hello-world | Container runs. |
| kubectl installed | kubectl version --client | Client version is shown. |
| Cluster reachable | kubectl get nodes | At least one node. |
| CoreDNS running | kubectl get pods -n kube-system | DNS Pods are running. |
| Ingress running | kubectl get pods -A | grep ingress | Controller is running. |
| Windows DNS correct | Resolve-DnsName demo.local | 127.0.0.1 or target address. |
| Edge reaches app | Browser test | HTTP response from demo app. |
29. Troubleshooting
29.1 Docker Is Not Reachable in WSL
Symptom:
Cannot connect to the Docker daemon
Check:
docker context ls
docker info
ls -l /var/run/docker.sock || true
Docker Desktop solution:
- Start Docker Desktop.
- Enable WSL Integration for Ubuntu.
- Restart WSL.
wsl --shutdown
Native Docker Engine solution:
sudo systemctl status docker --no-pager
sudo systemctl restart docker
sudo usermod -aG docker "$USER"
newgrp docker
29.2 systemctl Does Not Work
Symptom:
System has not been booted with systemd
Solution:
sudo tee /etc/wsl.conf <<'EOF'
[boot]
systemd=true
EOF
PowerShell:
wsl --shutdown
Then restart WSL and check:
ps -p 1 -o comm=
29.3 Edge Cannot Reach demo.local
Check:
Resolve-DnsName demo.local
Test-NetConnection demo.local -Port 80
If DNS is wrong:
- check the Windows hosts file,
- run
ipconfig /flushdns, - reload the browser.
If the port is not reachable:
- check
kind/k3dport mapping, - check the Ingress Controller,
- check port occupancy,
- check firewall rules.
29.4 curl http://demo.local Works in WSL, but Edge Does Not
Likely cause:
- only
/etc/hostsin WSL was changed. - the Windows hosts file is missing the entry.
Solution:
notepad $env:windir\System32\drivers\etc\hosts
ipconfig /flushdns
Entry:
127.0.0.1 demo.local
29.5 Edge Works, but curl in WSL Does Not
Possible causes:
- WSL
/etc/hostshas no entry, - hosts generation overwrites entries,
- proxy or VPN affects WSL.
Check:
getent hosts demo.local
cat /etc/hosts | tail -50
For WSL-internal testing, you can set it directly:
echo '127.0.0.1 demo.local' | sudo tee -a /etc/hosts
Note: With generateHosts=true, WSL can regenerate /etc/hosts on restart.
29.6 Ingress Returns 404
Check:
kubectl get ingress -A
kubectl describe ingress -n demo web
kubectl get svc -n demo
kubectl get endpoints -n demo
curl -v -H 'Host: demo.local' http://127.0.0.1
Common causes:
- wrong hostname in Ingress,
- wrong
ingressClassName, - wrong Service port,
- Service has no Endpoints,
- Deployment Pods are not ready.
29.7 Service Has No Endpoints
kubectl get svc,endpoints,pods -n demo -o wide
kubectl describe svc web -n demo
kubectl get pods -n demo --show-labels
Most common cause:
- Service selector does not match Pod labels.
29.8 Pod Stays Pending
kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
kubectl describe nodes
Possible causes:
- not enough CPU or memory,
- PVC not bound,
- taints without tolerations,
- image cannot be pulled,
- scheduler constraints do not match.
29.9 Pod Is CrashLoopBackOff
kubectl logs <pod> -n <namespace> --previous
kubectl describe pod <pod> -n <namespace>
Possible causes:
- application does not start,
- wrong environment variable,
- missing Secret,
- wrong ConfigMap,
- probe too aggressive,
- wrong startup command.
29.10 ImagePullBackOff
kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp
Solution depending on cause:
kind load docker-image demo-app:0.1.0 --name workshop
k3d image import demo-app:0.1.0 -c workshop
eval "$(minikube docker-env)"
docker build -t demo-app:0.1.0 .
29.11 Port 80 Is Already in Use
PowerShell:
Get-NetTCPConnection -LocalPort 80 -ErrorAction SilentlyContinue
WSL:
sudo ss -tulpn | grep ':80' || true
Solution:
- change the port, for example to 8080,
- stop the blocking service,
- adjust Docker/kind/k3d port mapping.
29.12 WSL Loses DNS with VPN
Check:
cat /etc/resolv.conf
nslookup github.com || true
curl -I https://kubernetes.io || true
Possible solutions:
- update WSL,
- set
dnsTunneling=true, - set
autoProxy=true, - configure the enterprise proxy correctly,
- restart WSL.
30. Cleanup
30.1 kind
kind get clusters
kind delete cluster --name workshop
30.2 minikube
minikube delete
rm -rf ~/.minikube
30.3 k3d
k3d cluster list
k3d cluster delete workshop
30.4 Docker Desktop Kubernetes
In Docker Desktop:
- disable Kubernetes or use Reset.
CLI check:
kubectl config delete-context docker-desktop || true
30.5 kubeadm
sudo kubeadm reset -f
sudo rm -rf ~/.kube /etc/cni/net.d /var/lib/cni /var/lib/kubelet /var/lib/etcd
sudo systemctl restart containerd || true
30.6 k3s
sudo /usr/local/bin/k3s-uninstall.sh || true
30.7 Remove Windows Hosts Entries
Administrative PowerShell:
notepad $env:windir\System32\drivers\etc\hosts
ipconfig /flushdns
Or use the removal script from chapter 11.
30.8 Clean Up Docker
Caution: This deletes local containers, networks, and images depending on your selection.
docker ps -a
docker network ls
docker volume ls
docker container prune
docker network prune
docker image prune
31. Workshop Labs
31.1 Lab 1: Install WSL and Enable systemd
Goal:
- Install WSL2 with Ubuntu.
- Enable
systemd. - Check Windows/WSL interop.
Acceptance:
ps -p 1 -o comm=
systemctl list-unit-files --type=service | head
powershell.exe -NoProfile -Command '$PSVersionTable.PSVersion'
31.2 Lab 2: Use Docker from WSL
Goal:
- Connect WSL to Docker Desktop or install Docker Engine in WSL.
Acceptance:
docker version
docker run --rm hello-world
31.3 Lab 3: kind Cluster with Ingress
Goal:
- Create a
kindcluster with port mapping. - Install Ingress-NGINX.
- Reach an app in Microsoft Edge.
Acceptance:
kubectl get nodes
kubectl get pods -A
curl -H 'Host: demo.local' http://127.0.0.1
Browser:
http://demo.local
31.4 Lab 4: Automate the Windows Hosts File
Goal:
- Create a PowerShell script for hosts entries.
- Remove the entries cleanly afterward.
Acceptance:
Resolve-DnsName demo.local
ipconfig /displaydns | Select-String demo.local
31.5 Lab 5: minikube with Add-ons
Goal:
- Start
minikubewith the Docker driver. - Enable Ingress and Metrics Server.
- Test
kubectl top.
Acceptance:
minikube status
kubectl top nodes || true
kubectl get pods -A
31.6 Lab 6: k3d with Traefik or Ingress-NGINX
Goal:
- Create a K3s-based cluster.
- Route traffic through port 80.
Acceptance:
k3d cluster list
kubectl get ingress -A
curl http://demo.local
31.7 Lab 7: Image Build and Local Loading
Goal:
- Build a local image.
- Load it into
kind,minikube, ork3d.
Acceptance:
kubectl get pods -n demo
kubectl describe pod -n demo -l app=demo-app
31.8 Lab 8: Troubleshooting
Goal:
- Distinguish DNS, port, Ingress, Service, and Pod problems.
Acceptance:
kubectl get events -A --sort-by=.lastTimestamp | tail -50
kubectl describe ingress -A
kubectl get svc,endpoints,pods -n demo -o wide
32. Security and Operations Rules
32.1 No Production Data
WSL labs must not contain production:
- customer data,
- tokens,
- cloud credentials,
- private keys,
- registry credentials,
- production kubeconfigs.
32.2 Protect kubeconfigs
chmod 700 ~/.kube
chmod 600 ~/.kube/config
32.3 Check for Secrets
grep -RInE '(BEGIN PRIVATE KEY|password|token|secret|AKIA|client_secret)' . \
--exclude-dir=.git || true
32.4 Clusters Are Disposable
Local WSL clusters should be disposable at any time. The authoritative state lives in:
- Git,
- documented scripts,
- versioned manifests,
- reproducible configurations.
32.5 Use Admin Rights Deliberately
You need administrator rights only for:
- WSL installation,
- specific Windows features,
- the hosts file,
- possibly firewall rules,
- Docker Desktop installation.
Do not run Kubernetes labs permanently from an administrative shell.
33. Checklists
33.1 Windows Preflight
| Item | Status |
|---|---|
| Windows 11 up to date | |
| Virtualization enabled in BIOS/UEFI | |
| WSL installed | |
| WSL updated | |
| Ubuntu runs with WSL2 | |
| Docker Desktop or Docker Engine available | |
| Hosts file can be edited as Administrator | |
| Enterprise proxy clarified | |
| No production secrets in the lab |
33.2 WSL Preflight
| Item | Command | Status |
|---|---|---|
| Ubuntu version | cat /etc/os-release |
|
| systemd | ps -p 1 -o comm= |
|
| sudo | sudo -v |
|
| Docker | docker run --rm hello-world |
|
| kubectl | kubectl version --client |
|
| helm | helm version |
|
| Git | git status |
|
| Windows interop | powershell.exe -NoProfile -Command '$PSVersionTable' |
33.3 Cluster Check
| Item | Command | Status |
|---|---|---|
| Correct context | kubectl config current-context |
|
| Nodes Ready | kubectl get nodes |
|
| System Pods Ready | kubectl get pods -A |
|
| DNS works | kubectl run dns-test ... |
|
| Demo Service has Endpoints | kubectl get endpoints -n demo |
|
| Ingress exists | kubectl get ingress -A |
|
| Browser access works | http://demo.local |
33.4 Cleanup Check
| Item | Command | Status |
|---|---|---|
| kind deleted | kind get clusters |
|
| minikube deleted | minikube status |
|
| k3d deleted | k3d cluster list |
|
| k3s removed | systemctl status k3s |
|
| kubeadm reset | kubectl get nodes |
|
| Hosts entries removed | Check Windows hosts file | |
| Docker leftovers checked | docker ps -a |
34. Reference Commands
34.1 WSL Commands in PowerShell
wsl --install -d Ubuntu-24.04
wsl --update
wsl --version
wsl --status
wsl --list --verbose
wsl --set-default-version 2
wsl --set-version Ubuntu-24.04 2
wsl --shutdown
wsl -d Ubuntu-24.04
34.2 Windows Hosts File
notepad $env:windir\System32\drivers\etc\hosts
ipconfig /flushdns
Resolve-DnsName demo.local
Test-NetConnection demo.local -Port 80
34.3 WSL System Check
uname -a
cat /etc/os-release
ps -p 1 -o comm=
systemctl is-system-running || true
ip addr
ip route
cat /etc/resolv.conf
34.4 Docker
docker version
docker info
docker ps
docker network ls
docker volume ls
docker run --rm hello-world
34.5 kubectl
kubectl version --client
kubectl config get-contexts
kubectl config current-context
kubectl cluster-info
kubectl get nodes -o wide
kubectl get pods -A
kubectl get events -A --sort-by=.lastTimestamp | tail -50
34.6 kind
kind version
kind create cluster --name workshop
kind create cluster --config clusters/kind/workshop.yaml
kind get clusters
kind load docker-image demo-app:0.1.0 --name workshop
kind delete cluster --name workshop
34.7 minikube
minikube version
minikube start --driver=docker --cpus=4 --memory=8192
minikube status
minikube addons list
minikube addons enable ingress
minikube addons enable metrics-server
minikube service <service> -n <namespace> --url
minikube tunnel
minikube delete
34.8 k3d
k3d version
k3d cluster create workshop --servers 1 --agents 2 --api-port 127.0.0.1:6550 -p "80:80@loadbalancer" -p "443:443@loadbalancer"
k3d cluster list
k3d kubeconfig get workshop
k3d image import demo-app:0.1.0 -c workshop
k3d cluster delete workshop
34.9 kubeadm
sudo kubeadm init --config clusters/kubeadm/init.yaml
mkdir -p ~/.kube
sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config
sudo chown "$(id -u):$(id -g)" ~/.kube/config
kubectl get nodes
sudo kubeadm reset -f
34.10 k3s
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode=644" sh -
sudo systemctl status k3s --no-pager
kubectl --kubeconfig /etc/rancher/k3s/k3s.yaml get nodes
sudo /usr/local/bin/k3s-uninstall.sh
35. Sources and Further Reading
Primary references for this document:
- Microsoft Learn: Install WSL
https://learn.microsoft.com/en-us/windows/wsl/install - Microsoft Learn: WSL configuration with
.wslconfigandwsl.conf
https://learn.microsoft.com/en-us/windows/wsl/wsl-config - Microsoft Learn: Network access with WSL
https://learn.microsoft.com/en-us/windows/wsl/networking - Docker Docs: Docker Desktop WSL2 Backend
https://docs.docker.com/desktop/features/wsl/ - Docker Docs: Docker Desktop Kubernetes
https://docs.docker.com/desktop/use-desktop/kubernetes/ - Kubernetes Docs: Install kubectl
https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/ - Kubernetes Docs: kubeadm
https://kubernetes.io/docs/reference/setup-tools/kubeadm/ - Kubernetes Docs: Install kubeadm
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ - Kubernetes Docs: Creating a cluster with kubeadm
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ - Kubernetes Docs: Container Runtimes
https://kubernetes.io/docs/setup/production-environment/container-runtimes/ - kind Documentation
https://kind.sigs.k8s.io/docs/user/quick-start/ - kind Configuration and Port Mappings
https://kind.sigs.k8s.io/docs/user/configuration/ - kind on WSL2
https://kind.sigs.k8s.io/docs/user/using-wsl2/ - minikube Start and Drivers
https://minikube.sigs.k8s.io/docs/start/
https://minikube.sigs.k8s.io/docs/drivers/ - minikube App Access
https://minikube.sigs.k8s.io/docs/handbook/accessing/ - k3d Documentation
https://k3d.io/ - K3s Documentation
https://docs.k3s.io/ - MicroK8s WSL2
https://microk8s.io/docs/install-wsl2
Appendix A: Minimal kind End-to-End Quickstart
# 1. Check Docker
docker run --rm hello-world
# 2. kind configuration
mkdir -p clusters/kind
cat > clusters/kind/workshop.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: workshop
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- role: worker
EOF
# 3. Create cluster
kind create cluster --config clusters/kind/workshop.yaml
# 4. Install Ingress
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=180s
# 5. Deploy demo
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web --image=nginx:1.27 -n demo
kubectl expose deployment web --port=80 --target-port=80 -n demo
cat > /tmp/ingress.yaml <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
spec:
ingressClassName: nginx
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
EOF
kubectl apply -f /tmp/ingress.yaml
# 6. Test from WSL
curl -H 'Host: demo.local' http://127.0.0.1
Windows hosts file:
127.0.0.1 demo.local
Edge:
http://demo.local
Appendix B: Minimal minikube End-to-End Quickstart
# 1. Start cluster
minikube start --driver=docker --cpus=4 --memory=8192
# 2. Add-ons
minikube addons enable ingress
minikube addons enable metrics-server
# 3. Demo
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web --image=nginx:1.27 -n demo
kubectl expose deployment web --port=80 --target-port=80 -n demo
# 4. Stable browser access through port-forward
kubectl port-forward -n demo --address 0.0.0.0 svc/web 8080:80
Edge:
http://localhost:8080
Appendix C: Minimal k3d End-to-End Quickstart
# 1. Create cluster
k3d cluster create workshop \
--servers 1 \
--agents 2 \
--api-port 127.0.0.1:6550 \
-p "80:80@loadbalancer" \
-p "443:443@loadbalancer"
# 2. Demo
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deployment web --image=nginx:1.27 -n demo
kubectl expose deployment web --port=80 --target-port=80 -n demo
# 3. Ingress
cat > /tmp/k3d-demo-ingress.yaml <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
spec:
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
EOF
kubectl apply -f /tmp/k3d-demo-ingress.yaml
# 4. Test
curl -H 'Host: demo.local' http://127.0.0.1
Windows hosts file:
127.0.0.1 demo.local
Edge:
http://demo.local
Appendix D: Recommended Workshop Default
For most groups, use:
Windows 11
-> WSL2
-> Ubuntu LTS
-> Docker Desktop WSL Integration
-> kind
-> Ingress-NGINX
-> Windows hosts file
-> Edge Browser
Why:
- fastest to reproduce,
- low VM overhead,
- good alignment with CI clusters,
- suitable for the Kubernetes object model, Deployments, Services, Ingress, ConfigMaps, Secrets, RBAC, and troubleshooting,
- easy cleanup.
For kubeadm, HA, CNI deep dives, etcd, node lifecycle, and production-like operations, switch to native VM, bare-metal, or remote clusters.