Install K3d on WSL Ubuntu with Gateway API support (NGF)
Setting up k3d on WSL2 Ubuntu with NGINX Gateway Fabric & Custom Docker Engine
Building a lightweight Kubernetes lab environment inside Windows Subsystem for Linux (WSL2) offers seamless speed and developer flexibility. However, combining modern architectures like the standard Kubernetes Gateway API via NGINX Gateway Fabric (NGF) while discarding the pre-baked cluster ingress configuration takes precise choreography.
This tutorial details a comprehensive engineering map to cleanly initialize a fresh WSL2 Ubuntu installation, decouple standard ingress controllers, compile natively backed Docker Engines, and implement a live-routed mock architecture setup.
Step 1: Systemd Initialization & Distro Optimization
Fresh installations of WSL2 Ubuntu natively omit standard systemd control configurations. Because standard native container engines require service daemons to consistently scale backgrounds, we must explicitly declare boot parameters.
Execute system packages alignment:
sudo apt update && sudo apt upgrade -y
Modify or create the internal WSL initialization metadata block via /etc/wsl.conf:
sudo nano /etc/wsl.conf
Append the target specification layout directly within the system configuration framework:
[boot] systemd=true
Save and write structural changes via Ctrl+O, Enter, and exit using Ctrl+X. To ensure your host machine cleanly reinitializes the background partition, drop down to an external Windows PowerShell instance and force clear runtime processes:
wsl --shutdown
Relaunch your target Linux environment tab to bring the updated systemd context completely live.
Step 2: Bare-Metal Docker Engine Setup (Avoiding Desktop overhead)
To avoid massive software layers or license bounds from external host dashboards, we install the native Linux enterprise variant directly inside the kernel environment.
First, implement target prerequisite verification systems:
sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg
In modern Ubuntu builds, dropping absolute system keys to old binary paths throw GPG verification faults. If you encounter a NO_PUBKEY 7EA0A9C3F273FCD8 verification lock during setup, execute this updated armored format initialization sequence to clear the signature map:
# Force clean old references sudo rm -f /etc/apt/sources.list.d/docker.list # Fetch the updated ascii armor key architecture sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc
With correct armor maps established, write the repository path into your source tree lists:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Trigger source parsing and download core functional engine runtime units:
sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Elevate privileges for your standard non-root shell profile to avoid structural sudo wrappers during local cluster builds:
sudo usermod -aG docker $USER newgrp docker
Validate total loop integrity by running a containerized health test instance:
docker run hello-world
Step 3: Cluster Tooling Matrix Acquisition
With operational container environments running, configure standard control paths by pulling specific orchestration, configuration management, and hypervisor command tools:
# 1. Acquire Kubernetes Target Command Interface (kubectl) curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl rm kubectl # 2. Acquire Helm Package Management Systems curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # 3. Acquire Native k3d Orchestration Boundary Tools curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
Step 4: k3d Cluster Provisioning (Stripping Traefik)
Standard k3s patterns ship with Traefik embedded as a built-in proxy. To protect target network traffic paths for NGINX Gateway Fabric, we completely disable the default setup and explicitly reserve host network interfaces on ports 80 and 443.
Provision the targeted engine cluster instance topology:
k3d cluster create my-gateway-cluster --port "80:80@loadbalancer" --port "443:443@loadbalancer" --k3s-arg "--disable=traefik@server:*"
Verify that core component services boot up smoothly with no remnants of the old default proxy layer:
kubectl get pods -A
Step 5: Modern Gateway API CRD Framework & NGF Provisioning
Because the classic Ingress resource paradigm is deprecated, we install the standardized CRD profiles managed directly by the Kubernetes SIG structure.
Pull and scale down channel resources:
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml
Unlike standard application indexes, NGINX distributes Gateway Fabric structures completely as an Open Container Initiative (OCI) package via the GitHub Container Registry. Traditional helm repo add workflows will fail with a 404 Not Found. Skip index additions entirely and download directly with an inline OCI reference flag:
helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric --create-namespace --namespace nginx-gateway --set service.type=LoadBalancer
Step 6: Core Gateway Infrastructure Declarations
An abstract control plane installation won't respond to host ports until a physical Gateway instance resource binds directly to an edge adapter profile.
Construct a target configurations manifest named gateway.yaml:
nano gateway.yaml
Populate the file blueprint with the structural tracking manifest detailed below:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: nginx-gateway
namespace: nginx-gateway
spec:
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
Apply changes directly into the orchestration environment engine:
kubectl apply -f gateway.yaml
Step 7: Smoke Testing cross-Namespace Ingress Controls
To verify proper functional routing end-to-end, create a running service instance in your target namespace environment and explicitly route to it from the cluster's gateway border using a custom HTTPRoute object.
Ensure that structural parameters like backendRefs are configured directly under individual rules nodes, rather than buried deep within structural matches arrays. Improperly nested fields throw deep API schema structural validation errors (strict decoding error: unknown field).
Deploy a demo environment instance configuration into the cluster topology:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
containers:
- name: hello
image: nginx:alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: test-app-svc
namespace: default
spec:
ports:
- port: 80
targetPort: 80
selector:
app: test-app
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: test-route
namespace: nginx-gateway
spec:
parentRefs:
- name: nginx-gateway
namespace: nginx-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: test-app-svc
namespace: default
port: 80
EOF
If curling your local endpoint displays an empty 503 Service Temporarily Unavailable banner, the NGINX control structure is functioning correctly, but the underlying service can't route traffic down to your backend instance pods. This is almost always caused by an ImagePullBackOff error during a standard Docker Hub rate limit lock. Swapping target tags over to streamlined configurations like nginx:alpine fixes pulling blocks instantly.
Step 8: Final Ingress Validation Sweep
Confirm that your app container status transitions completely into a stable 1/1 Running mode state:
kubectl get pods -n default
Now, query the host port directly from your terminal session to test routing behavior across the network:
curl http://localhost/
When the platform responds with the official NGINX welcome screen raw code payload (<!DOCTYPE html>...<title>Welcome to nginx!</title>), your stack is verified! Windows and Linux host ports are perfectly mapped straight through the custom k3d load balancer array, managed securely by the active NGINX Gateway Fabric control framework.
Comments