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
  • Rollbackgit revert to undo any change
  • Self-healing — Argo CD corrects manual drift automatically

Prerequisites

  • k3s cluster with 4 nodes (see k3s installation)
  • kubectl configured 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+k3s1

Step 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-gitops

Key directories:

  • bootstrap/argocd/ — Argo CD installation and App of Apps configuration
  • apps/ — Application deployments with Kustomize base + overlays
  • infrastructure/ — 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.yaml
apiVersion: 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.yaml

Apply 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 argocd

Or 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     Running

Step 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/lab

The overlay applies:

  • namespace.yaml — Creates the boutique namespace
  • ingress.yaml — Traefik ingress for the frontend
  • kustomization.yaml — References base manifests, patches out the LoadBalancer service

Verify:

kubectl get pods -n boutique
# All 12 services should show Running

Step 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: 80

Argo 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: insecure

This 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: true

Configure 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: true

This 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=true

The Flow

  1. Root app points to bootstrap/argocd/apps/
  2. Argo CD discovers boutique-app.yaml
  3. Boutique app syncs manifests from apps/online-boutique/overlays/lab
  4. To add a new app: drop another *-app.yaml in bootstrap/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        Healthy

Access Services

ServiceURLMethod
Online Boutiquehttp://boutique.lab.localBrowser
Argo CD UIhttp://argocd.lab.localBrowser

Get the Argo CD admin password:

kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d; echo

Test 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 -w

Troubleshooting

IssueFix
Argo CD returns 500Verify ServersTransport and IngressRoute are applied
Pods stuck in PendingCheck kubectl describe pod <name> -n boutique for resource constraints
Ingress not routingVerify /etc/hosts has correct IP, check kubectl get ingress -A
Argo CD app OutOfSyncRun argocd app diff online-boutique to see drift

Next Steps

Proceed to Phase 1: Observability to add Prometheus, Grafana, and Loki to your cluster.