September 15, 2025Terrateam

End-to-End GitOps: Terraform for Infrastructure, ArgoCD for Application Delivery

What you'll learn: This guide explains how to build a complete GitOps workflow using Terraform for infrastructure and ArgoCD for application deployment. You'll provision an EKS cluster with Terraform, deploy ArgoCD using Helm, and configure automatic application syncing from Git. You'll understand how infrastructure changes trigger Terraform pipelines and how application updates flow through ArgoCD.

Introduction to End-to-End GitOps

You've automated your infrastructure with Terraform, and your EKS cluster spins up perfectly. Then someone asks, "Now, how do we deploy the application?" And there you are, copying kubectl commands from a wiki page, manually applying manifests, hoping nothing breaks.

A disconnect like this between infrastructure and application deployment kills the GitOps dream. Your infrastructure lives in Git with proper version control and automated pipelines. Your applications are deployed through manual runbooks, Jenkins jobs from a few years ago, or a bash script someone wrote before they left the company, with comments in the code as documentation.

The problems compound quickly. Infrastructure and application versions drift apart. Nobody knows which application version runs in production because deployments happen outside Git. Rollbacks become monumental tasks while searching through Slack messages and email threads. That "quick fix" applied directly with kubectl? It's gone the next time someone runs Terraform.

True GitOps means everything flows from Git, both infrastructure and applications. Terraform and ArgoCD solve different parts of this puzzle. Terraform handles what Kubernetes runs on: VPCs, subnets, databases, load balancers, and the cluster itself. ArgoCD handles what runs inside Kubernetes: deployments, services, ingresses, and config maps.

Terraform handles what Kubernetes runs on:ArgoCD handles what runs inside Kubernetes:
VPCs Subnets Databases Load balancers The cluster itselfDeployments Services Ingresses Config maps

When you change a database configuration, Terraform updates RDS. When you bump an image tag, ArgoCD rolls out new pods. Each tool handles what it does best. There are no manual steps, no kubectl commands, and no drift between Git and reality.

These pieces can be connected into a single workflow where code changes automatically trigger the right tool for the right job; this ArgoCD and Terraform integration is what GitOps automation is about.

⚡ ⚡ ⚡

The Benefits of Using Terraform and ArgoCD Together

Typically, infrastructure teams own Terraform configurations for VPCs, RDS databases, and EKS clusters and don't worry about application deployments. Developers, on the other hand, own Kubernetes manifests and Helm charts and don't need AWS console access. Boundaries should be clear and ownership transparent, leading to fewer conflicts.

Terraform excels at provisioning cloud resources with complex dependencies. It handles IAM roles, security groups, and VPC peering with precision. But treating Kubernetes resources as infrastructure creates problems. Every pod restart triggers state drift. ConfigMap updates need full Terraform runs.

ArgoCD thrives where Terraform struggles. It continuously syncs Kubernetes resources, provides instant rollbacks, and shows real-time application state. But ArgoCD can't create your RDS database or configure VPC endpoints.

Terraform and ArgoCD are partners solving different problems. Terraform manages the cloud resources your cluster needs, while ArgoCD manages what runs inside that cluster. Together, they improve a few things:

  • Deployment speed: Most infrastructure changes complete in less than 10 minutes through Terraform, and application deployments finish in seconds through ArgoCD. Previously, combined deployments took 20+ minutes.
  • Rollback capability: ArgoCD rolls back applications in seconds. Terraform reverts infrastructure through Git. No more emergency console access.
  • Drift detection: Terraform catches infrastructure drift during plans. ArgoCD highlights application drift immediately. Nothing goes unnoticed.
  • Audit trail: Every change traces back to a Git commit. Compliance teams love the complete history.

The result is true GitOps across your entire stack, not just the parts that are convenient.

⚡ ⚡ ⚡

How to Use Terraform to Provision Infrastructure

As discussed, before ArgoCD can deploy applications, you need a Kubernetes cluster and supporting infrastructure. This section assumes you understand basic Terraform syntax, so we'll focus on the configurations that matter for ArgoCD integration. Our guide, Deploying an AWS EKS Cluster with Terraform and GitHub Actions, should give you the background you need.

Start with the EKS cluster foundation. Your cluster needs specific add-ons for ArgoCD to function properly:

resource "aws_eks_cluster" "main" {
  name     = var.cluster_name
  role_arn = aws_iam_role.cluster.arn
  version  = "1.29"

  vpc_config {
    subnet_ids              = aws_subnet.private[*].id
    endpoint_private_access = true
    endpoint_public_access  = true
  }
}

resource "aws_eks_addon" "ebs_csi" {
  cluster_name = aws_eks_cluster.main.name
  addon_name   = "aws-ebs-csi-driver"
  # Critical for ArgoCD persistent volumes
}

The EBS CSI driver is non-negotiable as ArgoCD needs persistent storage for its Redis cache and repository credentials. Without it, ArgoCD loses state during pod restarts.

Next, configure OIDC for pod identity. ArgoCD pods need AWS permissions for secrets management and ECR image pulls:

resource "aws_iam_openid_connect_provider" "cluster" {
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.cluster.certificates[0].sha1_fingerprint]
  url             = aws_eks_cluster.main.identity[0].oidc[0].issuer
}

Add your RDS database if applications need it:

resource "aws_db_instance" "app_database" {
  identifier     = "${var.cluster_name}-db"
  engine         = "postgres"
  engine_version = "15.4"
  instance_class = "db.t3.micro"

  # Security group allows cluster access
  vpc_security_group_ids = [aws_security_group.database.id]
}

You also need to export the values ArgoCD needs. These Terraform outputs are the inputs for your application configurations:

output "cluster_endpoint" {
  value = aws_eks_cluster.main.endpoint
}

output "database_endpoint" {
  value = aws_db_instance.app_database.endpoint
}

output "oidc_provider_arn" {
  value = aws_iam_openid_connect_provider.cluster.arn
}

Finally, run Terraform to create everything:

terraform init
terraform plan -out=tfplan
terraform apply tfplan

When your run finishes successfully, your infrastructure should be ready. The cluster runs, the database waits for connections, and OIDC enables pod-level AWS access. The foundation is solid.

But the cluster is empty. No applications, no ArgoCD, nothing but system pods. In the next section, we'll see how Terraform deploys ArgoCD itself to create the bridge between infrastructure and applications.

⚡ ⚡ ⚡

Deploying ArgoCD to the Cluster

With your EKS cluster running, deploy ArgoCD using Terraform's Helm provider. Using Helm this way keeps everything in code, so we don't have to run manual helm commands or kubectl apply steps.

Configure the Helm provider to use your new cluster:

provider "helm" {
  kubernetes {
    host                   = aws_eks_cluster.main.endpoint
    cluster_ca_certificate = base64decode(aws_eks_cluster.main.certificate_authority[0].data)
    token                  = data.aws_eks_cluster_auth.main.token
  }
}

data "aws_eks_cluster_auth" "main" {
  name = aws_eks_cluster.main.name
}

Deploy ArgoCD as a Helm release:

resource "helm_release" "argocd" {
  name             = "argocd"
  repository       = "https://argoproj.github.io/argo-helm"
  chart            = "argo-cd"
  namespace        = "argocd"
  create_namespace = true
  version          = "5.51.4"

  set {
    name  = "server.service.type"
    value = "LoadBalancer"
  }

  set {
    name  = "configs.params.server\\.insecure"
    value = "true"  # Use ingress with TLS in production
  }
}

The LoadBalancer service type gives you immediate access to ArgoCD's UI. In production, use an ingress with proper TLS certificates instead.

Retrieve the admin password and server URL:

resource "null_resource" "get_argocd_password" {
  depends_on = [helm_release.argocd]

  provisioner "local-exec" {
    command = <<-EOT
      aws eks update-kubeconfig --name ${var.cluster_name}
      kubectl -n argocd get secret argocd-initial-admin-secret \
        -o jsonpath="{.data.password}" | base64 -d > argocd-password.txt
    EOT
  }
}

output "argocd_server_url" {
  value = "kubectl get svc argocd-server -n argocd -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'"
  description = "Run this command to get ArgoCD URL"
}

Configure your first ArgoCD application to watch your Git repository. If you're following along, make sure you set the correct repo URL that contains your manifests:

# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: sample-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/app-manifests
    targetRevision: main
    path: environments/production
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Apply the manifest you just defined:

kubectl apply -f argocd-app.yaml

ArgoCD now watches your Git repository. When you push Kubernetes manifests to environments/production, ArgoCD automatically deploys them. No manual intervention needed.

When the deployment is done, verify everything works:

# Access ArgoCD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

# Check application status
argocd app list
argocd app get sample-app

Your GitOps pipeline is taking shape. Terraform manages the infrastructure layer, ArgoCD handles applications. The next section shows how code changes flow through this system.

⚡ ⚡ ⚡

How Code Changes Trigger the Right Pipelines

Your GitOps setup routes changes through the right tool automatically.

  1. Infrastructure changes trigger Terraform.
  2. Application changes trigger ArgoCD.

When you modify Terraform files, GitHub Actions runs your infrastructure pipeline:

name: Infrastructure Pipeline
on:
  push:
    paths: ['terraform/**/*.tf']

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Terraform Apply
        run: |
          cd terraform
          terraform init
          terraform apply -auto-approve

Changing your RDS instance size? Terraform updates it. Adding a new subnet? Terraform creates it. The cluster and databases update without touching applications.

ArgoCD takes over when you push changes to your application manifests:

# In your app repo: manifests/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: api
        image: myapp:v2.0.1  # Changed from v2.0.0

ArgoCD detects the drift within minutes (or immediately via webhook). It syncs the new image, performs a rolling update, and maintains zero downtime. No Terraform run needed.

Sometimes infrastructure changes require application updates, for example, if you add a new database to which applications must connect. Here's how you might handle it:

# GitHub Actions workflow
- name: Update App Config After Infrastructure Change
  if: contains(github.event.head_commit.message, '[update-app]')
  run: |
    # Update ConfigMap with new database endpoint
    export DB_ENDPOINT=$(terraform output -raw database_endpoint)

    # Update application manifest
    sed -i "s|DB_HOST:.*|DB_HOST: ${DB_ENDPOINT}|" manifests/configmap.yaml

    # Commit to trigger ArgoCD
    git add manifests/configmap.yaml
    git commit -m "Update database endpoint"
    git push

Another example: let's say your team needs a new microservice with its own database, this is the workflow you would typically implement:

  1. Developer creates terraform/rds-orders-service.tf
  2. GitHub Actions runs Terraform, creating the database
  3. Terraform outputs the connection string
  4. Developer updates manifests/orders-service/deployment.yaml with the database secret reference
  5. ArgoCD detects the new manifest and deploys the service
  6. Service connects to its database immediately

Two repositories, two tools, one smooth workflow.

Each tool handles its own task without stepping on the other's toes. You can choose to organize your code however you like. For some guidance, check out our Terraform Code Organization guide.

⚡ ⚡ ⚡

Conclusion

You've built a complete GitOps pipeline with the ArgoCD and Terraform integration. Terraform provisions your infrastructure, and ArgoCD deploys your applications automatically from Git. Every change flows through Git with proper review and audit trails. Infrastructure updates trigger Terraform pipelines. Application changes sync through ArgoCD. Rollbacks take seconds, not hours. Your environments stay consistent because they're all deployed from the same code.

This setup works great, but as you scale, you'll need dependency management between Terraform workspaces, policy enforcement, and drift detection across environments.

Terrateam eliminates that complexity. It provides enterprise-grade Terraform automation with built-in GitOps workflows, automatic dependency resolution, and policy-as-code enforcement. Your infrastructure deployments get the same sophistication ArgoCD brings to applications.

Check out our guide for more pipeline best practices, and sign up for Terrateam to focus on building pipelines instead of maintaining them.