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.mdTerraform 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
- One state file per environment per component
- Never manually change resources managed by Terraform
- Use
terraform importfor existing resources - Tag all resources:
managed_by = "terraform",environment,team - Require PR approval before apply to production
