Advertisement

From Terraform to GitOps: A Practical Migration Roadmap

CertVanta Team
December 5, 2025
15 min read

A step-by-step guide to migrating from traditional Terraform workflows to GitOps, including migration patterns, common pitfalls, and practical diagrams to guide your journey.

From Terraform to GitOps: A Practical Migration Roadmap

Why Migrate from Terraform to GitOps?

If you're running infrastructure with Terraform, you've likely hit familiar pain points: state file conflicts, drift detection challenges, manual apply workflows, and the constant question of "who deployed what, when?"

GitOps offers a fundamentally different approach: treating Git as the single source of truth and using automated controllers to continuously reconcile your actual infrastructure state with the declared state in your repository.

This guide provides a practical roadmap for migrating from Terraform-based workflows to GitOps, helping you understand when to migrate, how to do it incrementally, and what pitfalls to avoid.


Understanding the Architectural Shift

Traditional Terraform Workflow

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen

Flow: Developer → Plan → Review → Apply → State Updated

Characteristics:

  • Push-based: Developers/CI push changes to infrastructure
  • Manual gates: terraform plan/apply requires human intervention
  • State management: Requires careful state file handling
  • Drift detection: Manual or scheduled checks needed

GitOps Workflow

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen

Flow: Developer → PR → Merge → Controller Syncs → Cluster Updated

Characteristics:

  • Pull-based: Controllers pull desired state from Git
  • Automated reconciliation: Continuous sync without manual intervention
  • Built-in drift detection: Controllers automatically detect and correct drift
  • Declarative: Everything in Git is the source of truth

Choosing Your Migration Pattern: Decision Tree

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen


Migration Patterns: Three Approaches

Pattern 1: Complete Migration (Big Bang)

When to use: Small infrastructure, greenfield projects, or when starting fresh

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen

Timeline: 2-4 weeks Risk: High Rollback: Difficult

Pros:

  • Clean slate, no hybrid complexity
  • Faster to achieve full GitOps benefits

Cons:

  • High risk if issues arise
  • Requires significant upfront planning
  • Difficult to rollback

Pattern 2: Incremental Migration (Strangler Fig)

When to use: Large production systems, risk-averse organizations

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen

Timeline: 2-6 months Risk: Low Rollback: Easy per phase

Pros:

  • Lower risk, gradual learning curve
  • Each phase can be validated before proceeding
  • Easy to rollback individual phases

Cons:

  • Longer timeline
  • Temporary hybrid complexity
  • Must maintain both systems during transition

Pattern 3: Hybrid Model (Coexistence)

When to use: Complex environments with different requirements

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen

Timeline: Ongoing Risk: Medium Use case: Long-term coexistence

Pros:

  • Uses each tool for its strengths
  • Terraform for infrastructure, GitOps for applications
  • No pressure to fully migrate

Cons:

  • Requires managing two systems indefinitely
  • Integration complexity
  • Potential for confusion about ownership

Complete Migration Timeline

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen


Step-by-Step Migration Roadmap

Phase 1: Assessment & Planning (Week 1-2)

1.1 Inventory Your Terraform Resources

# Generate a list of all resources
terraform state list > terraform-inventory.txt

# Categorize resources by type
grep "aws_vpc" terraform-inventory.txt
grep "kubernetes_" terraform-inventory.txt
grep "helm_release" terraform-inventory.txt

1.2 Create Migration Categories

CategoryToolPriorityNotes
K8s ApplicationsGitOpsHighMove first (easiest win)
Helm ChartsGitOpsHighNatural fit for GitOps
ConfigMaps/SecretsGitOpsHighBetter secret management
VPCs/NetworksTerraformLowRarely change, keep in TF
DatabasesTerraformLowCritical infra, migrate last
IAM/RBACMixedMediumEvaluate case by case

1.3 Choose Your GitOps Tool

ArgoCD vs Flux Decision Matrix
┌────────────────────┬──────────────┬──────────────┐
│     Feature        │   ArgoCD     │     Flux     │
├────────────────────┼──────────────┼──────────────┤
│ UI Dashboard       │     ✅       │      ❌      │
│ Multi-tenancy      │     ✅       │   Partial    │
│ SSO Integration    │     ✅       │      ❌      │
│ Complexity         │   Medium     │     Low      │
│ Helm Support       │  Excellent   │  Excellent   │
│ Kustomize Support  │  Excellent   │  Excellent   │
│ CNCF Status        │  Graduated   │  Graduated   │
│ Best for           │ Large teams  │  Simplicity  │
└────────────────────┴──────────────┴──────────────┘

Phase 2: Environment Setup (Week 2-3)

2.1 Install GitOps Controller (ArgoCD Example)

# Create namespace
kubectl create namespace argocd

# Install ArgoCD
kubectl apply -n argocd -f \
  https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

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

2.2 Repository Structure

gitops-repo/
├── apps/
│   ├── dev/
│   │   ├── app1/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── app2/
│   ├── staging/
│   └── prod/
├── infrastructure/
│   ├── namespaces/
│   ├── ingress/
│   └── monitoring/
└── argocd-apps/
    ├── dev-apps.yaml
    ├── staging-apps.yaml
    └── prod-apps.yaml

Phase 3: Migrate Application Workloads (Week 3-6)

3.1 Export Kubernetes Resources from Terraform

# If using kubernetes provider
terraform state pull | jq '.resources[] |
  select(.type | startswith("kubernetes_"))'

3.2 Convert to GitOps Manifests

Before (Terraform):

resource "kubernetes_deployment" "app" {
  metadata {
    name      = "my-app"
    namespace = "production"
  }
  spec {
    replicas = 3
    selector {
      match_labels = {
        app = "my-app"
      }
    }
    template {
      metadata {
        labels = {
          app = "my-app"
        }
      }
      spec {
        container {
          name  = "app"
          image = "myapp:v1.0.0"
        }
      }
    }
  }
}

After (GitOps):

# apps/prod/my-app/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: myapp:v1.0.0

3.3 Create ArgoCD Application

# argocd-apps/prod-my-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/yourorg/gitops-repo
    targetRevision: main
    path: apps/prod/my-app
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Phase 4: State Management Transition (Week 6-8)

4.1 Remove Resources from Terraform State

# Remove Kubernetes resources from Terraform state
# (After confirming GitOps is managing them)
terraform state rm kubernetes_deployment.app
terraform state rm kubernetes_service.app

# Verify removal
terraform plan  # Should show no changes to removed resources

4.2 Migration Safety Checklist

Pre-Migration Validation
┌──────────────────────────────────────────┐
│ ☐ Resources exist in Git repository      │
│ ☐ ArgoCD Application created             │
│ ☐ Initial sync successful                │
│ ☐ Health checks passing                  │
│ ☐ Rollback plan documented               │
│ ☐ Team trained on new workflow           │
│ ☐ Monitoring/alerting configured         │
└──────────────────────────────────────────┘

Post-Migration Validation
┌──────────────────────────────────────────┐
│ ☐ Terraform state cleaned                │
│ ☐ GitOps sync working                    │
│ ☐ No drift detected                      │
│ ☐ Team can deploy via PR                 │
│ ☐ Documentation updated                  │
└──────────────────────────────────────────┘

Phase 5: Workflow Optimization (Week 8+)

5.1 Implement PR-Based Deployments

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen

5.2 Advanced GitOps Patterns

Progressive Delivery with Canary:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 5
  strategy:
    canary:
      steps:
      - setWeight: 20
      - pause: {duration: 5m}
      - setWeight: 50
      - pause: {duration: 5m}
      - setWeight: 100

Multi-Cluster Deployment:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app
spec:
  generators:
  - list:
      elements:
      - cluster: dev
        url: https://dev.k8s.example.com
      - cluster: prod
        url: https://prod.k8s.example.com
  template:
    metadata:
      name: 'my-app-{{cluster}}'
    spec:
      source:
        path: 'apps/{{cluster}}/my-app'

Common Migration Pitfalls & Solutions

Pitfall 1: Secret Management

Problem: Secrets in Git is a security risk

Solution: Sealed Secrets or External Secrets Operator

# Using Sealed Secrets
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: my-secret
spec:
  encryptedData:
    password: AgBZXc0dF7... # Encrypted, safe for Git

Solution: External Secrets with AWS Secrets Manager

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: my-app-secret
spec:
  secretStoreRef:
    name: aws-secrets-manager
  target:
    name: my-app-secret
  data:
  - secretKey: password
    remoteRef:
      key: prod/my-app/password

Pitfall 2: Drift Detection

Problem: Manual changes to cluster bypass GitOps

How Drift Detection Works:

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen

Solution: Enable Self-Heal & Notifications

spec:
  syncPolicy:
    automated:
      selfHeal: true  # Auto-correct drift
      prune: true     # Remove resources not in Git
  syncOptions:
    - CreateNamespace=true

Configure Drift Alerts:

# ArgoCD notifications ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
data:
  trigger.on-sync-status-unknown: |
    - when: app.status.sync.status == 'Unknown'
      send: [slack-drift-alert]

Pitfall 3: Large-Scale Terraform State

Problem: Too many resources to migrate at once

Solution: State Segmentation

# Before migration, split Terraform state
terraform state mv -state-out=k8s.tfstate \
  'kubernetes_deployment.app' \
  'kubernetes_service.app'

# Now k8s.tfstate can be migrated independently

Terraform + GitOps Coexistence Patterns

For teams maintaining both tools, here's a practical division:

Responsibility Matrix
┌────────────────────────┬────────────┬──────────┐
│      Resource Type     │  Terraform │  GitOps  │
├────────────────────────┼────────────┼──────────┤
│ VPC & Networking       │     ✅     │    ❌    │
│ RDS/CloudSQL Databases │     ✅     │    ❌    │
│ IAM Roles/Policies     │     ✅     │    ❌    │
│ K8s Cluster (EKS/GKE)  │     ✅     │    ❌    │
│ K8s Deployments        │     ❌     │    ✅    │
│ K8s Services/Ingress   │     ❌     │    ✅    │
│ ConfigMaps/Secrets     │     ❌     │    ✅    │
│ Helm Charts            │     ❌     │    ✅    │
│ Application Config     │     ❌     │    ✅    │
└────────────────────────┴────────────┴──────────┘

Rule of Thumb:
• Terraform → Infrastructure that changes rarely
• GitOps → Application layer that changes frequently

Architecture Comparison: Side-by-Side

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen


Cost-Benefit Analysis

Before Migration (Terraform Only)

Costs:

  • Manual apply processes: ~2 hours/week
  • State lock conflicts: ~1 hour/week
  • Drift investigation: ~3 hours/month
  • Failed deployments due to human error: ~5/year

Total Time Cost: ~150 hours/year


After Migration (GitOps)

Initial Investment:

  • Setup time: 40-80 hours
  • Training: 20 hours
  • Migration execution: 60-120 hours

Ongoing Benefits:

  • Automated deployments: Saves ~2 hours/week
  • No state conflicts: Saves ~1 hour/week
  • Auto drift correction: Saves ~3 hours/month
  • Reduced deployment failures: ~80% reduction

Total Time Saved: ~150 hours/year Break-even: 6-12 months ROI after Year 1: 25-50%


The Complete GitOps Ecosystem

Interactive Diagram

Click diagram or fullscreen button for better viewing • Press ESC to exit fullscreen


Key Takeaways

  1. Start Small: Migrate dev environments first, then staging, then production
  2. Hybrid is OK: You don't need to migrate everything—Terraform for infra, GitOps for apps works well
  3. Automate Everything: Enable auto-sync and self-heal to get full GitOps benefits
  4. Solve Secrets Early: Plan your secret management strategy before migration
  5. Monitor & Alert: Set up drift detection and sync failure notifications
  6. Document: Keep a migration playbook for your team

Next Steps

Ready to start your migration? Here's your week 1 checklist:

Week 1 Action Items
┌─────────────────────────────────────────────┐
│ ☐ Audit current Terraform resources        │
│ ☐ Choose GitOps tool (ArgoCD vs Flux)      │
│ ☐ Set up dev cluster for testing           │
│ ☐ Install GitOps controller                │
│ ☐ Create Git repository structure          │
│ ☐ Migrate one non-critical app             │
│ ☐ Validate deployment via PR               │
│ ☐ Document learnings                       │
└─────────────────────────────────────────────┘

The journey from Terraform to GitOps isn't a sprint—it's a marathon. But with the right approach, you'll build more reliable, auditable, and automated infrastructure deployments that scale with your team.


Have you migrated from Terraform to GitOps? What challenges did you face? Share your experiences and learnings with the cloud engineering community.

Advertisement

Related Articles

GitOps: Monorepo vs Polyrepo - A Practical Comparison
⚙️
October 12, 2025
12 min read
GitOpsMonorepo+7

A straightforward comparison of monorepo and polyrepo approaches for GitOps implementations. Understand the advantages, disadvantages, and when to use each strategy for your infrastructure and application deployments.

by Platform Engineering TeamRead Article
GitOps vs. ClickOps: Choosing the Right Deployment Workflow
⚙️
July 9, 2025
13 min read
GitOpsDevOps+6

Should you deploy using GitOps or ClickOps? Learn the trade-offs, best practices, and hybrid strategies to balance velocity, reliability, and auditability.

by CertVanta TeamRead Article
CI/CD at Scale: Designing Fast, Flaky-Resistant Pipelines
⚙️
July 29, 2025
12 min read
DevOpsCI/CD+7

Build CI/CD pipelines that scale. Learn how to design faster builds, reduce test flakiness, add security gates, and deploy confidently without slowing down engineering teams.

by CertVanta TeamRead Article