Why GitFlow Fails at Infrastructure
Source: Dev.to
TL;DR
Applying GitFlow (long‑lived feature or environment branches) to Terraform often leads to state drift and fragile pipelines. Unlike application code, Infrastructure as Code (IaC) has a third dimension—State—which cannot be merged via git merge.
Winning strategy: Use Trunk‑Based Development. Treat main as the single source of truth. Promote the same code commit across environments (Dev → Stage → Prod) by injecting environment‑specific variables (.tfvars), rather than merging code between environment branches.
The Core Problem: The “Third Dimension”
In standard application development you manage two primary dimensions:
- The Code: Your logic in Git.
- The Build: The artifact running on a server.
If your code works in Git, it generally works in the build.
In Terraform there is a third, dominant dimension: The State (terraform.tfstate).
The state maps your Git configuration to the real‑world APIs of AWS/Azure/GCP. Even if you store state remotely (S3, Terraform Cloud) to avoid JSON merge conflicts, you cannot solve logical divergence with Git alone.
When you use GitFlow with Terraform, you decouple the Code from the State.
The GitFlow Trap: “State Stomping” and “Phantoms”
A common anti‑pattern is mapping Git branches to environments:
feature/new-db→ Sandboxdev→ Developmentmain→ Production
The Scenario
Imagine two DevOps engineers, Alice and Bob, start working on separate features.
- Alice branches off
developtofeature/add-redis. She adds a Redis cluster and deploys to the Sandbox environment to test. - Bob branches off
developtofeature/resize-vpc. He changes the VPC CIDR and deploys to the same Sandbox environment (or a different one).
Because Terraform tracks resources by their address in the state file, Alice and Bob are now in a race condition.
The Consequence
When Alice or Bob finally merges back to develop, they are only merging text files. Git cannot merge the live infrastructure state. You end up with a “clean” Git history that contradicts the messy reality of your cloud provider, creating a divergence that will likely cause a failure during the next deployment.
- Shared Environment Risk (State Stomping): They step on each other’s locks or overwrite resources because their state files are out of sync with their branches.
- Separate Environment Risk (Phantom Infrastructure): If a feature branch creates resources in a dynamic environment and the branch is deleted after merging without running
terraform destroy, those resources remain running in the cloud. They become “orphans” – billing you monthly but existing in no codebase.
The Solution: Trunk‑Based Development
In Trunk‑Based Development (TBD), every commit to main is potentially deployable. You do not maintain long‑lived branches.
The Workflow
Instead of moving code between branches to promote it (e.g., merging dev into prod), you promote the artifact. In Terraform, the “artifact” is your module code combined with a specific commit SHA.
You use the same code for all environments, changing only the input variables.
The Pipeline Architecture
Practical Implementation
Structure your repository to separate logical infrastructure (the code) from environment configuration (the variables).
Directory Structure
/my-infra
/modules
/vpc
/k8s
main.tf # The generic entry point
variables.tf # Variable definitions only
config/
dev.tfvars # Dev‑specific values (e.g., instance_type = "t3.micro")
prod.tfvars # Prod‑specific values (e.g., instance_type = "m5.large")
The CI/CD Command Logic
Dev stage
# Initialize with the backend config (usually partial config)
terraform init -backend-config="bucket=my-tf-state-dev"
# Plan using the specific variables for this environment
terraform plan -var-file="config/dev.tfvars" -out=tfplan
# Apply exactly what was planned
terraform apply tfplan
Prod promotion
# Same code, different state backend, different vars
terraform init -backend-config="bucket=my-tf-state-prod"
terraform plan -var-file="config/prod.tfvars" -out=tfplan
terraform apply tfplan
Why This Is Safer
- Immutability: The exact Terraform code tested in Dev is what runs in Prod. No “bad merge” between Dev and Prod branches.
- State Isolation: Dev and Prod have completely separate state files (defined by the backend config). They never touch.
- Fast Feedback: If a commit breaks Dev, the pipeline stops and never reaches Prod.
The Exception: Shared Modules
There is one specific area in Terraform where Semantic Versioning is… (content continues)