Free O'Reilly E-Book: Learn GitOps & Kubernetes best practices with Argo CD: Up and Running. Download Now →

Free O'Reilly E-Book: Learn GitOps & Kubernetes best practices with Argo CD: Up and Running. Download Now →

Free O'Reilly E-Book: Learn GitOps & Kubernetes best practices with Argo CD: Up and Running. Download Now →

How to Integrate Terraform with Argo CD (Without Breaking GitOps)

Blake Pettersson

How to Integrate Argo CD with Terrafrom
How to Integrate Argo CD with Terrafrom

Why Terraform and Argo CD Together

Terraform provisions cloud infrastructure: Kubernetes clusters, databases, and identity or access controls. Argo CD deploys and manages the workloads that run on that infrastructure, following GitOps principles. Teams that adopt both tools eventually hit the same question: how does the output of Terraform's provisioning step become an input Argo CD can deploy from, without breaking Git's role as the source of truth? Three patterns have emerged to bridge Terraform and Argo CD:

  1. The GitOps Bridge pattern — Terraform writes values into Kubernetes cluster Secrets as labels or annotations; an ApplicationSet with a cluster generator reads them.

  2. A Git-based workflow for Helm — Terraform commits generated Helm values directly to Git; Argo CD reads them through a multi-source Application.

  3. A Git-based workflow for Kustomize — the same Git-based approach, applied to generated Kustomize overlays instead of Helm values.


Pattern

Config source Argo CD reads

Review step

Works with

GitOps Bridge

Cluster Secrets + Git

None built in

ApplicationSets (Helm)

Git-based (Helm)

Git only

Pull request

Multi-source Applications

Git-based (Kustomize)

Git only

Pull request

Multi-source Applications

The Bridge pattern has real production traction — AWS maintains its own reference implementation through EKS Blueprints for Terraform, including examples for ingress, secrets management, and multi-cluster hub-and-spoke topologies. Still, the pattern splits configuration between Git and live cluster state. The Git-based approach keeps everything in Git and adds a pull-request review step, at the cost of managing feature branches and reconciling them with Terraform state.

What This Guide Covers

This guide walks through where the GitOps Bridge pattern falls short, then shows the Git-based workflow for both Helm and Kustomize step by step.

TL;DR

  • Teams using both Terraform and Argo CD need a way to pass Terraform's provisioned infrastructure values into Argo CD deployments without breaking Git as the source of truth.

  • The GitOps Bridge pattern stores Terraform values in cluster Secrets, read by an ApplicationSet — workable, but splits config between Git and live clusters.

  • A Git-based workflow keeps Terraform's output in Git instead, for both Helm values and Kustomize overlays.

  • Argo CD deploys only after a reviewer merges a pull request, adding a review step the Bridge pattern doesn't have.

  • Teams choosing the Git-based approach need to plan for feature branch cleanup and reconciling changes with Terraform state.

What Is the GitOps Bridge Pattern?

The GitOps Bridge pattern is a common design strategy for integrating Terraform with Argo CD. The approach stores Terraform-generated values in the Argo CD cluster Secret as labels or annotations. An ApplicationSet cluster generator reads the metadata and passes the configuration to an Argo CD Application. 

The trade-off with the bridge pattern is that some deployment configuration lives in cluster metadata, reducing Git’s role as a complete record of the desired state.

The pattern has gained traction, including support from AWS, and works well for some teams. Terraform might provision an EKS cluster, create the IAM role ExternalDNS needs, and add the AWS account ID and IAM role name to the cluster Secret. Argo CD then reads those values and passes them to the ExternalDNS Helm chart.

A simplified but complete ApplicationSet might look like this:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: external-dns
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions:
    - missingkey=error
  generators:
    - clusters: {}
  template:
    metadata:
      name: "{{ .name }}-external-dns"
    spec:
      project: default
      destination:
        namespace: external-dns
        server: "{{ .server }}"
      source:
        repoURL: https://kubernetes-sigs.github.io/external-dns/
        chart: external-dns
        targetRevision: 1.21.1
        helm:
          releaseName: external-dns
          values: |
            serviceAccount:
              annotations:
                eks.amazonaws.com/role-arn: 'arn:aws:iam::{{ index .metadata.labels "aws-account-id" }}:role/{{ index .metadata.annotations "external-dns-iam-role-name" }}'
            txtOwnerId: "{{ .name }}"
      syncPolicy:
        syncOptions

The ApplicationSet extracts the account and role metadata from the cluster generator, then builds the required service account annotation. Pinning the chart to version 1.21.1 also prevents an upstream release from unexpectedly changing the deployment. 

What Are the Limitations of the GitOps Bridge Pattern? 

The GitOps Bridge pattern can introduce operational limitations by splitting deployment configuration between Git repositories and cluster Secrets. The implementation shown depends on the ApplicationSet cluster generator, complicates troubleshooting by placing some values in live cluster metadata, and requires additional steps for teams using Kustomize. 

Teams using Terraform and Argo CD may encounter four main limitations with the GitOps Bridge pattern: 

1. Creates an ApplicationSet Dependency

The implementation shown specifically requires an ApplicationSet with the cluster generator. Teams using standard Applications or other generator types need a different way to pass values into Argo CD.

2. Splits Deployment Inputs Across Git and Cluster Secrets 

Argo CD deploys manifests from Git, but some configuration values come from cluster Secrets. Troubleshooting requires engineers to check both locations to understand the final deployment state.

3. Makes Inline Helm Configuration Difficult to Maintain 

Inline Helm configurations can become large. Terraform can pass generated values directly into a Helm chart. However, the configuration becomes difficult to review and maintain as the number of parameters grows.

4. Adds Friction for Kustomize Users 

The specific implementation shown relies on Helm value substitution. Teams using Kustomize must add a step to generate or patch files before Argo CD can build the overlay.

How Do You Integrate Terraform With Argo CD Through Git? 

Terraform integrates with Argo CD through Git by generating Helm values or Kustomize overlays, committing them to a Git repository, and opening a pull request. Argo CD then reads the merged configuration from Git and deploys it. 

The Terraform-to-Git workflow looks like this:

  1. Terraform generates the configuration.

  2. Terraform commits the file to Git.

  3. A pull request provides a review step.

  4. Argo CD reads the merged configuration and deploys it.

Terraform’s GitHub provider can create and update files directly in Git. It can also open pull requests. Git remains the deployment source Argo CD reads, while Terraform still owns and updates the generated file. Team members shouldn’t edit that file independently because Terraform may overwrite those changes during a later run.

To follow the walkthrough below, you'll need:

  • Terraform installed

  • A GitHub repository where Terraform can commit files

  • A fine-grained GitHub personal access token or GitHub App with Contents: read and write and Pull requests: read and write permissions on the repository

  • The GitHub Terraform Provider configured

  • Optionally, an existing multi-source Argo CD Application

The following steps use Helm, but the same approach works with umbrella charts or Kustomize.

Step 1: Define Helm Values in Terraform 

Start by declaring your values in Terraform using a local block. These values will later be written to the Helm values file.

locals {
  helm_values = {
    server = {
      retention = "7d"

The server.retention value is supported by the Prometheus community Helm chart and changes how long Prometheus keeps collected metrics. In this example, the rendered deployment retains data for seven days rather than the chart’s 15-day default.

Step 2: Commit Helm Values to Git With Terraform

The GitHub provider’s github_repository_file resource lets you create or update files in a GitHub repository directly from Terraform, as long as the content is a string. The yamlencode() function converts the Terraform map into a YAML file that Helm can read. 

Here’s how to create the Helm values file with github_repository_file: 

resource "github_repository_file" "helm_values_example" {
  repository          = "tf-test"
  branch              = "main"
  file                = "main/values.yaml"
  content             = yamlencode(local.helm_values)
  commit_message      = "Managed by Terraform"
  commit_author       = "Terraform User"
  commit_email        = "terraform@example.com"

Run terraform apply.  This creates a commit in the tf-test GitHub repository with the values generated from Terraform. The overwrite_on_create option replaces an existing file if one is already present, so use it carefully. When working within a GitHub organization, define the organization in the Terraform provider configuration rather than prefixing the repository name.

Writing directly to main keeps the example simple, but many production repositories protect the default branch and require changes to go through a pull request instead. The next step shows that workflow.

Step 3: Reference Terraform-Generated Values in Argo CD

Now that the values file is in Git, you can reference it in an Argo CD multi-source Application. Here’s how to configure the Application:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus
  namespace: argocd
spec:
  project: default
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  sources:
    - repoURL: https://prometheus-community.github.io/helm-charts
      chart: prometheus
      targetRevision: 29.17.0
      helm:
        valueFiles:
          - $values/main/values.yaml
    - repoURL: https://github.com/blakepettersson/tf-test.git
      targetRevision: main
      ref

The first source provides the Prometheus Helm chart. The second assigns the name "values" to the Git repository containing the Terraform-generated file, allowing "$values/main/values.yaml" to reference the same "main/values.yaml" path created in Step 2. 

Step 4: Create a Feature Branch for Safer Changes

So far, Terraform is committing changes directly to main. In production, you’ll usually want a more controlled workflow.

Terraform can create a feature branch when the Helm values change, allowing you to open a pull request for review before merging anything into main.

If you already applied Step 2, do two things first. Change at least one value, for example, set retention to "10d" because GitHub will only open a pull request when the branch actually differs from main. Then remove the existing file from Terraform state:

terraform state rm github_repository_file.helm_values_example

Changing branch on github_repository_file forces replacement, and the destroy half of that replacement deletes the file from main, which breaks the Application from Step 3 until the pull request merges. Removing it from state leaves the main copy in place until the merge overwrites it.

The github_branch resource below creates a branch from main and names it using the first seven characters of a hash of the Helm values:

resource "github_branch" "pr" {
  repository    = "tf-test"
  branch        = "terraform-branch-${substr(sha256(jsonencode(local.helm_values)), 0, 7)}"
  source_branch = "main"

Because the branch name is based on the values content, each content change produces a new desired branch name.

Update the github_repository_file resource to commit to the feature branch: 

resource "github_repository_file" "helm_values_example" {
  repository          = "tf-test"
  branch              = github_branch.pr.branch
  file                = "main/values.yaml"
  content             = yamlencode(local.helm_values)
  commit_message      = "Managed by Terraform"
  commit_author       = "Terraform User"
  commit_email        = "terraform@example.com"

Step 5: Open a Pull Request With Terraform

The GitHub provider includes a github_repository_pull_request resource that opens a pull request when you run terraform apply after creating the feature branch. This resource connects the newly created feature branch to the main branch, creating a review step before Argo CD deploys the merged updates. 

Here’s the configuration to open the pull request:

resource "github_repository_pull_request" "helm_example_pr" {
  base_repository = github_repository_file.helm_values_example.repository
  base_ref         = "main"
  head_ref         = github_repository_file.helm_values_example.branch
  title            = "Update Prometheus Helm values"
  body             = "Updates the Terraform-generated Prometheus values file."

When you run terraform apply, Terraform will:

  1. Generate the Helm values

  2. Create a feature branch

  3. Commit the values to that branch

  4. Open a pull request

Terraform opens the pull request but doesn’t merge it. A reviewer or separate automation must approve and merge the change. Protected branches may also require approvals, status checks, or other repository rules before GitHub allows the merge.

There are two behaviors to plan for. Because the branch name is derived from the values, changing them again before a merge replaces the branch, and deleting the old branch automatically closes its open pull request. After a merge, if the repository auto-deletes branches, the next apply recreates the branch and file from the new main. That's harmless, but it will show up in the plan output, and a new pull request won't open until the values change again.

Why Should I Use Git to Connect Terraform and Argo CD?

Using Git to connect Terraform and Argo CD provides a reviewable deployment workflow with pull-request approval, support for both Helm and Kustomize, and a complete deployment history in Git. 

Feature

GitOps Bridge Pattern

Git-Based Workflow

Deployment source

Argo CD reads config from Git and cluster Secrets.

Argo CD deploys manifests and values from Git only.

Where generated values live

Terraform writes values into cluster Secrets as labels or annotations.

Terraform generates a values file and commits it to Git before deployment.

Review process

No built-in review step; values apply directly to the cluster.

A reviewer approves changes through a pull request before merging.

Helm support

Works with ApplicationSets using the cluster generator.

Works with multi-source Applications referencing a values file stored in Git.

Kustomize support

Requires additional steps to generate or patch files before Argo CD builds the overlay.

Terraform can generate Kustomize overlays or patches that Argo CD deploys from Git.

Operational trade-offs

Simpler for teams already using ApplicationSets and cluster generators at scale; splits config between Git and live cluster state.

Requires managing feature branches and pull requests, but keeps a single, auditable deployment history entirely in Git.

Git-based workflows are also becoming more common across the cloud native ecosystem.  

In the CNCF GitOps microsurvey, 71% of respondents ranked faster software delivery as the top reason for adopting GitOps, while 69% named the shift from manual processes to automation, which reduces the risk of misconfiguration, as its number-one security benefit.

We’ve seen those benefits in practice. We’ve used this pattern in production to generate Helm values and other deployment configuration with Terraform, commit the generated files through pull requests, and let Argo CD deploy only after the changes were reviewed. That removed the need to manage generated configuration in cluster Secrets while giving reviewers a clear Git history of every deployment change. 

How Do You Integrate Terraform and Argo CD With Kustomize? 

Generating Kustomize overlays with Terraform integrates Terraform and Argo CD through a Git-based workflow. Terraform commits the generated overlay files to Git, and Argo CD deploys the approved configuration from the repository. Here’s how:

Step 1: Define a Kustomize Overlay in Terraform

Just like with Helm values, define the overlay as a Terraform local object:

locals {
  environment = "dev"
  kustomize = {
    apiVersion = "kustomize.config.k8s.io/v1beta1"
    kind       = "Kustomization"
    resources = ["../../base"]
    namePrefix = "${local.environment}-"
    configMapGenerator = [
      {
        name = "app-config"
        literals = [
          "ENVIRONMENT=${local.environment}",
          "LOG_LEVEL=info"

Step 2: Create a Branch for the Kustomize Overlay

Create a feature branch using the same pattern as the Helm example:

resource "github_branch" "kustomize_pr" {
  repository    = "tf-test"
  branch        = "terraform-branch-${substr(sha256(jsonencode(local.kustomize)), 0, 7)}"
  source_branch = "main"
}

Step 3: Commit the Kustomize Overlay to Git

Encode the overlay as YAML and commit it to your Git repository:

resource "github_repository_file" "kustomize_example" {
  repository          = "tf-test"
  branch = github_branch.kustomize_pr.branch
  file                = "kustomize/overlays/dev/kustomization.yaml"
  content             = yamlencode(local.kustomize)
  commit_message      = "Managed by Terraform"
  commit_author       = "Terraform User"
  commit_email        = "terraform@example.com"

Terraform generates the kustomization.yaml file and commits it to the repository, and Argo CD deploys it after the change is reviewed. 

Step 4: Open a Pull Request for the Kustomize Changes

Open a pull request for the generated overlay:

resource "github_repository_pull_request" "kustomize_example_pr" {
  base_repository = github_repository_file.kustomize_example.repository
  base_ref        = "main"
  head_ref        = github_repository_file.kustomize_example.branch
  title           = "Update Kustomize overlay"
  body            = "Updates the Terraform-generated Kustomize overlay."

Step 5: Reference the Kustomize Overlay in Argo CD

After the pull request is merged, reference the overlay in your Argo CD Application:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: example
  namespace: argocd
spec:
  project: default
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  source:
    repoURL: https://github.com/blakepettersson/tf-test.git
    targetRevision: main
    path

This example uses a generated Kustomize overlay, but the same workflow also works for generated patches and other Kustomize resources. Terraform generates the files, Git provides the review process, and Argo CD deploys the approved configuration.

What Does a Modern Terraform and Argo CD Workflow Look Like?

A modern Terraform and Argo CD workflow uses Terraform to generate configuration, Git to review and store it, and Argo CD to deploy the approved version. Each tool keeps a clear role in the process. 

A Git-based Terraform and Argo CD workflow works well when pull-request review and support for both Helm and Kustomize are priorities. Anyone adopting this approach should also plan how to clean up branches and reconcile those changes with Terraform state. The existing GitOps Bridge pattern may still suit environments that rely on ApplicationSets and cluster-specific metadata. 

These examples are a starting point, so review repository permissions, branch protections, and state-management requirements before using them in production.

Feel free to experiment with the examples or fork the Terraform and GitHub proof-of-concept repository. If you have questions, reach out on the Akuity Community Discord. You’ll find me there as Blake Pettersson. 

Where Can I Learn More About Argo CD? 

If you enjoyed this blog, be sure to check out other great resources on Argo CD:

Ready to simplify delivery with Akuity?

Deploy, promote, and operate applications reliably, powered by OSS you trust and Intelligence you control.

Ready to simplify delivery with Akuity?

Deploy, promote, and operate applications reliably, powered by OSS you trust and Intelligence you control.

Ready to simplify delivery with Akuity?

Deploy, promote, and operate applications reliably, powered by OSS you trust and Intelligence you control.

Sign Up for Akuity Updates

Practical guidance on MTTR reduction, GitOps at scale, and safe automation, with product updates from the Argo CD and Kargo team.

@2026 Akuity Inc. All rights reserved.

Akuity Inc. 440 N. Wolfe Road, Sunnyvale, CA 94085-3869 US +1-510-771-7837

SOC2 Type 2 Compliant

Sign Up for Akuity Updates

Practical guidance on MTTR reduction, GitOps at scale, and safe automation, with product updates from the Argo CD and Kargo team.

@2026 Akuity Inc. All rights reserved.

Akuity Inc. 440 N. Wolfe Road, Sunnyvale, CA 94085-3869 US +1-510-771-7837

SOC2 Type 2 Compliant

Sign Up for Akuity Updates

Practical guidance on MTTR reduction, GitOps at scale, and safe automation, with product updates from the Argo CD and Kargo team.

@2026 Akuity Inc. All rights reserved.

Akuity Inc. 440 N. Wolfe Road, Sunnyvale, CA 94085-3869 US +1-510-771-7837

SOC2 Type 2 Compliant