Deploy and manage ArgoCD using the App-of-Apps pattern. Covers bootstrap Applications, ApplicationSets, and GitOps workflows. Use when setting up or troubleshooting ArgoCD deployments.
ArgoCD bootstrap follows the "App-of-Apps" pattern: a root Application that creates other Applications. This enables GitOps-driven deployment of all cluster resources from a single Git repository.
Terraform
ā
ArgoCD Helm Chart Installation
ā
Bootstrap Application (created by Terraform)
ā
ApplicationSet (created by Bootstrap)
ā
Individual Applications (created by ApplicationSet)
ā
Runner Scale Sets (managed by Applications)
Terraform creates a single "root" Application that points to the ArgoCD manifests directory:
resource "kubectl_manifest" "argocd_bootstrap" {
yaml_body = yamlencode({
apiVersion = "argoproj.io/v1alpha1"
kind = "Application"
metadata = {
name = "bootstrap"
namespace = "argocd"
}
spec = {
project = "default"
source = {
repoURL = "https://github.com/Matchpoint-AI/matchpoint-github-runners-helm"
targetRevision = "main"
path = "argocd" # Directory with ApplicationSets
}
destination = {
server = "https://kubernetes.default.svc"
namespace = "argocd"
}
syncPolicy = {
automated = {
prune = true
selfHeal = true
}
}
}
})
depends_on = [
helm_release.argocd,
time_sleep.wait_for_crds
]
}
Key fields:
path: "argocd" - Directory containing ApplicationSets and ApplicationstargetRevision: "main" - Git branch to sync (use specific tag for production)automated.prune: true - Remove resources deleted from Gitautomated.selfHeal: true - Revert manual changes to match GitThe argocd/ directory contains ApplicationSet manifests:
# argocd/applicationset-runners.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: github-runners
namespace: argocd
spec:
generators:
- list:
elements:
- name: arc-beta-runners
namespace: arc-runners
valuesFile: examples/beta-runners-values.yaml
- name: arc-frontend-runners
namespace: arc-frontend-runners
valuesFile: examples/frontend-runners-values.yaml
template:
metadata:
name: '{{name}}'
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/Matchpoint-AI/matchpoint-github-runners-helm
targetRevision: main
path: charts/github-actions-runners
helm:
releaseName: '{{name}}' # CRITICAL: Must match runnerScaleSetName
valueFiles:
- '../../{{valuesFile}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
Key concepts:
Generators - Define list of Applications to create:
generators:
- list:
elements:
- name: arc-beta-runners # Application name
namespace: arc-runners # Target namespace
valuesFile: examples/beta.yaml # Helm values file
Template - Define Application spec using generator variables:
template:
metadata:
name: '{{name}}' # Substituted from generator
Helm configuration:
helm:
releaseName: '{{name}}' # MUST match runnerScaleSetName in values!
valueFiles:
- '../../{{valuesFile}}' # Path relative to chart directory
The ApplicationSet generates individual Applications for each runner pool:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: arc-beta-runners
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/Matchpoint-AI/matchpoint-github-runners-helm
targetRevision: main
path: charts/github-actions-runners
helm:
releaseName: arc-beta-runners
valueFiles:
- ../../examples/beta-runners-values.yaml
destination:
server: https://kubernetes.default.svc
namespace: arc-runners
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Issue #112 Root Cause: ApplicationSet releaseName didn't match Helm chart runnerScaleSetName:
# ApplicationSet
releaseName: arc-runners # ā Kubernetes resource name
# examples/runners-values.yaml
runnerScaleSetName: "arc-beta-runners" # ā
GitHub label
Impact:
arc-runnersarc-beta-runnersBOTH must match:
# argocd/applicationset-runners.yaml
generators:
- list:
elements:
- name: arc-beta-runners # Used for releaseName
template:
spec:
source:
helm:
releaseName: '{{name}}' # = arc-beta-runners
# examples/beta-runners-values.yaml
gha-runner-scale-set:
runnerScaleSetName: "arc-beta-runners" # MUST MATCH releaseName
Workflow must also match:
# .github/workflows/ci.yaml
jobs:
build:
runs-on: arc-beta-runners # MUST MATCH runnerScaleSetName
Static list of elements:
generators:
- list:
elements:
- name: app1
env: prod
- name: app2
env: staging
Generate from files in Git repository:
generators:
- git:
repoURL: https://github.com/org/repo
revision: main
files:
- path: "apps/*/config.yaml"
Combine multiple generators:
generators:
- matrix:
generators:
- list:
elements:
- cluster: prod
- cluster: staging
- list:
elements:
- app: frontend
- app: backend
# Creates: prod-frontend, prod-backend, staging-frontend, staging-backend
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Revert manual kubectl changes
Use when:
syncPolicy: {} # No automated sync
Use when:
syncOptions:
- CreateNamespace=true # Create destination namespace if missing
- PruneLast=true # Delete old resources after new ones ready
- Replace=true # Use kubectl replace instead of apply
matchpoint-github-runners-helm/
āāā argocd/
ā āāā applicationset-runners.yaml # Creates runner Applications
ā āāā applications/ # Individual Applications (legacy)
ā ā āāā arc-controller.yaml # ARC controller Application
ā āāā apps-live/ # Alternative app definitions (not used)
āāā charts/
ā āāā github-actions-runners/ # Helm chart deployed by Applications
āāā examples/
āāā beta-runners-values.yaml # Values for arc-beta-runners
āāā frontend-runners-values.yaml # Values for arc-frontend-runners
Active paths:
argocd/applicationset-runners.yaml - Generates runner Applicationsargocd/applications/arc-controller.yaml - Deploys ARC controllerInactive paths:
argocd/apps-live/ - Old structure, not referenced by bootstrapFor resources that don't need templating:
# argocd/applications/arc-controller.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: arc-controller
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/Matchpoint-AI/matchpoint-github-runners-helm
targetRevision: main
path: charts/github-actions-controller
destination:
server: https://kubernetes.default.svc
namespace: arc-systems
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
For resources that need multiple instances with different configurations:
# argocd/applicationset-runners.yaml
# (See full example above)
Symptom:
error: unable to recognize "argocd/bootstrap.yaml": no matches for kind "Application"
Cause: ArgoCD CRDs not registered yet
Fix:
resource "time_sleep" "wait_for_crds" {
depends_on = [helm_release.argocd]
create_duration = "30s"
}
Symptom:
kubectl get application arc-beta-runners -n argocd
# Status: OutOfSync
Diagnosis:
kubectl describe application arc-beta-runners -n argocd
# Check status.conditions for error details
Common causes:
targetRevision branch doesn't existpath directory not found in repovalueFiles path incorrectFix:
# Force refresh
kubectl annotate application arc-beta-runners -n argocd \
argocd.argoproj.io/refresh="hard" --overwrite
# Manual sync
kubectl patch application arc-beta-runners -n argocd \
-p '{"operation":{"initiatedBy":{"automated":true}}}' --type=merge
Symptom:
ComparisonError: failed to render manifests: helm template failed
Diagnosis:
# Get error details
kubectl get application arc-beta-runners -n argocd -o yaml | grep -A 10 conditions
# Test helm template locally
helm template arc-beta-runners ./charts/github-actions-runners \
-f examples/beta-runners-values.yaml \
--namespace arc-runners
Common causes:
Symptom: Application stuck syncing, old resources not deleted
Cause: PruneLast=true waits for new resources to be Ready before deleting old ones
Fix:
# Check pod status
kubectl get pods -n arc-runners
# If pods stuck, check events
kubectl get events -n arc-runners --sort-by='.lastTimestamp' | tail -20
# If safe to force delete old resources
kubectl delete application arc-beta-runners -n argocd
kubectl apply -f argocd/applicationset-runners.yaml
# List all Applications
kubectl get applications -n argocd
# List all ApplicationSets
kubectl get applicationset -n argocd
# Check Application status
kubectl describe application arc-beta-runners -n argocd
# View Application manifest
kubectl get application arc-beta-runners -n argocd -o yaml
# Check ApplicationSet generators
kubectl get applicationset github-runners -n argocd -o yaml | grep -A 20 generators
# View ArgoCD controller logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller --tail=100
# Force Application refresh
kubectl annotate application arc-beta-runners -n argocd argocd.argoproj.io/refresh="hard" --overwrite