The GitOps Bootstrap Trap
The chicken-and-egg problem of bootstrapping ArgoCD in a Kubernetes cluster.
Every GitOps adoption eventually runs into the same question: if ArgoCD manages everything, who manages ArgoCD?
At first, this looks like a deployment-order problem. Provision a Kubernetes cluster, install ArgoCD, point it towards Git and let the controller handle everything else. But the interesting problem appears when another tool still believes it owns the resources ArgoCD has started reconciling.
Terraform, Helm and ArgoCD all have their own models of desired state. They are individually useful, but they do not share a single ownership model. When their responsibilities overlap, a successful initial deployment can turn into an ongoing operational problem.
The difficult part is not necessarily getting ArgoCD installed. It is deciding which system has authority over a resource after installation, and what happens when that authority needs to change.
In my EKS platform project, I used Terraform to provision the AWS infrastructure and Helm to bootstrap ArgoCD. Once ArgoCD was running, I used the App-of-Apps pattern to reconcile platform components from Git. This established a clear separation between provisioning the underlying cluster and managing the Kubernetes workloads running inside it.
The bootstrap paradox
GitOps is built around a relatively straightforward idea: define the desired state of a system in Git, then use a controller to reconcile the actual state against it. ArgoCD provides that reconciliation mechanism for Kubernetes.
Once configured, ArgoCD can detect differences between live Kubernetes resources and the configuration stored in a repository. Depending on the Application's synchronisation policy, it can apply changes automatically or wait for an engineer to approve them.
But a newly provisioned EKS cluster does not have an ArgoCD controller waiting to read a repository. There is no Application controller, no reconciliation process and no mechanism for installing the first set of GitOps-managed resources.
Something outside ArgoCD must perform that initial installation. This could be a Helm command, Terraform's Helm provider or a dedicated bootstrap workflow executed through CI/CD.
None of these approaches is inherently wrong. The important decision is what happens after the controller becomes operational. Does the bootstrap mechanism retain ownership, or does responsibility move to ArgoCD?
Where Terraform fits into the picture
Terraform is a natural choice for provisioning foundational AWS infrastructure. Networking, IAM roles, security groups, EKS clusters and supporting services can all be defined declaratively and managed through Terraform state.
Once the cluster exists and is accessible, Terraform can also interact with Kubernetes through its providers. A common approach is to use the Helm provider to install ArgoCD.
resource "helm_release" "argocd" {
name = "argocd"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-cd"
namespace = "argocd"
create_namespace = true
version = var.argocd_chart_version
}This configuration establishes a Terraform-managed Helm release. Terraform records the relationship between the resource address in its configuration and the release in the cluster.
Future Terraform plans can then compare the configured release against the state Terraform knows about and propose changes when necessary.
Installing ArgoCD this way is not itself a problem. The difficulty begins when ArgoCD is subsequently configured to manage the same Helm release or overlapping Kubernetes resources.
Terraform may expect one chart version, set of values or collection of metadata. ArgoCD may expect another. Both systems now have instructions to enforce their own understanding of the desired state.
When reconciliation becomes a tug of war
Imagine Terraform installs a platform component with configuration A. Later, an ArgoCD Application is created to manage that component using configuration B stored in Git.
ArgoCD compares the live resource against its desired configuration and detects a difference. It synchronises the resource to match Git.
The next Terraform plan identifies that the infrastructure no longer matches Terraform's configuration. An engineer applies the plan, restoring configuration A.
ArgoCD then detects the same difference again.
Terraform applies configuration A
|
v
ArgoCD compares live state with configuration B
|
v
ArgoCD synchronises configuration B
|
v
Terraform detects a difference
|
v
Terraform reapplies configuration A
|
v
ArgoCD reports OutOfSync againNeither system is necessarily malfunctioning. Terraform is enforcing its configuration when an apply occurs. ArgoCD is reconciling against the desired state defined in Git.
The architectural mistake is that two systems have been given competing authority over the same resources.
Repeatedly running terraform apply or forcing an ArgoCD synchronisation may temporarily change the resource's state, but it does not resolve the underlying conflict.
The Helm release that already exists
There is a related failure mode that appears when a Helm release already exists in the cluster, but Terraform does not track it in state.
Suppose ArgoCD was installed manually using Helm. Later, a Terraform configuration is introduced containing a helm_release resource with the same release name and namespace.
Terraform does not automatically adopt every existing resource that happens to match its configuration. If it has no state relationship with the existing Helm release, it may attempt to create another release.
This can produce an error such as:
cannot re-use a name that is still in useThe error does not necessarily indicate that the Helm chart is broken or that Kubernetes cannot run the application. It may indicate that Helm already knows about a release with the requested name.
This is where the distinction between infrastructure existing and infrastructure being tracked becomes important.
A Terraform resource declaration describes what Terraform should manage. Terraform state establishes the relationship between that declaration and an existing infrastructure object.
Before deleting anything, the first step should be to investigate which releases exist, which resources Terraform tracks and whether ArgoCD is managing any of the same objects.
helm list -A
terraform state list
kubectl get applications.argoproj.io -ADeleting an existing release simply to make Terraform apply successfully can introduce downtime and may leave the original ownership problem unresolved.
Import before apply
If the architectural decision is that Terraform should manage an existing Helm release, importing it into Terraform state can establish the missing relationship.
The process begins by inspecting the existing release and defining a Terraform resource that represents it. The release can then be imported before attempting to apply configuration changes.
terraform import helm_release.argocd argocd/argocd
terraform planThe example uses the conventional namespace/release-name import format. The exact syntax should be verified against the Helm provider version being used.
Importing is not the end of the process. It is essential to inspect the subsequent Terraform plan.
Terraform may propose an upgrade, configuration change or replacement because the imported release does not match the declared resource configuration.
A successful import followed by an unexpected destructive plan is not a successful migration.
There is also an important limitation: import is appropriate when Terraform is intended to become the owner. It does not resolve a situation where Terraform and ArgoCD are both supposed to manage the same resource indefinitely.
If ownership is moving to ArgoCD instead, the migration needs a different approach. Terraform's management of the resource must be relinquished deliberately without accidentally destroying the underlying infrastructure.
Why ArgoCD keeps reporting OutOfSync
An ArgoCD Application reporting OutOfSync is a symptom rather than a diagnosis.
It means ArgoCD has identified a difference between the desired configuration and the live Kubernetes resources. That difference could come from several places.
It might be a genuine configuration change. It might be a default value added by Kubernetes, a mutation performed by an admission webhook, metadata introduced by another tool or an external controller modifying fields after deployment.
Helm ownership annotations are particularly relevant when investigating resources managed through Helm.
metadata:
annotations:
meta.helm.sh/release-name: argocd
meta.helm.sh/release-namespace: argocdThese annotations help Helm identify the release associated with particular resources. Differences involving ownership metadata can become relevant when tools attempt to adopt or manage existing objects.
Kubernetes managedFields can also help reveal which managers have written particular fields. However, they should not automatically be blamed for every ArgoCD synchronisation difference.
The correct investigation starts with the actual diff.
argocd app get <application-name>
argocd app diff <application-name>
kubectl get deployment <name> \
-n <namespace> -o yaml
helm status <release-name> \
-n <namespace>These commands help establish what ArgoCD expects, what Kubernetes currently contains and whether Helm is involved in managing the resource.
One tempting response is to add ignoreDifferences rules until ArgoCD reports that everything is synchronised.
Ignoring known controller-managed fields can be appropriate. But suppressing differences before understanding their cause can conceal an actual ownership conflict.
The objective should not be to make the dashboard green. It should be to establish a predictable source of truth.
Drawing a clear ownership boundary
A useful approach is to divide the platform into infrastructure ownership and application ownership.
Terraform manages foundational AWS resources: networking, IAM, EKS and other infrastructure components whose lifecycle is closely connected to the underlying cloud environment.
ArgoCD manages Kubernetes applications and platform components that have deliberately been placed under GitOps.
Terraform may still manage selected Kubernetes resources, including bootstrap components. The important requirement is that ownership is explicit rather than accidental.
Terraform
|
+-- VPC and networking
+-- IAM and security
+-- EKS cluster
+-- Node infrastructure
+-- Explicit bootstrap resources
|
v
ArgoCD
|
v
Git repository
|
+-- Applications
+-- Services
+-- Ingress resources
+-- Platform workloadsThis separation is not a universal rule that Terraform must never interact with Kubernetes. Real platforms often have components that span cloud infrastructure and Kubernetes resources.
The point is to avoid giving two independent reconciliation systems conflicting instructions for the same objects.
Ownership decisions should also be documented. Engineers need to know which tool should be used to modify a resource, how upgrades are performed and what should happen when drift is detected.
Can ArgoCD manage itself?
Once ArgoCD has been bootstrapped, it can manage its own configuration through GitOps.
This provides the familiar benefits of declarative configuration: version control, reviewable changes and a consistent delivery process.
However, self-management creates an operational dependency worth understanding.
Suppose a configuration change breaks ArgoCD itself. The controller responsible for applying the correction may no longer be available.
That does not make self-management inherently unsafe. It means the platform needs a recovery mechanism that does not depend entirely on the controller remaining operational.
An engineer should know how to restore a known-good configuration using an appropriate out-of-band mechanism, such as Helm or kubectl, while respecting the platform's ownership model.
The same principle applies to upgrades. A declarative upgrade process is useful, but the ability to recover from a failed controller upgrade is equally important.
The real lesson
The interesting part of GitOps is not simply that a controller can apply YAML from a repository.
It is the operational contract behind that reconciliation: which system owns each resource, what constitutes drift, how ownership changes and how the platform recovers when the controller itself fails.
Terraform, Helm and ArgoCD can work together effectively. But their responsibilities need to be designed rather than discovered through repeated deployment failures.