Skip to main content
  1. Posts/
  2. Learning ArgoCD/
  3. Environments/

eu-staging Environment: k3d with its own ArgoCD

·723 words·4 mins
Ravi Singh
Author
Ravi Singh
Software engineer with 15+ years building backend systems and cloud platforms across fintech, automotive, and academia. I write about the things I build, debug, and learn — so I don’t forget them.
Learning ArgoCD - This article is part of a series.
Part 2: This Article

eu-staging Environment: k3d with its own ArgoCD
#

What We Built
#

A staging environment on a k3d cluster, following the same isolated-ArgoCD pattern established in eu-prod-minikube. This completes the three-tier pipeline:

1
2
3
eu-dev-rancher  →  eu-staging-k3d  →  eu-prod-minikube
  (Rancher/k3s)      (k3d)              (Minikube)
  ArgoCD (dev)       ArgoCD (staging)   ArgoCD (prod)

All environments run the same image versions: svc1:nginx:1.27, svc2:nginx:1.26. Promotion means bumping these tags in Git - ArgoCD on each cluster picks up the change.


k3d vs Minikube
#

Minikubek3d
Runs onVM (Docker Machine or HyperKit)Docker containers
Startup time~60s~10s
LoadBalancer accessminikube tunnel assigns real IPHost port mapping to 127.0.0.1
Built-in TraefikNoYes (k3s default - we disable it)
Multi-node clustersLimitedEasy (--agents N)
Resource overheadHigherLower

Key insight: k3d wraps k3s inside Docker containers. k3s includes Traefik by default, but since we manage Traefik via ArgoCD, we disable the built-in one at cluster creation.


Port Mapping in k3d
#

Minikube uses minikube tunnel to expose LoadBalancer services on real IPs (e.g. 192.168.x.x). k3d uses a different model: at cluster creation you bind host ports to the cluster’s internal load balancer.

1
2
Browser → host:8443 → k3d-lb container:443 → Traefik:443 → Ingress → Pod
Browser → host:8880 → k3d-lb container:80  → Traefik:80  → Ingress → Pod

All staging hostnames resolve to 127.0.0.1 in /etc/hosts. The non-standard ports (8880/8443 instead of 80/443) avoid requiring root privileges and don’t conflict with other services on the host.


Cluster Setup
#

1. Install k3d
#

1
brew install k3d

2. Create the cluster
#

1
2
3
4
k3d cluster create eu-staging \
  --port "8880:80@loadbalancer" \
  --port "8443:443@loadbalancer" \
  --k3s-arg "--disable=traefik@server:0"

Flags explained:

  • --port "8880:80@loadbalancer" - map host port 8880 to the k3d load balancer’s port 80
  • --port "8443:443@loadbalancer" - map host port 8443 to the k3d load balancer’s port 443
  • --k3s-arg "--disable=traefik@server:0" - disable k3s’s built-in Traefik on the server node; ArgoCD will manage it

The kubectl context is automatically set to k3d-eu-staging.

1
2
3
# Verify
kubectl config current-context   # → k3d-eu-staging
kubectl get nodes

3. Install ArgoCD
#

1
2
3
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=180s

4. Bootstrap
#

1
kubectl apply -f environments/eu-staging-k3d/bootstrap.yaml

This applies the root Application, which points ArgoCD at environments/eu-staging-k3d/platform/. ArgoCD then deploys everything in sync-wave order:

WaveWhat deploys
0cert-manager
1cert-manager-config (ClusterIssuer), reloader
2Traefik
3argocd-config (ingress + password + insecure mode)
4ApplicationSet → svc1, svc2

5. Add hostnames to /etc/hosts
#

1
2
3
4
5
sudo tee -a /etc/hosts <<'EOF'
127.0.0.1  svc1.eu-staging-k3d.ravikrs.local
127.0.0.1  svc2.eu-staging-k3d.ravikrs.local
127.0.0.1  argocd.eu-staging-k3d.ravikrs.local
EOF

6. Access
#

ServiceURL
ArgoCD UIhttps://argocd.eu-staging-k3d.ravikrs.local:8443
svc1https://svc1.eu-staging-k3d.ravikrs.local:8443
svc2https://svc2.eu-staging-k3d.ravikrs.local:8443

Login: admin / admin (same bcrypt hash as eu-prod-minikube).


Environment Configuration
#

eu-dev-ranchereu-staging-k3deu-prod-minikube
ClusterRancher Desktopk3dMinikube
svc1 imagenginx:1.27nginx:1.27nginx:1.27
svc2 imagenginx:1.26nginx:1.26nginx:1.26
Replicas112
Namespacealpha-devalpha-stagingalpha-prod
Hostnames*.eu-dev-rancher.ravikrs.local*.eu-staging-k3d.ravikrs.local*.eu-prod-minikube.ravikrs.local
Host ports80 / 443 (Rancher default)8880 / 8443minikube tunnel

Cluster Lifecycle
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Stop (pause) - preserves state
k3d cluster stop eu-staging

# Start again
k3d cluster start eu-staging

# Delete completely
k3d cluster delete eu-staging

# List all k3d clusters
k3d cluster list

Gotchas
#

k3s built-in Traefik must be disabled
#

If you forget --disable=traefik@server:0, k3s installs its own Traefik on port 80/443 inside the cluster. When ArgoCD then tries to install Traefik via Helm, the two instances conflict. Always disable it at cluster creation - it cannot be cleanly disabled after the fact.

Port mapping is set at creation time
#

Unlike Minikube where you run minikube tunnel on demand, k3d port mappings are baked in at k3d cluster create. To change them you must delete and recreate the cluster.

Browser will show certificate warning
#

The TLS certificate is issued by the local local-ca-issuer (self-signed). The browser will warn on first visit. Trust the cert in Keychain (macOS) or use --insecure with curl.

/etc/hosts uses 127.0.0.1, not a cluster IP
#

k3d’s load balancer listens on localhost. Unlike Minikube’s tunnel which gives you a routable cluster IP, all k3d traffic goes through 127.0.0.1:<host-port>. This means you cannot access staging from another machine on the same network (Minikube tunnel can allow this).

Learning ArgoCD - This article is part of a series.
Part 2: This Article