Version Control for Infrastructure
Version control transforms infrastructure management from ad-hoc changes to a structured, auditable process. Applying Git workflows to infrastructure code enables collaboration, change tracking, and safe deployments.
Why Version Control Matters
Version control provides critical capabilities for infrastructure management that manual processes cannot match.
Change Tracking
Every infrastructure modification creates a permanent record:
# View infrastructure change history
git log --oneline infrastructure/production/
# Example output:
# a3f8b21 Increase RDS instance size for performance
# 7d2c489 Add CloudFront distribution for static assets
# e1b4532 Enable automated backups for production database
# 9c7e6d1 Add monitoring alerts for API latency
# See what changed in specific commit
git show a3f8b21
# Output shows the actual diff:
- instance_class = "db.t3.medium"
+ instance_class = "db.r5.large"
This history answers critical questions:
- Who made the change?
- When did it happen?
- Why was it necessary?
- What exactly changed?
Collaboration
Multiple engineers work on infrastructure simultaneously without conflicts:
# Engineer A works on networking
git checkout -b add-vpc-endpoints
# Make changes to VPC configuration
git commit -m "Add VPC endpoints for S3 and DynamoDB"
git push origin add-vpc-endpoints
# Engineer B works on database independently
git checkout -b upgrade-database
# Make changes to database configuration
git commit -m "Upgrade PostgreSQL to version 14"
git push origin upgrade-database
# Both changes reviewed and merged independently
Branches isolate work-in-progress, preventing incomplete changes from affecting others.
Rollback Capability
Revert infrastructure to any previous state:
# Problem detected after deployment
git log --oneline -5
# a3f8b21 (HEAD -> main) Increase RDS instance size
# 7d2c489 Add CloudFront distribution
# e1b4532 Enable automated backups
# Rollback to previous version
git revert a3f8b21
git push origin main
# Or rollback to specific point in time
git checkout 7d2c489 -- infrastructure/database/
git commit -m "Rollback database configuration to pre-upgrade state"
git push origin main
Rollback provides safety net for experimentation and quick recovery from issues.
graph TD
A[Working State] --> B[Change Applied]
B --> C{Problem Detected?}
C -->|No| D[Continue Forward]
C -->|Yes| E[Git Revert]
E --> F[Previous Working State]
style E fill:#ffe6e6
style F fill:#d4f1d4
Audit Trail
Compliance requirements demand change documentation:
# Generate audit report for date range
git log --since="2024-01-01" --until="2024-03-31" \
--pretty=format:"%h - %an, %ar : %s" \
infrastructure/production/
# Output provides compliance evidence:
# a3f8b21 - John Doe, 2 weeks ago : Increase RDS instance size
# 7d2c489 - Jane Smith, 1 month ago : Add CloudFront distribution
# e1b4532 - Bob Johnson, 2 months ago : Enable automated backups
# Export detailed audit log
git log --since="2024-01-01" --format=fuller --stat \
infrastructure/production/ > audit-q1-2024.txt
Repository Structure
Organizing infrastructure code effectively supports maintainability and clarity.
Monorepo Approach
Single repository contains all infrastructure:
infrastructure/
├── environments/
│ ├── production/
│ │ ├── networking/
│ │ │ └── main.tf
│ │ ├── compute/
│ │ │ └── main.tf
│ │ ├── database/
│ │ │ └── main.tf
│ │ └── monitoring/
│ │ └── main.tf
│ ├── staging/
│ │ ├── networking/
│ │ ├── compute/
│ │ ├── database/
│ │ └── monitoring/
│ └── development/
│ ├── networking/
│ ├── compute/
│ └── database/
├── modules/
│ ├── vpc/
│ ├── eks-cluster/
│ ├── rds-database/
│ └── monitoring-stack/
├── policies/
│ ├── security-policies/
│ └── compliance-checks/
└── scripts/
├── deploy.sh
└── validate.sh
Monorepo benefits:
- Atomic Changes: Modify multiple components in single commit
- Shared Modules: Reuse code across environments
- Simplified Dependencies: All code accessible in one place
- Unified Tooling: Same CI/CD pipeline for all infrastructure
Monorepo challenges:
- Large Repository: Clone time increases with history
- Broad Access: Everyone has access to all environments
- CI/CD Complexity: Must selectively trigger based on changes
Multi-Repo Approach
Separate repositories for different concerns:
# Repository: infrastructure-networking
networking/
├── production/
├── staging/
└── modules/
# Repository: infrastructure-compute
compute/
├── production/
├── staging/
└── modules/
# Repository: infrastructure-database
database/
├── production/
├── staging/
└── modules/
Multi-repo benefits:
- Access Control: Restrict sensitive infrastructure access
- Independent Lifecycle: Deploy components independently
- Smaller Repositories: Faster clone and operations
- Team Ownership: Clear ownership boundaries
Multi-repo challenges:
- Module Sharing: Must publish shared modules separately
- Cross-Repo Changes: Coordinating related changes harder
- Multiple Pipelines: Separate CI/CD for each repository
Environment-Based Structure
Organize primarily by environment:
infrastructure-production/
├── networking/
├── compute/
├── database/
└── monitoring/
infrastructure-staging/
├── networking/
├── compute/
└── database/
infrastructure-development/
├── compute/
└── database/
This structure provides strong environment isolation but increases duplication.
Branching Strategies
Different branching models suit different team sizes and release cadences.
Trunk-Based Development
Work primarily on main branch with short-lived feature branches:
# Create short-lived feature branch
git checkout -b add-monitoring-alerts
# Make focused changes
git commit -m "Add CloudWatch alarms for API latency"
# Merge quickly (same day)
git checkout main
git merge add-monitoring-alerts
git push origin main
Trunk-based development works well for:
- Small teams with high trust
- Frequent deployments (multiple per day)
- Strong automated testing
- Infrastructure with good rollback mechanisms
gitGraph
commit
commit
branch feature-1
checkout feature-1
commit
commit
checkout main
merge feature-1
commit
branch feature-2
checkout feature-2
commit
checkout main
merge feature-2
commit
GitFlow
Structured branching with dedicated release branches:
# Development work on develop branch
git checkout develop
git checkout -b feature/add-cdn
# Implement feature
git commit -m "Add CloudFront CDN configuration"
git checkout develop
git merge feature/add-cdn
# Create release branch
git checkout -b release/v1.2.0 develop
# Final testing and fixes
git commit -m "Update production replica counts"
# Merge to main for production
git checkout main
git merge release/v1.2.0
git tag v1.2.0
# Merge back to develop
git checkout develop
git merge release/v1.2.0
GitFlow suits:
- Larger teams requiring coordination
- Scheduled release cycles
- Multiple environments with gates
- Complex approval processes
Environment Branches
Branches represent environments:
# Production branch (protected)
git checkout production
# Only promoted changes allowed
# Staging branch
git checkout staging
# Changes deployed to staging first
# Development branch
git checkout development
# Active development happens here
# Promotion flow
git checkout staging
git merge development
git push origin staging
# After validation
git checkout production
git merge staging
git push origin production
Environment branches provide:
- Clear representation of each environment
- Explicit promotion path
- Easy identification of environment state
Drawbacks:
- Merge conflicts when promoting
- Complex history graph
- Difficult to share fixes across environments
Code Review Practices
Infrastructure changes require careful review to prevent outages.
Pull Request Template
Standardize review information:
## Infrastructure Change Request
### Description
Brief description of infrastructure changes
### Motivation
Why is this change necessary?
### Changes Made
- List specific resources modified
- Note any resource deletions
- Highlight security implications
### Testing
- [ ] Ran terraform plan locally
- [ ] Validated with tflint
- [ ] Tested in development environment
- [ ] Reviewed security impact
### Rollback Plan
How to revert if problems occur
### Estimated Downtime
Expected impact on running services
### Deployment Schedule
Preferred deployment time window
Templates ensure reviewers have necessary context.
Review Checklist
Systematically evaluate infrastructure changes:
### Security Review
- [ ] No hardcoded secrets in code
- [ ] Appropriate IAM permissions (least privilege)
- [ ] Network security groups properly scoped
- [ ] Encryption enabled for data at rest
- [ ] TLS configured for data in transit
### Cost Review
- [ ] Estimated monthly cost impact documented
- [ ] Resource sizing appropriate for load
- [ ] Auto-scaling configured where applicable
- [ ] Unused resources removed
### Reliability Review
- [ ] High availability configured
- [ ] Backup strategy defined
- [ ] Monitoring and alerting configured
- [ ] Disaster recovery plan updated
### Compliance Review
- [ ] Meets regulatory requirements
- [ ] Audit logging enabled
- [ ] Data residency requirements met
- [ ] Change documented for compliance
Automated Checks
Run validation automatically on pull requests:
# .github/workflows/pr-validation.yaml
name: Infrastructure PR Validation
on:
pull_request:
paths:
- 'infrastructure/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init -backend=false
working-directory: infrastructure/production
- name: Terraform Validate
run: terraform validate
working-directory: infrastructure/production
- name: Run tflint
uses: terraform-linters/setup-tflint@v3
run: tflint --recursive
- name: Security Scan
uses: aquasecurity/tfsec-action@v1.0.0
- name: Cost Estimation
uses: infracost/actions/setup@v2
run: |
infracost breakdown --path=infrastructure/production \
--format=comment --out-file=/tmp/infracost.txt
- name: Post Comment
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const comment = fs.readFileSync('/tmp/infracost.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
Automation catches common issues before human review.
Change Management Workflow
Structure the path from development to production.
Development Cycle
sequenceDiagram
participant Dev as Developer
participant Local as Local Environment
participant Git as Git Repository
participant CI as CI Pipeline
participant Review as Code Review
participant Stage as Staging
participant Prod as Production
Dev->>Local: Write infrastructure code
Dev->>Local: terraform plan
Local->>Dev: Show planned changes
Dev->>Git: Create feature branch
Dev->>Git: Push changes
Git->>CI: Trigger validation
CI->>CI: Format, validate, lint, scan
CI->>Git: Report results
Dev->>Review: Create pull request
Review->>Review: Team review
Review->>Git: Approve and merge
Git->>Stage: Deploy to staging
Stage->>Stage: Validate functionality
Stage->>Prod: Promote to production
Deployment Windows
Schedule infrastructure changes carefully:
# deployment-schedule.yaml
environments:
production:
allowed_windows:
- day: Tuesday
start: "14:00"
end: "16:00"
timezone: "America/New_York"
- day: Thursday
start: "14:00"
end: "16:00"
timezone: "America/New_York"
blackout_periods:
- start: "2024-11-25"
end: "2024-11-29"
reason: "Holiday freeze"
- start: "2024-12-20"
end: "2025-01-02"
reason: "End of year freeze"
Deployment windows reduce risk by:
- Ensuring team availability for issues
- Avoiding high-traffic periods
- Preventing holiday disruptions
- Allowing time for validation
Secrets Management
Handle sensitive values without committing them to Git.
Environment Variables
Reference secrets from environment:
# variables.tf
variable "database_password" {
description = "Database master password"
type = string
sensitive = true
}
# Reference in resource
resource "aws_db_instance" "database" {
password = var.database_password
}
Provide via environment variable:
export TF_VAR_database_password="SecurePassword123!"
terraform apply
Secret Management Tools
Integrate with dedicated secret stores:
# Fetch from HashiCorp Vault
data "vault_generic_secret" "database" {
path = "secret/production/database"
}
resource "aws_db_instance" "database" {
password = data.vault_generic_secret.database.data["password"]
}
# Fetch from AWS Secrets Manager
data "aws_secretsmanager_secret_version" "database" {
secret_id = "production/database/master-password"
}
resource "aws_db_instance" "database" {
password = jsondecode(data.aws_secretsmanager_secret_version.database.secret_string)["password"]
}
SOPS for Encrypted Files
Encrypt sensitive files before committing:
# Install SOPS
wget https://github.com/mozilla/sops/releases/download/v3.7.3/sops-v3.7.3.linux
sudo mv sops-v3.7.3.linux /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
# Create secret file
cat > secrets.yaml <<EOF
database_password: SuperSecretPassword
api_key: sk-1234567890abcdef
EOF
# Encrypt with AWS KMS
sops --encrypt --kms arn:aws:kms:us-east-1:123456789:key/abc secrets.yaml > secrets.enc.yaml
# Commit encrypted file safely
git add secrets.enc.yaml
git commit -m "Add production secrets"
# Decrypt when needed
sops --decrypt secrets.enc.yaml > secrets.yaml
Common Pitfalls
Committing Secrets: Accidentally committed secrets remain in Git history forever. Use git-secrets or similar tools to prevent commits containing secrets.
No Code Review: Skipping review for "quick fixes" leads to outages. All infrastructure changes must go through review, regardless of urgency.
Ignoring Drift: Manual changes outside Git create divergence. Regularly compare actual infrastructure state with code.
Large Pull Requests: Massive infrastructure changes are difficult to review effectively. Break changes into logical, reviewable pieces.
Key Takeaways
- Version control provides change tracking, collaboration support, rollback capability, and audit trails essential for infrastructure management
- Organize code using monorepo for simplicity, multi-repo for access control, or environment-based structure for strong isolation
- Choose branching strategies based on team size and release cadence - trunk-based for small teams, GitFlow for scheduled releases, environment branches for explicit promotion
- Implement thorough code review with pull request templates, review checklists, and automated validation checks
- Structure change management with development cycles, deployment windows, and blackout periods to reduce risk
- Never commit secrets to Git - use environment variables, secret management tools like Vault, or encrypted files with SOPS
- Avoid common mistakes: prevent secret commits with tooling, require review for all changes, address configuration drift promptly, and keep pull requests focused and reviewable