Git for Cloud Engineers: Workflows, GitOps, and Infrastructure PRs
Git is not just version control for application code — it is the primary mechanism for managing infrastructure changes on most cloud teams. Pull requests review Terraform, GitOps treats Git commits as the source of truth for deployments, and a well-run infra repo looks a lot like a well-run software repo. This page covers how Git actually works in cloud engineering practice.
Branching strategies: trunk-based versus feature branches
Two branching models are common in cloud and DevOps teams. Understanding the trade-offs helps you adapt to whichever one your team uses.
Trunk-based development: Everyone commits to a single main branch (often called main or trunk) frequently — at least daily. Changes are small. Short-lived feature branches may exist but are merged within hours or a day or two. This model reduces merge conflicts and keeps the main branch in a deployable state.
Feature branches: Each piece of work lives on its own branch until it is complete, then merges to main via a pull request. Branches can be long-lived — days or weeks. Common in teams that have formal review gates before merging.
For infrastructure (Terraform, Kubernetes manifests), most teams use a pull request model regardless of branching strategy. The PR is the gate for review, approval, and running the Terraform plan. The branch name usually describes the change: add-rds-instance, update-security-groups.
The Terraform PR workflow in practice
The standard workflow for infrastructure changes with Terraform and Git:
- Create a branch from main
- Write or modify Terraform code
- Run
terraform planlocally to check the change - Push the branch and open a pull request
- CI runs
terraform planautomatically and posts the output to the PR - A teammate reviews the code and the plan output
- After approval, the PR is merged to main
- CI or a tool like Atlantis runs
terraform applyon the merged main branch
The critical step is that terraform apply runs from the main branch after merge, not from the PR branch. This ensures the applied configuration matches what was reviewed.
A typical GitHub Actions workflow for Terraform plan on PRs:
name: Terraform Plan
on:
pull_request:
paths:
- 'terraform/**'
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.7.0"
- name: Terraform Init
run: terraform init
working-directory: terraform/
- name: Terraform Plan
run: terraform plan -no-color
working-directory: terraform/
env:
AWS_ROLE_ARN: ${{ vars.TERRAFORM_ROLE_ARN }}GitOps: what it means and why it matters
GitOps is an operational model where Git is the single source of truth for the desired state of a system. Instead of running commands to deploy things, you commit a change to Git. An automated tool (like Argo CD or Flux) detects the change and reconciles the actual state to match.
The key principle: nothing changes in production except via a Git commit. This means all changes are auditable (git log is your change history), reversible (git revert undoes a change), and reviewed (PRs enforce approval before changes land).
GitOps is most commonly associated with Kubernetes deployments, where a change to a deployment YAML in a Git repo triggers Argo CD to update the cluster. But the principle applies to Terraform too — some teams use GitOps-style tooling (like Atlantis or Terraform Cloud) to enforce that infrastructure only changes via Git.
Practical GitOps pattern: Application code and infrastructure code live in separate repositories. The app repo builds images and tags them. The infra/config repo contains Kubernetes manifests. A CI pipeline updates the image tag in the infra repo on a successful build, which triggers Argo CD to deploy the new version.
Git commands for everyday cloud work
The commands cloud engineers use most — beyond the basic add/commit/push cycle:
# Create and switch to a new branch
git checkout -b add-vpc-peering
# Or with newer git:
git switch -c add-vpc-peering
# See what changed
git diff
git diff --staged # show staged changes
# Stage specific files, not everything
git add terraform/vpc.tf
git add terraform/variables.tf
# See the log with a useful format
git log --oneline --graph --all
# Undo a staged change without losing the edit
git restore --staged terraform/vpc.tf
# Undo a local change entirely
git restore terraform/vpc.tf
# Fetch the latest remote changes without merging
git fetch origin
# Rebase your branch on the latest main
git fetch origin && git rebase origin/main
# Find which commit introduced a bug
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
# See what changed in a specific commit
git show abc1234
# Create an annotated tag
git tag -a v1.3.0 -m "Release v1.3.0"
git push origin v1.3.0The habit of using git rebase instead of git merge when updating a branch keeps the history linear and easier to read. On most cloud infrastructure repos, a linear history matters because you are auditing what changed and when.
gitignore for infrastructure repos
An infrastructure repository needs a careful .gitignore. Several files that get created during Terraform and general development should never be committed.
# Terraform local state and plan files
*.tfstate
*.tfstate.backup
.terraform/
*.tfplan
crash.log
# Local environment and secrets
.env
.env.local
*.tfvars
!example.tfvars # DO commit the example file, not the real one
# OS and editor noise
.DS_Store
.idea/
*.swp
__pycache__/
*.pyc
# Secrets and credentials (belt and braces)
*.pem
*.key
credentials.json
service-account.jsonThe most dangerous omission: forgetting to ignore *.tfvars files that contain real secret values (passwords, API keys). Add an example file (example.tfvars or terraform.tfvars.example) to show the expected shape without the real values.
Tagging releases for infrastructure
Tags give you named points in history you can reference. For infrastructure repos, tags are how you mark working states you can return to, and they feed into CI pipelines that trigger deployments.
Semantic versioning (v1.0.0, v1.1.0, v2.0.0) works for infrastructure modules. The convention: breaking changes increment the major version, new backwards-compatible features increment minor, bug fixes increment patch.
For Terraform modules published to a registry (internal or public), tagging is how version pinning works. When a team pins version = ”~> 2.1” in their Terraform, they are pinning to the Git tags of your module repository.
# List existing tags
git tag -l
# Tag the current commit
git tag -a v2.0.0 -m "v2.0.0 - Add multi-region support"
# Push all tags
git push origin --tags
# Delete a tag (do carefully — others may be pinned to it)
git tag -d v2.0.0-rc1
git push origin --delete v2.0.0-rc1Writing useful commit messages for infrastructure
Infrastructure commit messages matter more than most engineers think. When something goes wrong in production at 2am, the git log is how you reconstruct what changed. A good commit message explains what changed and why — not just what command you ran.
- Weak:
update terraform - Weak:
fix thing - Stronger:
increase RDS instance size from db.t3.medium to db.t3.large - Stronger:
add NAT gateway to private subnet in eu-west-1b — fixes Lambda outbound connectivity
The subject line (first line) should complete the sentence “If applied, this commit will…”. The body (optional, separated by a blank line) explains the context — why the change was made, what the alternatives were, any caveats.
Summary
- Terraform changes go through a PR workflow: plan on the PR branch, apply from main after merge
- GitOps means Git is the source of truth — production only changes via a reviewed and merged commit
- Ignore
*.tfstate,*.tfvars(with real secrets), and.terraform/in infrastructure repos - Tags mark stable versions; semantic versioning applies to infrastructure modules just as it does to libraries
- Good infrastructure commit messages explain why, not just what — the git log is your change audit trail