Skip to content
Back to Blog
DevOps

GitOps for Infrastructure: Terraform & Git Workflow

Adopt GitOps practices for infrastructure management: Terraform modules, Git branching, pull request reviews, and automated apply pipelines.

Jul 2025
17 min read

GitOps for Infrastructure: Terraform + Git Workflow

GitOps treats infrastructure configuration as code in Git. Every change goes through pull request, review, and automated apply.

Project Structure

TEXT
infra/
├── modules/
│   ├── network/          # Reusable VPC/network module
│   ├── vm/               # VM provisioning module
│   └── firewall/         # Firewall rules module
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   └── production/
├── .github/workflows/
│   └── terraform.yml     # CI/CD pipeline
└── README.md

Terraform Module Example

HCL
# modules/network/main.tf
variable "name" { type = string }
variable "cidr" { type = string }
variable "subnets" { type = list(object({ name = string, cidr = string })) }

resource "proxmox_network" "main" {
  name    = var.name
  cidr    = var.cidr
  comment = "Managed by Terraform"
}

resource "proxmox_network_subnet" "subnets" {
  for_each = { for s in var.subnets : s.name => s }
  name     = each.value.name
  network  = proxmox_network.main.id
  cidr     = each.value.cidr
}

Remote State with Locking

HCL
# backend.tf
terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "production/network/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}

CI/CD Pipeline (GitHub Actions)

YAML
name: Terraform
on:
  pull_request:
    paths: ['infra/**']
  push:
    branches: [main]
    paths: ['infra/**']

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3

      - name: Terraform Init
        run: terraform init
        working-directory: infra/environments/production

      - name: Terraform Plan
        run: terraform plan -out=tfplan
        if: github.event_name == 'pull_request'

      - name: Terraform Apply
        run: terraform apply tfplan
        if: github.ref == 'refs/heads/main'

Drift Detection

BASH
# Check for drift from desired state
terraform plan -detailed-exitcode
# Exit 0 = no changes, 1 = error, 2 = changes detected

# Run in cron for continuous compliance
0 6 * * * cd /infra/production && terraform plan -detailed-exitcode || alert "Drift detected!"

Best Practices

  1. One state file per environment per component
  2. Never manually change resources managed by Terraform
  3. Use terraform import for existing resources
  4. Tag all resources: managed_by = "terraform", environment, team
  5. Require PR approval before apply to production