The Strategic Boundary: Integrating Terraform and Argo CD for Unified Infrastructure and Application Delivery

The modern software delivery pipeline demands a precise division of labor between the tools that provision cloud infrastructure and the tools that manage containerized application workloads. Terraform and Argo CD represent the two pillars of this architectural approach, yet their integration remains a subject of intense debate among platform engineers. Terraform serves as the primary infrastructure-as-code (IaC) engine, capable of provisioning virtual machines, networks, databases, and entire Kubernetes clusters across diverse cloud providers such as AWS and Google Cloud. In contrast, Argo CD functions as a GitOps continuous delivery controller that continuously syncs Kubernetes manifests from a Git repository to a cluster. While these tools are distinct in their primary objectives—Terraform creates and manages infrastructure across providers, whereas Argo CD assumes infrastructure already exists and focuses on deploying applications within a Kubernetes context—teams frequently struggle to define the operational boundary between them. This article provides a comprehensive technical analysis of how to integrate these two systems, exploring the philosophical differences, practical implementation patterns, and the specific mechanisms required to maintain a reliable, GitOps-compliant delivery pipeline.

Understanding the Fundamental Difference

To effectively integrate Terraform and Argo CD, one must first understand their core operational paradigms. Terraform is the most popular solution for implementing Infrastructure as Code. Its provider registry contains a vast collection of integrations for all major cloud providers, as well as wealth of connectivity for databases, networking components, and continuous integration platforms. Terraform is designed to manage resources that often have no native Kubernetes representation. Typical examples of resources managed by Terraform include virtual machines, storage primitives, networking constraints and routes, and DNS records. Because these resources exist outside the boundary of the Kubernetes cluster, Terraform is the only logical tool to manage them.

Argo CD, conversely, is a GitOps tool specifically built for Kubernetes. It watches Git repositories and automatically syncs the desired state to the cluster. In its vanilla version, Argo CD only handles resources that are described in Kubernetes manifests. This means that if a team wishes to manage a resource that has no Kubernetes representation, Terraform must handle it. A critical technical distinction is that Argo CD does not natively handle Terraform state. It is a GitOps controller built for Kubernetes manifests, so managing Terraform state files, locking, or backends falls entirely outside its scope. To reconcile Terraform through Argo CD, teams typically use an operator like Crossplane or the Argo CD Terraform controller, which stores state in a remote backend such as S3 or Terraform Cloud.

The following table outlines the primary differences between the two tools based on their core functions and scope:

Feature Terraform Argo CD
Primary Function Provisions cloud infrastructure (VMs, Networks, DBs) Continuous delivery of Kubernetes applications
Source of Truth Terraform State File (remote or local) Git Repository (Kubernetes Manifests)
Scope Cloud Resources and Kubernetes Cluster Provisioning Resources inside the Kubernetes Cluster
State Management Native State Management with Locking No Native State Management for Non-K8s Resources
Execution Model Imperative/Declarative Execution Plan Continuous Sync/Reconciliation
Secret Handling Native Terraform State (Encrypted/Remote) Kubernetes Secrets/External Secret Operators

The Natural Evolution: Defining the Operational Boundary

For most organizations, the most natural and stable evolution is to define a strict boundary where each tool is completely oblivious to the other. In this model, Terraform handles the platform layer, which includes installing Argo CD, configuring single sign-on (SSO), and connecting repositories. Argo CD then handles the application layer, continuously deploying workloads from Git.

This approach establishes a clear starting point:

  • Everything that is deployed or managed on Kubernetes is controlled by Argo CD.
  • Everything that is outside the cluster is controlled by Terraform.

Under this regime, Terraform runs first. It creates the infrastructure required for the application. It can also install Argo CD into the cluster, but it should manage no other Kubernetes resource. Once Argo CD is installed, it takes over for all existing resources that are managed by the cluster, such as Deployments and Services. This isolation ensures that there is no direct communication between the tools. Terraform does not know anything about Argo CD, and Argo CD does not know anything about Terraform. All communication happens via Git. This clean separation avoids the complexity of tool interaction and reduces the surface area for configuration drift.

Integrating Outputs: The Commit-to-Git Pattern

While the isolated approach is robust, many teams need to pass variables from Terraform to the applications deployed by Argo CD. Common questions include how to pass variables from Terraform to Helm charts and how to retrieve secrets generated by Terraform for use in Kubernetes applications. One effective method to solve this is the "Commit-to-Git" pattern.

Argo CD will continuously sync changes it finds in Git, without concern for how those changes were committed. This means that teams can use Terraform to commit files to Git, which will then be picked up by Argo CD. This is a very clean approach as each tool remains fully isolated from the other. The handoff works through a specific sequence of operations:

  • Terraform runs first, creating the infrastructure and potentially installing Argo CD.
  • Output variables are put in Kubernetes manifests or Argo CD application sets.
  • Terraform supports templating files on its own as a built-in capability.
  • The final result is committed to Git by Terraform like any other manifest.
  • Argo CD notices the Git changes and syncs the files without visibility into how they were created.

This pattern is particularly useful for non-secret configuration data. For example, if a Terraform module creates a specific service account or generates a unique identifier for a workload, that information can be templated into a Kubernetes manifest. The following code block demonstrates a simplified scenario where Terraform outputs are utilized to create a manifest that Argo CD will subsequently sync:

```hcl

Example: Terraform generates a manifest for Argo CD

resource "templatefile" "app_manifest" {
template = file("${path.module}/manifests/deployment.yaml.tpl")

vars = {
serviceaccountname = awsiamrole.eks_node.name
environment = var.environment
}
}

resource "localfile" "deployment" {
content = templatefile.app
manifest.rendered
filename = "${path.module}/git-repo/manifests/deployment.yaml"
}
```

In this example, the workload ID information comes from Terraform but must be used in Argo CD. By committing the templated file to the Git repository, Argo CD detects the change and applies the manifest to the cluster. This method avoids the need for complex runtime communication between the tools.

Handling Secrets and the Limitation of State

A significant challenge in this integration is the management of secrets. The previous section noted that the commit-to-Git pattern covers many use cases but not secrets. Committing sensitive data to Git, even if encrypted, is generally considered an anti-pattern in security-conscious environments. Therefore, teams must look for alternative methods to pass secrets from Terraform to Argo CD deployments.

Since Argo CD does not natively handle Terraform state, it cannot directly read the encrypted state file where Terraform stores sensitive data. To bridge this gap, teams often utilize external secret management solutions or Kubernetes-native secret stores. If a team chooses to reconcile Terraform through Argo CD, they typically use an operator like Crossplane or the Argo CD Terraform controller. These controllers store state in a remote backend such as S3 or Terraform Cloud. This allows the state to be accessible within the Kubernetes environment, enabling controllers to retrieve values and inject them into the cluster.

However, for the majority of use cases, it is more secure to use dedicated secret management tools such as HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault. Terraform can read secrets from these providers during the provisioning phase and write them into Kubernetes Secrets objects. Argo CD can then reference these Secrets in its application manifests. This approach maintains the security boundary while still allowing the application layer to access the necessary credentials.

Advanced Patterns: The GitOps Bridge and Its Trade-offs

Beyond the basic commit-to-Git pattern, some teams adopt more complex integration strategies. One such approach is the GitOps Bridge Pattern. This pattern has gained traction, including backing from major cloud providers, and involves Terraform exporting the values that Argo CD Application needs and injecting them into a cluster secret as labels or annotations. Argo CD then consumes that metadata using an ApplicationSet with a cluster generator.

While this pattern is powerful for managing large numbers of clusters, it introduces real-world trade-offs. A simplified version of this pattern relies on the following logic:

  • Terraform exports application data.
  • Data is injected into cluster secrets as labels or annotations.
  • Argo CD ApplicationSet uses a cluster generator to read these secrets.

The following table compares the characteristics of the Isolated Pattern and the GitOps Bridge Pattern:

Feature Isolated Pattern (Commit to Git) GitOps Bridge Pattern
Complexity Low to Moderate High
Source of Truth Git Repository Cluster Secrets/Annotations
Generator Type Any (App or AppSet) Cluster Generator (AppSet only)
GitOps Purity High (Git is Source of Truth) Low (Metadata from Cluster)
Scalability Good for single/multi-cluster Excellent for multi-cluster fleets
Secrets Handling Requires external secret management Can leverage cluster-level secrets

The primary limitation of the GitOps Bridge Pattern is that it requires ApplicationSets. This pattern only works with ApplicationSets using the cluster generator. This is fine if the organization is managing dozens of clusters, but it is not ideal if the team wants to use a basic Application or a different generator. Furthermore, it breaks GitOps as the source of truth because the metadata used by Argo CD comes from cluster secrets rather than Git. This means that if a cluster secret is manually altered or corrupted, the Git repository no longer reflects the desired state, leading to potential drift and operational confusion.

Installing Argo CD with Terraform

Terraform handles the platform layer, which includes the installation of Argo CD itself. The official Argo CD Helm chart is the recommended installation method. By using Terraform to install Argo CD, teams ensure that the deployment of the GitOps controller is version-controlled and repeatable. The following code block demonstrates a production-ready Terraform configuration for installing Argo CD with high availability and ingress configuration:

```hcl

Create the ArgoCD namespace

resource "kubernetes_namespace" "argocd" {
metadata {
name = "argocd"
labels = {
"app.kubernetes.io/managed-by" = "terraform"
}
}
}

Deploy ArgoCD

resource "helmrelease" "argocd" {
name = "argocd"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-cd"
namespace = kubernetes
namespace.argocd.metadata[0].name
version = "9.5.15"

values = [
yamlencode({
# High availability configuration
controller = {
replicas = 2
resources = {
requests = {
cpu = "250m"
memory = "512Mi"
}
limits = {
memory = "1Gi"
}
}
}
server = {
replicas = 2
# Ingress configuration
ingress = {
enabled = true
ingressClassName = "nginx"
hosts = ["argocd.${var.domain}"]
tls = [{
secretName = "argocd-tls"
hosts = ["argocd.${var.domain}"]
}]
annotations = {
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
"nginx.ingress.kubernetes.io/backend-protocol" = "HTTP"
}
}
# Extra arguments for the server
extraArgs = [
"--insecure" # TLS is handled by the ingress controller
]
}
repoServer = {
replicas = 2
resources = {
requests = {
cpu = "100m"
memory = "256Mi"
}
limits = {
memory = "512Mi"
}
}
}
})
]
}
```

This configuration highlights several best practices. First, it sets up high availability by defining two replicas for the controller, server, and repoServer components. Second, it configures the ingress controller to handle TLS termination, allowing the internal Argo CD server to run in an insecure mode since traffic is already secured at the ingress layer. Third, it uses resource requests and limits to prevent resource exhaustion, ensuring that the Argo CD controller does not compete excessively with application workloads for CPU and memory.

Conclusion

The integration of Terraform and Argo CD is not a matter of choosing one tool over the other, but rather defining a clear, enforceable boundary between infrastructure provisioning and application delivery. Terraform remains the superior tool for managing cloud resources that exist outside the Kubernetes cluster, while Argo CD excels at managing the state of resources within the cluster through GitOps principles. The most robust strategy for most teams is the isolated pattern, where Terraform provisions the cluster and installs Argo CD, and Argo CD manages all Kubernetes workloads. Data exchange between the two is achieved through the Git repository, where Terraform commits templated manifests that Argo CD syncs. For complex multi-cluster environments, the GitOps Bridge Pattern offers scalability but at the cost of GitOps purity. Teams must carefully weigh the benefits of automation against the risks of introducing hidden state dependencies. By adhering to these architectural guidelines, organizations can leverage the strengths of both Terraform and Argo CD to achieve a seamless, reliable, and secure software delivery pipeline.

Sources

  1. SpaceLift Blog
  2. Octopus Blog
  3. OneUptime Blog
  4. Akuity Blog

Related Posts