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. kindextraPortMappingsare not a substitute for a Kubernetes NodePort Service; when they are used, the nodecontainerPortand ServicenodePortmust 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_URLand 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
argocdconsistently 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.