Bootstrap Argo CD Without Creating a Circular Owner
Install the GitOps controller once, then let it own workload declarations without asking it to create its own cluster.
Run it in the public monorepo
This course is built around the public Zheta Kubernetes Lab monorepo. The excerpt below is runnable source, not pseudocode.
Source: argocd/applicationsets/forge-prod.yaml
source: { repoURL: "https://github.com/ZGTR/zheta-kubernetes-lab.git", targetRevision: master, path: gitops/apps/forge/overlays/prod }
destination: { server: '{{url}}', namespace: zheta-forge }
syncPolicy: { automated: { prune: true, selfHeal: true }, syncOptions: [CreateNamespace=true] }
Code to reality
- Declared intent
- Bootstrap one production-local Argo Application inside the production EKS cluster.
- Interpreter
- The in-cluster ApplicationSet controller renders the production overlay for its own Kubernetes API.
- Software effect
- Argo owns only production namespace objects and repairs or prunes divergence in that environment.
- Hardware effect
- Once release gates pass, reconciliation creates Forge containers only on production worker nodes.
- Observable evidence
- Generated Application revision, Sync and Healthy status, rollout state, and a repaired drift probe agree.
Start with the people and the result they need
The source tables below remain the detailed contract. Begin with these customer paths:
- D25-UC-01
- Person: Platform bootstrap operator
- Job: Install one pinned GitOps controller inside each environment cluster
- Observable result: Each Argo CD instance reconciles exactly one local ApplicationSet targeting https://kubernetes.default.svc
- D25-UC-02
- Person: GitOps security reviewer
- Job: Restrict repositories, namespaces, resource kinds, and production sync authority
- Observable result: forge AppProject accepts the approved repository and zheta-forge namespace only
Turn each customer job into a testable story
Now turn each customer job into a story with a result that an engineer can check:
- D25-US-01
- Story: As a platform bootstrap operator, I want one pinned in-cluster installation per environment, so that reconciliation starts from a known controller…
- Observable acceptance: Evidence links approval, source/digest, controller UIDs, local API identity, fixed destination, health, time, and run
- D25-US-02
- Story: As a GitOps security reviewer, I want project and sync policy enforced before applications appear, so that Git cannot deploy arbitrary resources or…
- Observable acceptance: Positive and negative render/sync attempts record actor, repository, path, destination, resource, expected/observed result, and audit ID
Add real state and observable proof
Finally trace each story through the system that owns its state and the evidence that proves the outcome:
- D25-FLOW-01
- Trigger: Approved platform change requests GitOps bootstrap
- Responsible systems: Local EKS API, Argo CD controllers, in-cluster identity, ApplicationSet controller
- Authoritative state: Git for bootstrap/project/ApplicationSet intent; the local Kubernetes API for installed components, Applications, and status
- Owned record: GitOpsBootstrapRevision
- Observable evidence: Approval, controller digest/UIDs, local API and context, Application name, health, environment, timestamp, run, and audit IDs
- Failure signal: API unavailable, component unready, wrong context, remote destination, or local Application missing
- D25-FLOW-02
- Trigger: Reviewer proposes an Application outside approved repository or namespace
- Responsible systems: AppProject admission, repo server, application controller, destination RBAC/audit
- Authoritative state: AppProject and Application desired/status state plus Git revision and Evidence Store
- Owned record: LocalDestinationBinding
- Observable evidence: Denied repo/path/destination/resource, project revision, approved dev result, actor, environment, time, and evidence ID
- Failure signal: Disallowed source/destination/kind accepted, production sync without approval, or positive control failure
The enterprise problem and today’s slice
Enterprise problem: GitOps becomes circular or unsafe when Argo CD is expected to recreate the EKS API that hosts it, manage its own first credential, or share one unrestricted identity across accounts. Whole-course context: The prior chapters define isolated clusters but do not claim they exist; today defines the bootstrap that runs only after one intended API answers. Today’s slice: Argo CD runs inside each private EKS cluster and applies argocd/project.yaml plus exactly one matching argocd/applicationsets/forge-{dev,staging,prod}.yaml; every ApplicationSet targets its environment-local https://kubernetes.default.svc. End-of-day evidence: A real bootstrap must record approval, pinned install digest, component UIDs, project policy, denied source/destination, environment, time, run, and audit IDs. Still unsolved: Without a reachable cluster and verified controller install, no GitOps success is claimed.
Customer use cases
Argo CD can reconcile only through an answering Kubernetes API, so its green status belongs to workload configuration rather than infrastructure existence. Bootstrap therefore has one bounded owner and an explicit handoff.
| Use case ID | Actor | Customer job | Success outcome | Denial or recovery evidence |
|---|---|---|---|---|
| D25-UC-01 | Platform bootstrap operator | Install one pinned GitOps controller inside each environment cluster | Each Argo CD instance reconciles exactly one local ApplicationSet targeting https://kubernetes.default.svc | Missing local API or wrong environment manifest fails visibly; no remote cluster credential exists |
| D25-UC-02 | GitOps security reviewer | Restrict repositories, namespaces, resource kinds, and production sync authority | forge AppProject accepts the approved repository and zheta-forge namespace only | Unapproved repository, destination, cluster-wide resource, or direct production sync is denied while an approved dev render succeeds |
Actor-centred user stories
Bootstrap and steady-state reconciliation have different privileges, so using one permanent administrator violates least privilege. These stories require an auditable handoff.
| Story ID | Use case IDs | User story | Observable acceptance conditions |
|---|---|---|---|
| D25-US-01 | D25-UC-01 | As a platform bootstrap operator, I want one pinned in-cluster installation per environment, so that reconciliation starts from a known controller revision and stays environment-local | Evidence links approval, source/digest, controller UIDs, local API identity, fixed destination, health, time, and run |
| D25-US-02 | D25-UC-02 | As a GitOps security reviewer, I want project and sync policy enforced before applications appear, so that Git cannot deploy arbitrary resources or cross account boundaries | Positive and negative render/sync attempts record actor, repository, path, destination, resource, expected/observed result, and audit ID |
End-to-end product flows
The customer action is enabling controlled delivery, not installing pods. The denied flow proves that a reachable controller does not possess universal cluster authority.
| Flow ID | Use case IDs | Path | Trigger | Numbered steps | Terminal evidence |
|---|---|---|---|---|---|
| D25-FLOW-01 | D25-UC-01 | Happy | Approved platform change requests GitOps bootstrap | 1. Verify one environment account and kube context. 2. Apply pinned controller manifests with bootstrap role. 3. Wait for controller health. 4. Apply AppProject. 5. Apply only that environment’s ApplicationSet. 6. Verify destination is https://kubernetes.default.svc. 7. Revoke bootstrap session. | Approval, controller digest/UIDs, local API and context, Application name, health, environment, timestamp, run, and audit IDs |
| D25-FLOW-02 | D25-UC-02 | Denied | Reviewer proposes an Application outside approved repository or namespace | 1. Resolve actor and project. 2. Submit denied object. 3. Observe policy rejection or non-sync. 4. Render approved dev path as positive control. 5. Seal evidence. | Denied repo/path/destination/resource, project revision, approved dev result, actor, environment, time, and evidence ID |
The operator enables a bounded delivery capability; generated Applications are evidence that destination registrations and policy agree.
System design derived from the flows
Terraform owns VPC and EKS existence, while Argo CD owns rendered Kubernetes workload declarations. Kubernetes controllers then own runtime replicas; none of these loops replaces another.
| Use case ID | Entry point | Responsible services | Authoritative store | Failure evidence |
|---|---|---|---|---|
| D25-UC-01 | Pinned bootstrap manifest and environment-local ApplicationSet | Local EKS API, Argo CD controllers, in-cluster identity, ApplicationSet controller | Git for bootstrap/project/ApplicationSet intent; the local Kubernetes API for installed components, Applications, and status | API unavailable, component unready, wrong context, remote destination, or local Application missing |
| D25-UC-02 | argocd/project.yaml policy review | AppProject admission, repo server, application controller, destination RBAC/audit | AppProject and Application desired/status state plus Git revision and Evidence Store | Disallowed source/destination/kind accepted, production sync without approval, or positive control failure |
The bootstrap creates controllers; the project constrains their authority; the single environment-specific ApplicationSet creates one Application for the same cluster.
The three small ApplicationSets reuse one contract but deliberately bind to each cluster’s https://kubernetes.default.svc. AppProject limits source, destination, and resource kinds; official Argo CD guidance describes these as project controls (Argo CD Projects). Automated prune and selfHeal reconcile Git/live drift, but do not prove substrate or customer health (Argo CD automated sync).
Data model and ownership
Cluster credentials and product memberships must remain different records, or a deployment token could become a customer identity. No generated-application database change is needed for delivery bootstrap.
Generated-application database: Not created in this slice — GitOps installation, destination, policy, and reconciliation evidence are durable hosted-runtime state.
| Record or entity | Store and owner | Primary key | Foreign key or opaque reference | Tenant key | Material constraint | Lifecycle and deletion | Use case IDs |
|---|---|---|---|---|---|---|---|
| GitOpsBootstrapRevision | Git and Kubernetes API, owned by Delivery Platform | commit SHA plus controller manifest digest | Opaque cluster API and approval refs | environment_id | Pinned supported revision, reviewed source, scoped bootstrap role | Install, upgrade through reviewed revision, uninstall only after Applications retire; retain audit | D25-UC-01, D25-UC-02 |
| LocalDestinationBinding | Environment Kubernetes API, owned by Delivery Platform | Application UID | Opaque EKS ARN and kube-context evidence ref | environment_id | Destination is exactly https://kubernetes.default.svc; no remote credential or cross-environment target | Bootstrap, reconcile, revoke controller identity, remove after app retirement | D25-UC-01, D25-UC-02 |
| ProjectPolicyEvidence | Evidence Store, owned by Security Operations | evidence_id | Opaque project revision, denied object, positive control, and audit refs | environment_id | Positive and negative paths share revision and observation window | Append, seal at review, retain with controller lifecycle, expire by policy | D25-UC-02 |
Installation, destination credentials, and policy evidence have different owners and revocation paths. Their opaque references correlate delivery without granting provider or generated-app authority.
Apply the repository contracts
An unpinned remote install can change between runs, so verify a reviewed Argo CD release artifact before applying repository configuration. This production snippet uses exact current paths and the production context.
: "${AWS_PROFILE:?set the approved AWS profile}" "${AWS_REGION:?set the intended AWS region}" "${EXPECTED_ACCOUNT_ID:?set the intended 12-digit AWS account}"
actual_account="$(aws --profile "$AWS_PROFILE" --region "$AWS_REGION" sts get-caller-identity --query Account --output text)"
[ "$actual_account" = "$EXPECTED_ACCOUNT_ID" ] || { echo "AWS account mismatch" >&2; exit 1; }
aws --profile "$AWS_PROFILE" --region "$AWS_REGION" eks update-kubeconfig --name zheta-forge-prod --alias zheta-forge-prod
kubectl --context zheta-forge-prod apply -f argocd/project.yaml
kubectl --context zheta-forge-prod apply -f argocd/applicationsets/forge-prod.yaml
kubectl --context zheta-forge-prod -n argocd get applicationsets,applications
kubectl --context zheta-forge-prod -n argocd get application forge-prod -o wide
| Effect field | What happens |
|---|---|
| Declared intent | Allow approved Forge paths and generate one Application against the environment-local Kubernetes API |
| Interpreter | Kubernetes API, AppProject admission, ApplicationSet controller, repo server, and application controller |
| Software effect | Project, ApplicationSet, and Application objects are stored and reconciled |
| Hardware effect | Controller pods consume cluster CPU, memory, storage, DNS, and network to Git and destination APIs |
| Evidence | Object generations, fixed local destination, kube context, Git revisions, sync/health, denied policy test, and audit IDs |
Key takeaways
GitOps begins with an explicit, bounded bootstrap owner.
- Argo CD cannot recreate an unavailable EKS API that hosts or receives its reconciliation.
- AppProject policy limits sources, destinations, and resources before sync.
SyncedandHealthyprove only the observed Git-to-cluster boundary.
Checklist
The handoff is complete when steady-state controllers no longer need bootstrap authority.
- [ ] Controller source and digest are pinned and reviewed
- [ ] Each environment has its own Argo instance, kube context, and fixed local destination
- [ ] Unapproved repository or namespace is denied
- [ ] Bootstrap session is revoked and audit retained