Phase 0 - Argo CD & GitOps Deployment Guide
Series: Kubernetes Homelab on VMware Workstation Prerequisites: 4-node k3s cluster running (1 control-plane + 3 workers) Source Code: jmartinez-homelab-gitops
What We’re Building
By the end of this guide, you will have:
- Argo CD managing your cluster via GitOps
- Google’s Online Boutique microservices demo deployed
- Traefik ingress routing for all services
- A clean, production-style directory structure
Why Argo CD + GitOps
Instead of running kubectl apply manually, your cluster continuously watches a Git repository. When you push a change to YAML, Argo CD detects the drift and automatically syncs the cluster to match. This gives you:
- Declarative state — Git is the single source of truth
- Audit trail — Every change is a commit
- Rollback —
git revertto undo any change - Self-healing — Argo CD corrects manual drift automatically
Prerequisites
- k3s cluster with 4 nodes (see k3s installation)
-
kubectlconfigured to access your cluster - Git repo created (we’ll use GitHub)
Verify your cluster:
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# k3s-server Ready control-plane 1d v1.34.3+k3s1
# k3s-agent-1 Ready <none> 1d v1.34.3+k3s1
# k3s-agent-2 Ready <none> 1d v1.34.3+k3s1
# k3s-agent-3 Ready <none> 1d v1.34.3+k3s1Step 1: Project Structure
Clone or create this directory layout:
homelab-gitops/
├── apps/
│ └── online-boutique/
│ ├── base/
│ │ ├── kustomization.yaml
│ │ └── manifests.yaml
│ └── overlays/
│ └── lab/
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ └── ingress.yaml
├── bootstrap/
│ └── argocd/
│ ├── apps/
│ │ ├── kustomization.yaml
│ │ └── boutique-app.yaml
│ ├── kustomization.yaml
│ ├── root-app.yaml
│ ├── argocd-ingressroute.yaml
│ ├── argocd-serverstransport.yaml
│ └── bootstrap.sh
└── infrastructure/
└── monitoring/
├── base/
└── overlays/
git clone https://github.com/james-martinez0/jmartinez-homelab-gitops.git
cd jmartinez-homelab-gitopsKey directories:
bootstrap/argocd/— Argo CD installation and App of Apps configurationapps/— Application deployments with Kustomize base + overlaysinfrastructure/— Cluster-wide services (monitoring, networking)
Step 2: Install Argo CD
Argo CD is installed via Kustomize, pulling the official manifests directly from the Argo CD repo.
Review the kustomization:
cat bootstrap/argocd/kustomization.yamlapiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: argocd
resources:
- https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
- argocd-ingressroute.yaml
- argocd-serverstransport.yaml
- root-app.yamlApply it:
# Create namespace and install Argo CD
kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -k bootstrap/argocd/
# Wait for Argo CD to be ready
kubectl wait --for=condition=available --timeout=600s deployment/argocd-server -n argocdOr use the bootstrap script:
cd bootstrap/argocd
./bootstrap.sh
cd ../..Verify all pods are running:
kubectl get pods -n argocd
# NAME READY STATUS
# argocd-application-controller-0 1/1 Running
# argocd-applicationset-controller-xxxxxxxxx-xxxxx 1/1 Running
# argocd-dex-server-xxxxxxxxx-xxxxx 1/1 Running
# argocd-notifications-controller-xxxxxxxxx-xxxxx 1/1 Running
# argocd-redis-xxxxxxxxx-xxxxx 1/1 Running
# argocd-repo-server-xxxxxxxxx-xxxxx 1/1 Running
# argocd-server-xxxxxxxxx-xxxxx 1/1 RunningStep 3: Deploy Online Boutique
Online Boutique is Google’s microservices demo — 12 services communicating via gRPC. Perfect for demonstrating Kubernetes patterns.
The app is deployed using Kustomize with a base layer and environment-specific overlays:
# Validate the build
kubectl kustomize apps/online-boutique/overlays/lab
# Deploy
kubectl apply -k apps/online-boutique/overlays/labThe overlay applies:
namespace.yaml— Creates theboutiquenamespaceingress.yaml— Traefik ingress for the frontendkustomization.yaml— References base manifests, patches out the LoadBalancer service
Verify:
kubectl get pods -n boutique
# All 12 services should show RunningStep 4: Expose Services via Traefik
Since K3s includes Traefik as its ingress controller, we use it to expose services. Both the app and Argo CD UI need ingress — but they can’t both use the same host/path rules. We solve this with host-based routing.
Online Boutique Frontend
Standard Kubernetes Ingress with a hostname:
# apps/online-boutique/overlays/lab/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: frontend-ingress
namespace: boutique
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: boutique.lab.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80Argo CD UI
Argo CD forces HTTPS redirects. A standard Ingress targeting port 80 will hit a 307 → HTTPS loop. The fix is to use Traefik’s IngressRoute CRD which can terminate TLS at the proxy and forward to Argo CD’s HTTPS port.
# bootstrap/argocd/argocd-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: argocd-server
namespace: argocd
spec:
entryPoints:
- web
routes:
- match: Host(`argocd.lab.local`)
kind: Rule
services:
- name: argocd-server
port: 443
serversTransport: insecureThis requires a ServersTransport to skip TLS verification (Argo CD uses a self-signed cert):
# bootstrap/argocd/argocd-serverstransport.yaml
apiVersion: traefik.io/v1alpha1
kind: ServersTransport
metadata:
name: insecure
namespace: argocd
spec:
insecureSkipVerify: trueConfigure DNS
Add to your laptop’s hosts file:
Linux/Mac: /etc/hosts
Windows: C:\Windows\System32\drivers\etc\hosts
<NODE_IP> boutique.lab.local argocd.lab.local
Replace <NODE_IP> with any of your node IPs (e.g., 192.168.5.40).
Step 5: App of Apps Pattern
The App of Apps pattern lets Argo CD manage multiple applications from a single root Application. When you add a new Application YAML to the apps directory, Argo CD automatically discovers and deploys it.
Root Application
# bootstrap/argocd/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-application
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/james-martinez0/jmartinez-homelab-gitops.git
targetRevision: HEAD
path: bootstrap/argocd/apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: trueThis Application points to bootstrap/argocd/apps/, which contains child Application resources.
Child Application
# bootstrap/argocd/apps/boutique-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: online-boutique
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/james-martinez0/jmartinez-homelab-gitops.git
targetRevision: HEAD
path: apps/online-boutique/overlays/lab
destination:
server: https://kubernetes.default.svc
namespace: boutique
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueThe Flow
- Root app points to
bootstrap/argocd/apps/ - Argo CD discovers
boutique-app.yaml - Boutique app syncs manifests from
apps/online-boutique/overlays/lab - To add a new app: drop another
*-app.yamlinbootstrap/argocd/apps/
Step 6: Verify Everything
Argo CD Status
# Check application sync status
kubectl get application -n argocd
# NAME SYNC STATUS HEALTH STATUS
# online-boutique Synced Healthy
# root-application Synced HealthyAccess Services
| Service | URL | Method |
|---|---|---|
| Online Boutique | http://boutique.lab.local | Browser |
| Argo CD UI | http://argocd.lab.local | Browser |
Get the Argo CD admin password:
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d; echoTest GitOps
Make a change and watch Argo CD sync:
# Edit a replica count in the boutique manifests
# Commit and push
git add -A && git commit -m "test: scale frontend" && git push
# Watch Argo CD detect and sync
kubectl get application online-boutique -n argocd -wTroubleshooting
| Issue | Fix |
|---|---|
| Argo CD returns 500 | Verify ServersTransport and IngressRoute are applied |
| Pods stuck in Pending | Check kubectl describe pod <name> -n boutique for resource constraints |
| Ingress not routing | Verify /etc/hosts has correct IP, check kubectl get ingress -A |
| Argo CD app OutOfSync | Run argocd app diff online-boutique to see drift |
Next Steps
Proceed to Phase 1: Observability to add Prometheus, Grafana, and Loki to your cluster.