Terraform has already done the platform setup.

By this point, the environment root has declared the cluster and installed the controllers. argocd.tf owns the ArgoCD Helm release and its ingress route. image-updater.tf owns the updater installation and its ECR identity. There is no separate ArgoCD installation step here.

The handoff is the project’s k8s-argo-apps/application.yaml: tell ArgoCD which Git repository and path contain the desired application state.

Where each change belongs
Terraform platform codeApplication and ArgoCD YAML
VPC, EKS, IAM, Karpenter, ingress controllers, DNS/certificate automation, storage integrations, ArgoCD installation.ArgoCD source/destination/sync settings; application Deployments or StatefulSets, Services, routes, and image references.

Platform YAML embedded in Terraform remains Terraform-owned. Do not put a second copy of its NodePool, Certificate, or controller release in the application repository. This boundary keeps a future Terraform apply and a future ArgoCD sync from competing.

1. Put the workload YAML in the application repository.

The supplied Application watches apps/dev/argo-cd-apps. That is a repository path, not a special ArgoCD requirement. It can hold ordinary application manifests, or be adapted to an app-of-apps structure. For the first deployment, use direct workload manifests so the handoff is easy to follow:

your-apps-repo/
  argocd/
    application.yaml              # bootstrap definition, outside watched path
  apps/dev/argo-cd-apps/
    workload.yaml                 # Deployment/Service/routes for the pilot

Clone your company’s application configuration repository and run the following from its root:

mkdir -p argocd apps/dev/argo-cd-apps
curl -fSLo apps/dev/argo-cd-apps/workload.yaml \
  https://myownmode.com/assets/examples/platform-check.yaml

The optional echo workload gives you a simple first application. Change every namespace: platform-check to namespace: gitops-pilot, set both route hostnames to an unused name under your wildcard domain, and select an approved image. Its Service and Deployment names can stay unchanged.

You can instead use your own workload YAML. The repository’s k8s-argo-apps/stateful-app.yaml is a StatefulSet example with a volume claim template. Adapt its old image reference, Service, namespace, and storage behavior before choosing it as a stateful pilot. The EBS CSI integration comes from Terraform; application volume claims belong with the workload.

Review and merge the workload into the branch ArgoCD will track before bootstrapping it. Keep credentials out of Git and avoid sharing the namespace with the manually managed test from part 2.

2. Adapt the ArgoCD Application already supplied.

Download the repository’s Application example into argocd/application.yaml, or copy it directly from your infrastructure checkout:

curl -fSLo argocd/application.yaml \
  https://myownmode.com/assets/examples/argo-application.yaml

The main fields in the original file are:

metadata:
  name: apps-dev
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-github-owner/your-apps-repo.git
    targetRevision: HEAD
    path: apps/dev/argo-cd-apps
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false

This is an excerpt; keep the complete file, including its sync options, when editing. Replace the repository URL. Select an explicit tracked branch such as main if that fits your workflow. Adjust the path only if your repository layout differs. The destination server points to the cluster running ArgoCD.

The full example sets CreateNamespace=false, so create the workload namespace before the first sync. Set spec.destination.namespace to gitops-pilot for this example and ensure the manifests agree.

Decide on the sync behavior before applying it. The original enables automatic sync, pruning, and self-healing. Pruning removes managed resources no longer declared; self-healing corrects live drift. Its foreground deletion finalizer can remove managed resources when deleting the Application. If the team wants to inspect the first diff manually, remove the automated block before bootstrap and enable it later through a reviewed YAML change. See ArgoCD sync behavior.

The default project is the template’s starting point. For a company deployment, define an AppProject and RBAC appropriate to the team’s sources, destinations, and resources, then reference that project. The optional scoped pilot example shows an alternative with manual first sync. Choose one bootstrap definition; do not apply both for the same workload.

Configure private Git repository access through your approved ArgoCD credential process. Terraform’s AWS Pod Identity setup for ECR does not authenticate ArgoCD to Git.

3. Bootstrap once, then let ArgoCD reconcile.

Use the pilot context and a fresh namespace. Review and commit the adapted Application outside its watched directory. For the original Application name apps-dev:

kubectl config current-context
kubectl create namespace gitops-pilot
kubectl apply --dry-run=server -f argocd/application.yaml
kubectl apply -f argocd/application.yaml

kubectl -n argocd get application apps-dev
kubectl -n gitops-pilot get deployments,pods,services

This is the intentional bootstrap use of kubectl: applying the ArgoCD definition that connects Git to the existing platform. The application workload itself comes through ArgoCD. With the original automated policy, reconciliation can start as soon as the Application exists.

After authenticating a compatible ArgoCD CLI to the intended server, inspect the revision and status:

argocd app get apps-dev --refresh
argocd app diff apps-dev
# Only if you chose a manual first sync:
argocd app sync apps-dev
argocd app wait apps-dev --sync --health --timeout 300

curl --fail --show-error https://YOUR_PILOT_HOSTNAME

A diff can return a nonzero exit code when there are changes to inspect. For the echo workload, trusted HTTPS should return platform-ready. Verify the tracked Git revision as well as the endpoint: synchronization alone is not an application acceptance test.

If repository comparison fails, check credentials, branch, and path. If resources are rejected, inspect project permissions and admission events. For image pulls or traffic failures, use the platform checks from part 2.

4. Release applications by changing their YAML.

CI builds and tests the application, publishes an image to ECR, and records its digest. The delivery change is then a reviewed update to the image reference in the application manifest:

image: ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/ORG/APP@sha256:IMAGE_DIGEST

Use the repository URL produced by terraform -chdir=global/repos-ecr output ecr_repository_urls and the real digest. Promote the same artifact across environments through the corresponding application paths and ArgoCD definitions. Avoid rebuilding a different image for each environment.

Ordinary application releases do not need another Terraform apply. Terraform is the path for infrastructure changes; Git commits to application YAML are the path for workload releases. Review Application bootstrap changes through the platform team’s chosen ownership process too.

If GitHub Actions builds images, give that job the required ECR permissions. The infrastructure OIDC module contains much broader deployment permissions; an image build job does not need the full infrastructure role or direct Kubernetes access simply to release through GitOps.

5. Use the Terraform-installed Image Updater where it fits.

The updater’s Terraform code already declares the release, service account, ECR read permissions, and Pod Identity association. The values template renders the account/region-specific registry and authentication script:

modules/eks-karpenter/image-updater.tf
modules/eks-karpenter/values/image-updater.yaml.tpl

That prepares image discovery. Configure the application’s update strategy, supported Helm/Kustomize integration, and write-back method for the installed updater release before enabling automatic image changes. For the plain YAML example here, reviewed image-reference commits are a straightforward starting point.

The Image Updater write-back documentation distinguishes Git changes from ArgoCD overrides. Git write-back is not automatically a pull-request approval process. Choose one owner for each image field and keep the company’s promotion rules explicit.

6. Rehearse recovery, then expand adoption.

Try a harmless application change first: update the echo argument to -text=release-two, review and merge it, and verify the new response. Revert that release through a new reviewed Git change and confirm the original response returns.

git switch main
git pull --ff-only
git switch -c rollback-pilot
git revert RELEASE_COMMIT_SHA
git push -u origin rollback-pilot
# Review and merge the rollback; use a non-merge release commit for this command.
# Observe automatic sync, or sync manually if that is your policy.
argocd app wait apps-dev --sync --health --timeout 300

For real images, retain the previous digest in ECR. Restoring an image does not reverse database writes or schema migrations; include those in the application’s recovery plan.

The company’s adoption path can now reuse the same foundation: Terraform environment configuration for the platform, ArgoCD YAML for the repository connection, and application YAML for each workload. Move one service at a time with its owner, endpoint checks, release evidence, and rollback decision agreed.

Keep ownership consistent during retirement as well. Review the Application’s pruning/finalizer behavior before deleting it, and remove application/controller-created resources before destroying a disposable Terraform environment. Shared registries and state remain separate roots.

SERIES COMPLETE

Terraform for the platform. YAML and GitOps for the applications.

← Back to the modules · All three parts →