Skip to main content
RunBook Academy

← All labs in Git, CI/CD & GitOps

Lab · intermediate · ~100 min

Lab 19: Design and validate an Argo CD installation

C · SimulationB · Nested virtualisation

Objectives

  • Pin the teaching target separately from the minimum-supported version
  • Install Argo CD into the correct namespace and wait for its core workloads: two Deployments and the controller StatefulSet
  • Access the API with `kubectl port-forward` instead of unmatched kind port mappings
  • Require a real repository URL reachable by the controller
  • Define phase-two reconciliation and cleanup evidence

Prerequisites

Design decisions

  • The current teaching target is Kubernetes 1.36.x with Argo CD 3.5.x. Minimum support is documented separately; it is not silently used as the lab target.
  • The lab uses kubectl port-forward service/argocd-server 8443:443. kind extraPortMappings are not a substitute for a Kubernetes NodePort Service; when they are used, the node containerPort and Service nodePort must match. This lab needs neither.
  • An Argo CD repository must be reachable from the repo-server pod. A path on the learner’s workstation is not a cluster-reachable repository.
  • The Application is not rendered until the learner supplies a non-placeholder REPO_URL and pushes the workload to it.

Task 1 — Record the compatibility contract

# check-shell-blocks: allow-invalid
LAB="$HOME/argocd-install-lab"
mkdir -p "$LAB/workload"
cd "$LAB"

cat > versions.env <<'EOF'
KIND_VERSION_TARGET=0.31.x
KUBERNETES_VERSION_TARGET=1.36.x
KIND_NODE_IMAGE=kindest/node:v1.36.1
ARGOCD_VERSION_TARGET=v3.5.0
ARGOCD_VERSION_MINIMUM=v3.0.0
EOF

Phase two records the immutable kind node image digest as well as the tag. The tag keeps this design readable; the captured digest proves what ran.

Task 2 — Author the kind configuration

# check-shell-blocks: allow-invalid
cd "$HOME/argocd-install-lab"

cat > kind-config.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: argocd-lab
nodes:
  - role: control-plane
    image: kindest/node:v1.36.1
  - role: worker
    image: kindest/node:v1.36.1
EOF

There are deliberately no extraPortMappings. API access is through the kubeconfig, and Argo CD UI/API access is through an explicit port-forward.

Task 3 — Author the namespace-correct installation

# check-shell-blocks: allow-invalid
cd "$HOME/argocd-install-lab"

cat > install-argocd.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

CLUSTER_NAME="${CLUSTER_NAME:-argocd-lab}"
ARGOCD_NS="${ARGOCD_NS:-argocd}"
ARGOCD_VERSION="${ARGOCD_VERSION:-v3.5.0}"
CONTEXT="kind-${CLUSTER_NAME}"

kind get clusters | grep -Fxq "$CLUSTER_NAME" || {
  echo "kind cluster $CLUSTER_NAME does not exist" >&2
  exit 2
}
kubectl --context "$CONTEXT" get nodes >/dev/null

kubectl --context "$CONTEXT" create namespace "$ARGOCD_NS" \
  --dry-run=client -o yaml | kubectl --context "$CONTEXT" apply -f -

kubectl --context "$CONTEXT" apply --server-side -n "$ARGOCD_NS" -f \
  "https://raw.githubusercontent.com/argoproj/argo-cd/${ARGOCD_VERSION}/manifests/install.yaml"

for deployment in argocd-server argocd-repo-server; do
  kubectl --context "$CONTEXT" -n "$ARGOCD_NS" rollout status \
    "deployment/${deployment}" --timeout=5m
done

kubectl --context "$CONTEXT" -n "$ARGOCD_NS" rollout status \
  "statefulset/argocd-application-controller" --timeout=5m

printf '%s\n' \
  "Argo CD is ready in namespace ${ARGOCD_NS}." \
  "In another terminal: kubectl --context ${CONTEXT} -n ${ARGOCD_NS} port-forward service/argocd-server 8443:443"
EOF

chmod 0750 install-argocd.sh
bash -n install-argocd.sh

The repo-server wait uses $ARGOCD_NS, not the kind cluster name. Every namespaced Argo CD operation goes through the same variable. Note the workload kinds: argocd-server and argocd-repo-server are Deployments, but argocd-application-controller is a StatefulSet in the official install manifests, so its readiness wait targets statefulset/, not deployment/.

Task 4 — Create the Git-managed workload

# check-shell-blocks: allow-invalid
cd "$HOME/argocd-install-lab"

cat > workload/namespace.yaml <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: argocd-demo
EOF

cat > workload/deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
  namespace: argocd-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
        - name: web
          image: nginx:1.27.4
          ports:
            - name: http
              containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 2
            periodSeconds: 3
EOF

Before phase two, place these files in a Git repository reachable from the kind network and push them to the main branch. A private repository also requires an Argo CD repository credential configured through the documented Secret format or CLI.

Task 5 — Author and safely render the Application

# check-shell-blocks: allow-invalid
cd "$HOME/argocd-install-lab"

cat > application.yaml.template <<'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: demo
  namespace: argocd
spec:
  project: default
  source:
    repoURL: REPLACE_WITH_CLUSTER_REACHABLE_GIT_URL
    targetRevision: main
    path: workload
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd-demo
  syncPolicy:
    automated:
      enabled: true
      prune: false
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
EOF

cat > render-application.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
: "${REPO_URL:?set REPO_URL to a Git URL reachable from the cluster}"
case "$REPO_URL" in
  *REPLACE_WITH*|file://*|'') echo 'REPO_URL must be a cluster-reachable Git URL' >&2; exit 2 ;;
esac
cp application.yaml.template application.yaml
REPO_URL="$REPO_URL" yq -i '.spec.source.repoURL = strenv(REPO_URL)' application.yaml
EOF

chmod 0750 render-application.sh
bash -n render-application.sh

No public repository is invented by the lesson. The phase-two operator owns the repository, proves it is reachable from repo-server, and records the revision Argo CD reconciles.

Task 6 — Define phase-two evidence

# check-shell-blocks: allow-invalid
cd "$HOME/argocd-install-lab"

cat > phase-two-evidence.md <<'EOF'
# Phase-two execution evidence

- [ ] kind and kubectl client versions captured.
- [ ] kind node image tag and immutable digest captured.
- [ ] All nodes report Ready on Kubernetes 1.36.x.
- [ ] Argo CD 3.5.x server and repo-server Deployment rollouts and the application-controller StatefulSet rollout complete in namespace argocd.
- [ ] Port-forward reaches the Argo CD API without a NodePort mapping.
- [ ] Repository is reachable from argocd-repo-server and its exact commit SHA is recorded.
- [ ] Application becomes Synced and Healthy at that SHA.
- [ ] A Git change reconciles; a Git revert restores the previous workload.
- [ ] Application, Argo CD namespace, and kind cluster are removed.
- [ ] Logs and command transcript are archived with credentials redacted.
EOF

Task 7 — Run local phase-one assertions

cd "$HOME/argocd-install-lab"

bash -n install-argocd.sh render-application.sh
grep -F 'namespace "${ARGOCD_NS}"' install-argocd.sh
if grep -q 'extraPortMappings' kind-config.yaml; then
  echo 'FAIL: this design should use port-forward, not node port mappings' >&2
  exit 1
fi
grep -F 'service/argocd-server 8443:443' install-argocd.sh
grep -F 'REPLACE_WITH_CLUSTER_REACHABLE_GIT_URL' application.yaml.template
grep -F 'ARGOCD_VERSION_TARGET=v3.5.0' versions.env

Validation

  • Current and minimum versions are distinct.
  • The kind design has no unmatched port mapping.
  • The install script targets argocd consistently and parses with Bash.
  • The Application refuses a filesystem or placeholder repository URL.
  • The source repository is learner-owned rather than fictitious.
  • Runtime reconciliation, rollback, and cleanup remain explicit phase-two gates rather than claimed results.

Expected outcome

The phase-one deliverables describe a coherent Argo CD 3.5 / Kubernetes 1.36 installation and a real source-of-truth contract. They remove the known namespace, exposure, repository, and version defects without pretending that static validation proves a running control plane.

Deliverables

  • · versions.env — current target and minimum-supported versions
  • · kind-config.yaml — a two-node Kubernetes 1.36 cluster without invalid port mappings
  • · install-argocd.sh — namespace-correct, pinned installation script
  • · workload/ — the manifest content pushed to a real Git repository
  • · application.yaml.template — Argo CD Application with an explicit repository placeholder
  • · phase-two-evidence.md — live reconciliation, rollback, and cleanup assertions

Verification status

Last reviewed
2026-08-25
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.