Windows + WSL2 + k3s + Helm + Argo CD
(sudo kubectl assumed / no port-forward / persistent GUI)
This article explains how to build k3s on WSL2 (Ubuntu) running on Windows,
and set up a GitOps environment with Helm + Argo CD, exposing the GUI persistently without using port-forward.
Based on real troubleshooting experience, this guide prioritizes reproducibility above all else.
Assumptions & Design Policy (Important)
- k3s is used as a development-only single-node cluster
sudo kubectlis the default on the server (WSL) side- The canonical kubeconfig is
/etc/rancher/k3s/k3s.yaml - We do not force kubectl to run as a non-root user
- Windows is treated purely as an entry point (Ingress / GUI)
This policy fully aligns with the design philosophy described in the official k3s documentation.
Goal (Final State)
- k3s running on WSL2 (Ubuntu)
- Cluster operations performed via
sudo kubectl - Argo CD exposed persistently via Ingress (Traefik)
- No use of
kubectl port-forward - Resilient to WSL IP address changes
- Accessible from Windows browser at
http://argocd.localhost
1. Preparing WSL2 / Ubuntu (Windows)
Install WSL2 (if not already installed)
wsl --install
Verify status
wsl --list --verbose
- Ubuntu must exist
- Version must be
2
2. Initial Ubuntu Setup (WSL)
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl ca-certificates gnupg lsb-release
3. Installing k3s (WSL)
3.1 Installation
curl -sfL https://get.k3s.io | sh -
3.2 Verify startup (as per official docs)
sudo kubectl get nodes
If the node shows Ready, k3s is working correctly.
4. k3s and sudo kubectl
k3s is designed to be managed as root, including:
/etc/rancher/k3s/k3s.yaml- containerd
- CNI / Traefik
Therefore:
sudo kubectlis the defaultpermission deniederrors are not abnormal- For development, reproducibility is preferred over permission tuning
5. Installing Helm (WSL)
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
6. Installing Argo CD (sudo kubectl)
6.1 Create namespace
sudo kubectl create namespace argocd
6.2 Apply manifests
sudo kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
6.3 Verify startup
sudo kubectl get pods -n argocd
sudo kubectl get svc -n argocd
7. Exposing Argo CD via Ingress (No port-forward)
Traefik is enabled by default in k3s.
7.1 Run argocd-server in HTTP mode
sudo kubectl -n argocd patch deployment argocd-server \
--type='json' \
-p='[
{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--insecure"}
]'
sudo kubectl rollout restart deployment argocd-server -n argocd
7.2 Create Ingress (Traefik)
sudo kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd
namespace: argocd
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: argocd.localhost
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 80
EOF
Verification:
sudo kubectl get ingress -n argocd
sudo kubectl describe ingress argocd -n argocd
8. Connectivity Check from WSL (Critical)
Verify Ingress behavior directly from inside WSL:
curl -H "Host: argocd.localhost" http://127.0.0.1
If HTML is returned, k3s + Traefik + Argo CD are fully functional.
9. Editing Windows hosts File
Open Notepad as Administrator:
notepad C:\Windows\System32\drivers\etc\hosts
Append:
127.0.0.1 argocd.localhost
Verify:
ping argocd.localhost
10. Forwarding Windows → WSL (Ingress Port 80)
Note: This is separate from the Kubernetes API port (6443).
Administrator PowerShell:
$wslIp = (wsl hostname -I).Trim().Split(" ")[0]
netsh interface portproxy reset
netsh interface portproxy add `
v4tov4 `
listenaddress=127.0.0.1 `
listenport=80 `
connectaddress=$wslIp `
connectport=80
Verify:
netsh interface portproxy show all
11. Accessing Argo CD from Browser
http://argocd.localhost
Retrieve login credentials (WSL):
sudo kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d && echo
- user:
admin - password: output of the command above
12. Handling WSL IP Changes (Windows Script)
Since WSL IPs change on restart, create a script to update portproxy automatically.
12.1 Script Creation
%USERPROFILE%\update-argocd-portproxy.ps1
# ==========================================================
# k3s on WSL portproxy updater
# - API Server : 127.0.0.1:6443 -> WSL:6443
# - Ingress : 127.0.0.1:80 -> WSL:80
# ==========================================================
Write-Host "Starting k3s portproxy updater..." -ForegroundColor Cyan
# Ensure WSL is running
wsl -e bash -c "true" | Out-Null
# Retrieve WSL IP
$wslIp = (wsl hostname -I).Trim().Split(" ")[0]
if (-not $wslIp) {
Write-Error "WSL IP address not found"
exit 1
}
Write-Host "WSL IP detected: $wslIp"
# Reset existing portproxy rules
netsh interface portproxy reset | Out-Null
# ----------------------------------------------------------
# Kubernetes API Server (kubectl / Lens / VS Code)
# ----------------------------------------------------------
netsh interface portproxy add `
v4tov4 `
listenaddress=127.0.0.1 `
listenport=6443 `
connectaddress=$wslIp `
connectport=6443
Write-Host "API portproxy updated : 127.0.0.1:6443 -> $wslIp:6443" -ForegroundColor Green
# ----------------------------------------------------------
# Ingress (Argo CD / Web UI)
# ----------------------------------------------------------
netsh interface portproxy add `
v4tov4 `
listenaddress=127.0.0.1 `
listenport=80 `
connectaddress=$wslIp `
connectport=80
Write-Host "Ingress portproxy updated : 127.0.0.1:80 -> $wslIp:80" -ForegroundColor Green
Write-Host "k3s portproxy update completed." -ForegroundColor Cyan
Execute:
powershell -ExecutionPolicy Bypass -File $env:USERPROFILE\update-argocd-portproxy.ps1
13. Summary
- k3s is most stable when operated with
sudo kubectl - Follow the platform’s design philosophy instead of fighting permissions
- Combine Ingress + hosts + portproxy for a persistent GUI
- Avoiding port-forward significantly improves developer experience
- Argo CD provides a natural entry point into GitOps workflows
What to Do Next (Follow-up Topics)
- Managing Helm values via GitOps
- App-of-apps pattern
- Enabling HTTPS with cert-manager
- Differences when migrating from k3s to EKS/GKE