(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 kubectl is 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 kubectl is the default
  • permission denied errors 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

discussion