From Terraform to GitOps: A Practical Migration Roadmap
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 DiagramClick 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 DiagramClick 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 DiagramClick 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 DiagramClick 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 DiagramClick 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 DiagramClick 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 DiagramClick 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
| Category | Tool | Priority | Notes |
|---|---|---|---|
| K8s Applications | GitOps | High | Move first (easiest win) |
| Helm Charts | GitOps | High | Natural fit for GitOps |
| ConfigMaps/Secrets | GitOps | High | Better secret management |
| VPCs/Networks | Terraform | Low | Rarely change, keep in TF |
| Databases | Terraform | Low | Critical infra, migrate last |
| IAM/RBAC | Mixed | Medium | Evaluate 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 DiagramClick 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 DiagramClick 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 DiagramClick 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 DiagramClick diagram or fullscreen button for better viewing • Press ESC to exit fullscreen
Key Takeaways
- Start Small: Migrate dev environments first, then staging, then production
- Hybrid is OK: You don't need to migrate everything—Terraform for infra, GitOps for apps works well
- Automate Everything: Enable auto-sync and self-heal to get full GitOps benefits
- Solve Secrets Early: Plan your secret management strategy before migration
- Monitor & Alert: Set up drift detection and sync failure notifications
- 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.