Workshop wiki · English

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.

Workshop examples as ZIP files
Learning progress in the web app

Mark chapters as read, view reading progress and check off practice modules after enabling the optional web app. Workshop progress is calculated from chapters across all document pages. Your choices stay on this device; there is no account or server sync. Installation is not required. Withdrawing your choice in Privacy settings deletes the progress.

Reading progress0%
0%
0 / 39 chapters read
Other language versionGerman ↗

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 with kubeadm,
  • 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.


01 / 39

2. Target State

At the end, your Windows 11 machine should have:

  • an up-to-date WSL2 distribution, preferably Ubuntu LTS,
  • systemd enabled 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 localhost integration 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.

02 / 39

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]

03 / 39

4. Supported Variants

This document covers the following provisioning paths:

  1. WSL as Kubernetes client

    • kubectl, helm, kustomize, stern, k9s, jq, yq
    • connection to remote clusters
  2. kind in WSL

    • Kubernetes nodes as Docker containers
    • good port mappings for Ingress
    • very suitable for workshops
  3. 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
  4. k3d in WSL

    • K3s in Docker
    • simple local load-balancer port exposure
    • fast for K3s-oriented labs
  5. Docker Desktop Kubernetes

    • enable Kubernetes in Docker Desktop
    • fast, but less controllable
  6. kubeadm in WSL

    • for experimental understanding only
    • not recommended as the standard lab path
  7. k3s directly in WSL

    • lightweight
    • requires attention to system services and networking
  8. MicroK8s, k0s, and other distributions

    • mentioned and classified as optional variants

04 / 39

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

05 / 39

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.

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 ip with the Docker driver is always directly reachable from the Windows browser,
  • changes to /etc/hosts automatically 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.


06 / 39

7. Install and Verify WSL2

7.1 Install WSL

Open PowerShell as Administrator.

powershell
wsl --install -d Ubuntu-24.04

If Ubuntu 24.04 is not available, list available distributions:

powershell
wsl --list --online

Then install a suitable Ubuntu LTS version:

powershell
wsl --install -d Ubuntu

7.2 Update WSL

powershell
wsl --update
wsl --version

7.3 Set WSL2 as Default

powershell
wsl --set-default-version 2

7.4 Check Installed Distributions

powershell
wsl --list --verbose

Expected example:

text
  NAME            STATE           VERSION
* Ubuntu-24.04    Running         2

If your distribution still uses WSL1:

powershell
wsl --set-version Ubuntu-24.04 2

7.5 Start the Distribution

powershell
wsl -d Ubuntu-24.04

Or via the Start menu:

text
Ubuntu 24.04

7.6 Capture Windows Information

For later troubleshooting, record:

powershell
winver
wsl --version
wsl --status
wsl --list --verbose

07 / 39

8. Prepare Ubuntu in WSL

8.1 Update the System

Inside Ubuntu on WSL:

bash
sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove -y

8.2 Install Base Tools

bash
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

bash
whoami
id
sudo -v

Your user must be able to use sudo.

8.4 Set the Time Zone

bash
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

bash
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:

bash
mkdir -p ~/workshops
cd ~/workshops

Not recommended for intensive Kubernetes and Docker workflows:

bash
cd /mnt/c/Users/<your-user>/Documents

This works, but it is often noticeably slower with many small files and bind mounts.


08 / 39

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:

powershell
notepad $env:USERPROFILE\.wslconfig

Recommended default for many workshop machines:

ini
[wsl2]
memory=8GB
processors=4
swap=4GB
localhostForwarding=true
dnsTunneling=true
autoProxy=true

[experimental]
autoMemoryReclaim=gradual
sparseVhd=true

Then fully restart WSL:

powershell
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.

ini
[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true
localhostForwarding=true

Then run:

powershell
wsl --shutdown

Notes:

  • mirrored is useful in many enterprise networks, but can conflict with firewall or endpoint-security rules in some setups.
  • For the workshop, NAT with localhostForwarding=true is often sufficient.
  • If you enable mirrored, re-test Docker, kind, minikube, DNS, and Edge access.

9.3 Enable systemd in Ubuntu

Open in WSL:

bash
sudo nano /etc/wsl.conf

Recommended workshop configuration:

ini
[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:

powershell
wsl --shutdown
wsl -d Ubuntu-24.04

Check in WSL:

bash
ps -p 1 -o comm=
systemctl is-system-running || true
systemctl list-unit-files --type=service | head

Expected output:

text
systemd

9.4 Check Windows Drives

bash
ls -ld /mnt/c
ls -l /mnt/c/Windows/System32/drivers/etc/hosts

If /mnt/c is missing:

bash
mount | grep drvfs || true

Then check wsl.conf and restart WSL.

9.5 Check Interop

bash
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.


09 / 39

10. Filesystem and Workshop Files

text
/home/<you>/workshops/
├── kubernetes-workshop/
├── clusters/
│   ├── kind/
│   ├── minikube/
│   └── k3d/
├── bin/
└── tmp/

Create it:

bash
mkdir -p ~/workshops ~/clusters/{kind,minikube,k3d} ~/bin ~/tmp

Add ~/bin to the path:

bash
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

bash
find ~/workshops/kubernetes-workshop -maxdepth 3 -type f | sort | head -100

10.4 Avoid Secrets

Before every change:

bash
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.


10 / 39

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:

bash
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:

powershell
notepad $env:windir\System32\drivers\etc\hosts

For local Ingress labs, add for example:

text
# 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:

powershell
ipconfig /flushdns

11.4 Open the Hosts File from WSL via PowerShell

bash
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:

bash
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
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Path\to\Repository\scripts\windows\add-k8s-hosts.ps1

Or start it from WSL:

bash
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

bash
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
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Path\to\Repository\scripts\windows\remove-k8s-hosts.ps1

11.7 Check from Windows

In PowerShell:

powershell
Resolve-DnsName demo.local
Test-NetConnection demo.local -Port 80

In the browser:

text
http://demo.local

11.8 Check from WSL

bash
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:

text
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: usually 127.0.0.1 or 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.

11 / 39

12. Container Runtime and Docker Strategy

Most recommended WSL variants use Docker:

  • kind requires Docker, Podman, or a compatible container runtime.
  • k3d requires Docker.
  • minikube with the Docker driver requires Docker.
  • Docker Desktop Kubernetes runs directly inside the Docker Desktop stack.

For mixed participant groups, this is usually the most stable baseline:

text
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:

  1. Open Settings.
  2. Select Resources.
  3. Open WSL Integration.
  4. Enable integration for your Ubuntu distribution.
  5. Restart Docker Desktop.

Check in WSL:

bash
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:

bash
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:

bash
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

12 / 39

13. Install Base Tooling

13.1 Treat Kubernetes Versions Deliberately

Do not hard-code the Kubernetes version in every script. Use variables:

bash
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

bash
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

bash
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

bash
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version

13.5 Check Kustomize

kubectl includes Kustomize functionality:

bash
kubectl kustomize --help | head

Optional standalone kustomize:

bash
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

bash
# 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

bash
for cmd in git curl jq yq docker kubectl helm; do
  printf '%-12s' "$cmd"
  command -v "$cmd" || true
done

13 / 39

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:

bash
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:

bash
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

bash
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:

bash
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:

bash
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:

yaml
extraPortMappings:
  - containerPort: 80
    hostPort: 8080
    protocol: TCP
  - containerPort: 443
    hostPort: 8443
    protocol: TCP

Then use in the browser:

text
http://demo.local:8080
https://demo.local:8443

14.7 Install Ingress-NGINX

bash
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

bash
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:

bash
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:

text
127.0.0.1 demo.local

Test from WSL:

bash
curl -H 'Host: demo.local' http://127.0.0.1
curl http://demo.local

Test in Microsoft Edge:

text
http://demo.local

14.9 kind with Local Registry

Start a local registry:

bash
docker run -d --restart=always -p 127.0.0.1:5001:5000 --name kind-registry registry:2

kind configuration with registry mirror:

bash
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:

bash
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:

bash
docker build -t localhost:5001/demo-app:0.1.0 .
docker push localhost:5001/demo-app:0.1.0

In the manifest:

yaml
image: localhost:5001/demo-app:0.1.0

14.10 Delete the kind Cluster

bash
kind delete cluster --name workshop
docker rm -f kind-registry || true

14 / 39

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

bash
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

bash
minikube start \
  --driver=docker \
  --cpus=4 \
  --memory=8192 \
  --disk-size=30g

Check:

bash
kubectl config current-context
kubectl get nodes -o wide
minikube status

15.4 Set a Kubernetes Version

bash
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:

bash
minikube start --driver=docker --kubernetes-version=v1.36.1

Adjust the patch version before the workshop.

15.5 Enable Add-ons

bash
minikube addons list
minikube addons enable ingress
minikube addons enable metrics-server

Check:

bash
kubectl get pods -A
kubectl top nodes || true

15.6 Demo Application

bash
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:

bash
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:

bash
kubectl port-forward -n demo --address 0.0.0.0 svc/web 8080:80

Then in Edge:

text
http://localhost:8080

Or with a hostname:

Windows hosts file:

text
127.0.0.1 demo.local

Browser:

text
http://demo.local:8080

15.8 Ingress with minikube

Enable Ingress:

bash
minikube addons enable ingress
kubectl wait -n ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=180s

Ingress object:

bash
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:

bash
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.

bash
minikube tunnel

In a second terminal:

bash
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

bash
minikube delete

15 / 39

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

bash
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

bash
k3d cluster create workshop \
  --servers 1 \
  --agents 2 \
  --api-port 127.0.0.1:6550 \
  -p "80:80@loadbalancer" \
  -p "443:443@loadbalancer"

Check:

bash
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.

bash
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:

bash
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:

text
127.0.0.1 demo.local

Test:

bash
curl http://demo.local

Browser:

text
http://demo.local

16.5 k3d without Traefik and with Ingress-NGINX

bash
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:

bash
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

bash
k3d cluster delete workshop

16 / 39

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:

  1. Open Settings.
  2. Select Kubernetes.
  3. Enable Enable Kubernetes.
  4. Apply and wait.

Check in WSL:

bash
kubectl config get-contexts
kubectl config use-context docker-desktop
kubectl get nodes -o wide

17.3 Demo

bash
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:

text
http://localhost:8080

17.4 Limits

Docker Desktop Kubernetes is convenient, but:

  • less transparent than kind, minikube, or kubeadm,
  • 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.

17 / 39

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 containerd runtime,
  • disabled swap or explicitly configured kubelet behavior,
  • required kernel and networking features,
  • willingness to troubleshoot.

18.2 Install containerd

bash
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:

bash
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd

Check:

bash
systemctl status containerd --no-pager
sudo ctr version

18.3 Kernel and Network Parameters

bash
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

bash
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

bash
swapon --show || true
free -h

If swap is active and kubelet should not use it:

bash
sudo swapoff -a

WSL swap is controlled through .wslconfig. For real kubeadm labs, define this behavior deliberately.

18.6 kubeadm Configuration

bash
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

bash
sudo kubeadm init --config clusters/kubeadm/init.yaml

Configure kubectl:

bash
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:

bash
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

Wait:

bash
kubectl get pods -A -w

18.9 Allow Single-Node Scheduling

bash
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
kubectl get nodes

18.10 Test Demo

bash
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:

text
http://localhost:8080

18.11 Clean Up kubeadm

bash
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:

  • kubeadm on 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, kind is usually more appropriate.

18 / 39

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

bash
curl -sfL https://get.k3s.io | \
  INSTALL_K3S_EXEC="--write-kubeconfig-mode=644" \
  sh -

Check:

bash
sudo systemctl status k3s --no-pager
kubectl --kubeconfig /etc/rancher/k3s/k3s.yaml get nodes

Use the kubeconfig:

bash
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

bash
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:

text
http://localhost:8080

19.4 Cleanup

bash
sudo /usr/local/bin/k3s-uninstall.sh
rm -rf ~/.kube/config

19 / 39

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.

20 / 39

21. Managing Remote Clusters from WSL

WSL is very useful as a Linux CLI environment for remote clusters.

21.1 Store kubeconfig

bash
mkdir -p ~/.kube
chmod 700 ~/.kube
cp /mnt/c/Users/<windows-user>/Downloads/kubeconfig ~/.kube/config
chmod 600 ~/.kube/config

21.2 Check Context

bash
kubectl config get-contexts
kubectl config current-context
kubectl cluster-info
kubectl get nodes -o wide

21.3 Use Multiple kubeconfigs

bash
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.


21 / 39

22. Ingress, Gateway, and Browser Access

22.1 Basic Rule

For Edge to reach a Kubernetes endpoint, three things must be true:

  1. The hostname must resolve on Windows.
  2. The target port must be reachable from Windows.
  3. The cluster must be able to route the hostname.

22.2 Standard for kind

text
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

text
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:

text
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:

text
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.


22 / 39

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

bash
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:

bash
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:

yaml
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:

bash
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.


23 / 39

24. Registry, Images, and Build Flows

24.1 Load an Image Directly into kind

bash
docker build -t demo-app:0.1.0 .
kind load docker-image demo-app:0.1.0 --name workshop

Manifest:

yaml
image: demo-app:0.1.0
imagePullPolicy: IfNotPresent

24.2 Build an Image Directly in minikube

bash
eval "$(minikube docker-env)"
docker build -t demo-app:0.1.0 .

Then use it in the cluster:

yaml
image: demo-app:0.1.0
imagePullPolicy: IfNotPresent

Back to the normal Docker environment:

bash
eval "$(minikube docker-env -u)"

24.3 Import an Image into k3d

bash
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.

bash
docker run -d --restart=always \
  --name local-registry \
  -p 127.0.0.1:5001:5000 \
  registry:2

Image:

bash
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

bash
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: Always even though the image is only loaded locally.

24 / 39

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.

bash
kubectl get storageclass
kubectl get pv,pvc -A

PVC test:

bash
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

bash
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:

yaml
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 ~/tmp or ~/clusters,
  • do not use production secrets,
  • keep clusters disposable,
  • back up important notes outside cluster storage.

25 / 39

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:

powershell
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:

powershell
Get-NetTCPConnection -LocalPort 80 -ErrorAction SilentlyContinue
Get-Process -Id (Get-NetTCPConnection -LocalPort 80).OwningProcess

WSL:

bash
sudo ss -tulpn | grep -E ':80|:443|:8080|:8443' || true

26.5 Distinguish DNS Problems

Windows:

powershell
Resolve-DnsName demo.local

WSL:

bash
getent hosts demo.local
nslookup demo.local || true

Cluster:

bash
kubectl run dns-debug --rm -it --restart=Never --image=busybox:1.36 -- nslookup kubernetes.default.svc.cluster.local

26.6 Distinguish Ingress Problems

bash
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

26 / 39

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:

bash
./scripts/provision.sh --dry-run

For a WSL kind variant, a target could look like this:

bash
./scripts/provision.sh --target wsl-kind --dry-run
./scripts/provision.sh --target wsl-kind

For minikube:

bash
./scripts/provision.sh --target wsl-minikube --dry-run
./scripts/provision.sh --target wsl-minikube

For k3d:

bash
./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.

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

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

bash
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:

bash
./scripts/wsl/preflight.sh

27 / 39

28. Validation

28.1 WSL Baseline

bash
uname -a
cat /etc/os-release
ps -p 1 -o comm=
mount | grep drvfs || true

Expected:

  • Ubuntu LTS,
  • WSL2 kernel,
  • systemd as PID 1,
  • /mnt/c mounted.

28.2 Docker

bash
docker version
docker info --format '{{.ServerVersion}}'
docker run --rm hello-world

28.3 Kubernetes CLI

bash
kubectl version --client
kubectl config get-contexts
kubectl config current-context || true

28.4 Cluster

bash
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

bash
kubectl run dns-test --rm -it --restart=Never --image=busybox:1.36 -- \
  nslookup kubernetes.default.svc.cluster.local

28.6 Service and Ingress

bash
kubectl get svc -A
kubectl get ingress -A
curl -v http://localhost || true
curl -v http://demo.local || true

28.7 Windows Browser

PowerShell:

powershell
Resolve-DnsName demo.local
Test-NetConnection demo.local -Port 80

Edge:

text
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. |


28 / 39

29. Troubleshooting

29.1 Docker Is Not Reachable in WSL

Symptom:

text
Cannot connect to the Docker daemon

Check:

bash
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.
powershell
wsl --shutdown

Native Docker Engine solution:

bash
sudo systemctl status docker --no-pager
sudo systemctl restart docker
sudo usermod -aG docker "$USER"
newgrp docker

29.2 systemctl Does Not Work

Symptom:

text
System has not been booted with systemd

Solution:

bash
sudo tee /etc/wsl.conf <<'EOF'
[boot]
systemd=true
EOF

PowerShell:

powershell
wsl --shutdown

Then restart WSL and check:

bash
ps -p 1 -o comm=

29.3 Edge Cannot Reach demo.local

Check:

powershell
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/k3d port 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/hosts in WSL was changed.
  • the Windows hosts file is missing the entry.

Solution:

powershell
notepad $env:windir\System32\drivers\etc\hosts
ipconfig /flushdns

Entry:

text
127.0.0.1 demo.local

29.5 Edge Works, but curl in WSL Does Not

Possible causes:

  • WSL /etc/hosts has no entry,
  • hosts generation overwrites entries,
  • proxy or VPN affects WSL.

Check:

bash
getent hosts demo.local
cat /etc/hosts | tail -50

For WSL-internal testing, you can set it directly:

bash
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:

bash
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

bash
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

bash
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

bash
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

bash
kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp

Solution depending on cause:

bash
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:

powershell
Get-NetTCPConnection -LocalPort 80 -ErrorAction SilentlyContinue

WSL:

bash
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:

bash
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.

29 / 39

30. Cleanup

30.1 kind

bash
kind get clusters
kind delete cluster --name workshop

30.2 minikube

bash
minikube delete
rm -rf ~/.minikube

30.3 k3d

bash
k3d cluster list
k3d cluster delete workshop

30.4 Docker Desktop Kubernetes

In Docker Desktop:

  • disable Kubernetes or use Reset.

CLI check:

bash
kubectl config delete-context docker-desktop || true

30.5 kubeadm

bash
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

bash
sudo /usr/local/bin/k3s-uninstall.sh || true

30.7 Remove Windows Hosts Entries

Administrative PowerShell:

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.

bash
docker ps -a
docker network ls
docker volume ls

docker container prune
docker network prune
docker image prune

30 / 39

31. Workshop Labs

31.1 Lab 1: Install WSL and Enable systemd

Goal:

  • Install WSL2 with Ubuntu.
  • Enable systemd.
  • Check Windows/WSL interop.

Acceptance:

bash
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:

bash
docker version
docker run --rm hello-world

31.3 Lab 3: kind Cluster with Ingress

Goal:

  • Create a kind cluster with port mapping.
  • Install Ingress-NGINX.
  • Reach an app in Microsoft Edge.

Acceptance:

bash
kubectl get nodes
kubectl get pods -A
curl -H 'Host: demo.local' http://127.0.0.1

Browser:

text
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:

powershell
Resolve-DnsName demo.local
ipconfig /displaydns | Select-String demo.local

31.5 Lab 5: minikube with Add-ons

Goal:

  • Start minikube with the Docker driver.
  • Enable Ingress and Metrics Server.
  • Test kubectl top.

Acceptance:

bash
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:

bash
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, or k3d.

Acceptance:

bash
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:

bash
kubectl get events -A --sort-by=.lastTimestamp | tail -50
kubectl describe ingress -A
kubectl get svc,endpoints,pods -n demo -o wide

31 / 39

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

bash
chmod 700 ~/.kube
chmod 600 ~/.kube/config

32.3 Check for Secrets

bash
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.


32 / 39

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

33 / 39

34. Reference Commands

34.1 WSL Commands in PowerShell

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

powershell
notepad $env:windir\System32\drivers\etc\hosts
ipconfig /flushdns
Resolve-DnsName demo.local
Test-NetConnection demo.local -Port 80

34.3 WSL System Check

bash
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

bash
docker version
docker info
docker ps
docker network ls
docker volume ls
docker run --rm hello-world

34.5 kubectl

bash
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

bash
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

bash
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

bash
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

bash
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

bash
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

34 / 39

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 .wslconfig and wsl.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

35 / 39

Appendix A: Minimal kind End-to-End Quickstart

bash
# 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:

text
127.0.0.1 demo.local

Edge:

text
http://demo.local

36 / 39

Appendix B: Minimal minikube End-to-End Quickstart

bash
# 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:

text
http://localhost:8080

37 / 39

Appendix C: Minimal k3d End-to-End Quickstart

bash
# 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:

text
127.0.0.1 demo.local

Edge:

text
http://demo.local

38 / 39

For most groups, use:

text
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.

39 / 39

K8S · Orthevs Academy

31 modules as one continuous learning path.

The sequence remains unchanged: from the lab skeleton through technical foundations and operating models to final troubleshooting. The technical directory names are deliberately preserved.

Orthevs – Engineering the foundation for what comes next.

Orthevs as an app

Install Orthevs

The Orthevs web app can be installed on supported mobile devices and desktop browsers.

iPhone and iPad

Open Orthevs in Safari. Tap Share, then Add to Home Screen. Enable Open as Web App if offered, then tap Add.

Android

For the web app, open the browser menu and choose Install app or Add to Home screen. Confirm the installation.

Desktop browser

Open the browser menu or the install icon in the address bar, then choose Install app or Add to Dock.

Web app installation depends on your browser. If no install prompt appears, follow the browser steps shown above.