Skip to content
WSLDeep Dive Published Updated 7 min readViews unavailable

Building a Local Kubernetes Lab with kind on WSL 2

A repeatable kind-on-WSL workflow for local Kubernetes development, with runtime boundaries, port mapping, resource checks, and cleanup.

WSL 2 is a useful Linux development environment for building and testing Kubernetes workloads, but it is not itself a production Kubernetes host. A local cluster can help validate manifests, controllers, service discovery, and a developer workflow; it cannot prove multi-host availability, cloud load-balancer behavior, production storage, or cluster-level failure recovery. Keep that boundary explicit when choosing the runtime and interpreting test results.

This guide uses kind (Kubernetes-in-Docker) to create a disposable local cluster from node containers. On Windows, one supported workflow is Docker Desktop’s WSL 2 backend with integration enabled for the chosen Linux distribution. Another is a deliberately managed container runtime installed in WSL. Pick one owner for the container daemon: installing and starting a second Docker Engine inside the same distribution while Docker Desktop integration is also active can make the CLI target an unexpected daemon and consume resources twice.

Understand the boundary before installing tools

The WSL distribution is where you run kind, kubectl, build tools, and your source tree. The container runtime is a separate dependency that starts the Kubernetes node containers. With Docker Desktop, enable WSL integration for the specific distro in Docker Desktop settings, and verify that the Linux CLI reaches the intended daemon. Docker documents its WSL backend prerequisites and integration model; exact UI labels and minimum versions can change, so use the current vendor documentation rather than an old screenshot.

Keep the repository in the Linux filesystem, for example under ~/src, if Linux build tools and containers will perform most file operations. This avoids making the Windows-mounted /mnt/c path the default for metadata-heavy builds. Before creating a cluster, check that the selected WSL distro is version 2, the runtime is responsive, and the WSL host has enough CPU, memory, and free disk for both the node image and workloads:

wsl.exe --list --verbose
wsl.exe --status
docker context show
docker info --format '{{.ServerVersion}}'
df -h "$HOME"

If Docker commands are not available or docker info fails, resolve the daemon/integration problem before installing kind. Do not mask a missing daemon by switching contexts blindly; identify the context and endpoint the CLI is using. Docker Desktop and WSL resource limits affect workloads beyond this cluster because WSL 2 distributions share host resources.

Install kind and kubectl from their maintained sources

Install kind using its current upstream installation instructions for the Linux architecture in the distro, and install a kubectl client compatible with the Kubernetes cluster you intend to test. Pin tool versions in a team bootstrap script or developer environment rather than downloading an unreviewed moving binary on every shell start. Verify the executable paths and versions:

command -v kind
command -v kubectl
kind --version
kubectl version --client

Check that kubectl is not silently targeting a shared or remote production context. kind creates a dedicated kubeconfig context named kind-<cluster-name>; choose it explicitly for every command that can mutate resources.

Create a small cluster with an explicit host port

For Docker Desktop, kind’s documented extra port mappings provide a path from the host-side published port to a kind node port. Save this as kind-wsl.yaml in a dedicated lab directory:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30080
        hostPort: 8080
        listenAddress: "127.0.0.1"
        protocol: TCP

Create the cluster and wait for readiness:

kind create cluster --config ./kind-wsl.yaml --name wsl-dev --wait 5m
kubectl config current-context
kubectl cluster-info --context kind-wsl-dev
kubectl get nodes -o wide

The port mapping reserves 127.0.0.1:8080 on the host-side runtime path and forwards it to port 30080 on the control-plane node. It does not create a Kubernetes Service or prove that a Windows browser can reach the listener. Start with a request from the same WSL distribution; then separately verify Windows access using the active WSL networking mode and documented localhost-forwarding behavior. Do not set the listener to 0.0.0.0 just to make a failing test pass; that broadens which interfaces can connect.

The cluster definition is intentionally one control-plane node. Add workers only when a test requires multi-node scheduling or node-affinity behavior. Multiple kind nodes still share one Windows host and its failure domain; they do not simulate independent machines, availability zones, or resilient control planes.

Deploy and test a minimal HTTP workload

Save the following as web.yaml. For a repeatable team test, replace the moving nginx:stable tag with an approved image pinned by digest; the floating tag here is convenient for a disposable smoke test, not a supply-chain or reproducibility policy.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:stable
          ports:
            - name: http
              containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - name: http
      port: 80
      targetPort: http
      nodePort: 30080

Apply it only after confirming the selected context:

test "$(kubectl config current-context)" = "kind-wsl-dev"
kubectl apply --context kind-wsl-dev -f ./web.yaml
kubectl --context kind-wsl-dev rollout status deployment/web --timeout=120s
kubectl --context kind-wsl-dev get pods,services -o wide
curl --fail --show-error http://127.0.0.1:8080/

The Deployment’s selector must match the Pod template labels; the Service selector must match those Pod labels; and the kind mapping’s containerPort must match the Service’s NodePort. A successful rollout plus an HTTP response checks image pull, scheduling, readiness, service selection, node port, and the runtime’s host mapping in one short path. It still does not test an Ingress controller, an external cloud load balancer, persistent storage, or production DNS.

Keep resource use and cluster state intentional

Each Kubernetes node is a container, but the cluster consumes real host disk and memory. The image layers remain after Pods are deleted; they are not equivalent to a running process’s memory. Inspect docker ps, docker system df, kubectl get pods -A, and WSL’s current memory/disk configuration before concluding that Kubernetes itself leaked resources. Avoid routinely running multiple nested local clusters, Docker build caches, databases, and IDE indexing inside a small memory limit without measuring actual pressure.

Do not store irreplaceable data in a disposable kind cluster. Recreating the cluster deletes its node-local state. Use temporary data for a lab, or use a deliberate external development database/storage service. A local PersistentVolume backed by the host can help test a manifest shape, but it does not reproduce a cloud CSI driver, access modes, snapshots, or node failure behavior.

Diagnose common failures in order

  • If docker info cannot reach a daemon, fix the runtime integration or selected Docker context before looking at Kubernetes YAML.
  • If kind times out creating a node, inspect the container runtime’s node container and logs, then check free disk, memory pressure, and image download access.
  • If a Pod is ImagePullBackOff, inspect kubectl describe pod and events; distinguish registry DNS/network/authentication from an invalid image reference.
  • If the Pod is ready but the host request fails, verify the Service selector and NodePort, kind port mapping, listening address, and the request from inside WSL before testing the Windows-to-WSL path.
  • If a scheduling test does not behave as it does in a real cluster, verify node count, labels, taints, and resource availability. A single host cannot reproduce independent-node outages.

Gather bounded evidence rather than repeatedly deleting and recreating everything:

kubectl --context kind-wsl-dev get events --sort-by=.lastTimestamp
kubectl --context kind-wsl-dev describe pod -l app=web
docker ps --filter name=wsl-dev

Delete the lab when it is no longer needed:

kind delete cluster --name wsl-dev

Then verify kind get clusters no longer lists wsl-dev. This is a destructive operation for that local cluster’s API objects and node-local data; it does not delete source manifests saved outside the cluster.

Definition of a useful local-cluster test

This workflow is ready for a developer when the team can recreate the cluster from checked-in configuration, explicitly select the intended kind context, run a workload and a host-port smoke test, collect useful Kubernetes events on failure, and remove the lab without touching another kubeconfig context. Keep a separate staging or production environment for tests whose success criterion depends on multiple hosts, real cloud integrations, persistence guarantees, network policies, or production availability.

Related:

Sources:

Comments