25

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 IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D25-UC-01Platform bootstrap operatorInstall one pinned GitOps controller inside each environment clusterEach Argo CD instance reconciles exactly one local ApplicationSet targeting https://kubernetes.default.svcMissing local API or wrong environment manifest fails visibly; no remote cluster credential exists
D25-UC-02GitOps security reviewerRestrict repositories, namespaces, resource kinds, and production sync authorityforge AppProject accepts the approved repository and zheta-forge namespace onlyUnapproved 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 IDUse case IDsUser storyObservable acceptance conditions
D25-US-01D25-UC-01As 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-localEvidence links approval, source/digest, controller UIDs, local API identity, fixed destination, health, time, and run
D25-US-02D25-UC-02As a GitOps security reviewer, I want project and sync policy enforced before applications appear, so that Git cannot deploy arbitrary resources or cross account boundariesPositive 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 IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D25-FLOW-01D25-UC-01HappyApproved platform change requests GitOps bootstrap1. 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-02D25-UC-02DeniedReviewer proposes an Application outside approved repository or namespace1. 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 IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D25-UC-01Pinned bootstrap manifest and environment-local ApplicationSetLocal EKS API, Argo CD controllers, in-cluster identity, ApplicationSet controllerGit for bootstrap/project/ApplicationSet intent; the local Kubernetes API for installed components, Applications, and statusAPI unavailable, component unready, wrong context, remote destination, or local Application missing
D25-UC-02argocd/project.yaml policy reviewAppProject admission, repo server, application controller, destination RBAC/auditAppProject and Application desired/status state plus Git revision and Evidence StoreDisallowed 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 entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
GitOpsBootstrapRevisionGit and Kubernetes API, owned by Delivery Platformcommit SHA plus controller manifest digestOpaque cluster API and approval refsenvironment_idPinned supported revision, reviewed source, scoped bootstrap roleInstall, upgrade through reviewed revision, uninstall only after Applications retire; retain auditD25-UC-01, D25-UC-02
LocalDestinationBindingEnvironment Kubernetes API, owned by Delivery PlatformApplication UIDOpaque EKS ARN and kube-context evidence refenvironment_idDestination is exactly https://kubernetes.default.svc; no remote credential or cross-environment targetBootstrap, reconcile, revoke controller identity, remove after app retirementD25-UC-01, D25-UC-02
ProjectPolicyEvidenceEvidence Store, owned by Security Operationsevidence_idOpaque project revision, denied object, positive control, and audit refsenvironment_idPositive and negative paths share revision and observation windowAppend, seal at review, retain with controller lifecycle, expire by policyD25-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 fieldWhat happens
Declared intentAllow approved Forge paths and generate one Application against the environment-local Kubernetes API
InterpreterKubernetes API, AppProject admission, ApplicationSet controller, repo server, and application controller
Software effectProject, ApplicationSet, and Application objects are stored and reconciled
Hardware effectController pods consume cluster CPU, memory, storage, DNS, and network to Git and destination APIs
EvidenceObject generations, fixed local destination, kube context, Git revisions, sync/health, denied policy test, and audit IDs

Three authorization planes and lifecycle rule

Argo CD belongs to the hosted-runtime boundary and receives no automatic provider organization or generated-app tenant role. The provider plane approves releases and collaborators; each generated application still owns end-user sessions and data authorization.

Decision rule: bootstrap only after the cluster exists; remove Applications and registrations before uninstalling Argo CD; remove Argo CD before destroying its cluster; retain audit independently.

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.
  • Synced and Healthy prove 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