Best Practice Workflow for Terraform Deployment to Azure¶
Industry best practice workflow for deploying to Azure using Terraform in a controlled, scalable, and auditable way.
🌳 Branch-Based Development¶
🎯 Objective¶
Isolate infrastructure changes in a dedicated Git branch to enable safe experimentation, clear versioning, and collaborative review.
Why It Matters¶
- Isolation: Keeps your changes separate from production (
main
) until they’re validated and approved. - Traceability: Every change is tied to a commit, branch, and pull request—great for audits and rollback.
- Parallel Work: Multiple team members can work on different parts of the infrastructure without stepping on each other.
🛠️ Typical Workflow¶
1. Create a Feature Branch¶
Use a naming convention that reflects the purpose of the change:
Other examples:
- feature/update-vnet
- infra/aks-cluster
- bugfix/incorrect-tags
- refactor/module-storage
✅ Naming conventions help with automation, filtering, and clarity.
2. Create or Modify .tf
Files¶
Structure your Terraform code modularly. For example:
✅ Modular design promotes reuse and separation of concerns.
3. Commit Your Changes¶
Use clear, descriptive commit messages:
✅ Good commit hygiene makes PRs easier to review and helps future you understand what you did.
4. Push the Branch¶
Push your branch to GitHub:
This triggers your CI workflow (if configured) to validate and plan the changes.
🧠 Best Practices¶
Practice | Why It’s Useful |
---|---|
Use small, focused branches | Easier to review and test |
Avoid committing .tfstate or .terraform |
These should be in .gitignore |
Use pre-commit hooks | Enforce formatting and validation before pushing |
Keep modules versioned | Enables rollback and reuse across environments |
🧪 Automated Validation & Formatting¶
🎯 Objective¶
Ensure Terraform code is consistently formatted, syntactically valid, and secure—without provisioning any infrastructure. This step is essential for maintaining code quality and catching issues early during development.
When to Run These Checks¶
- After every local change
- On every commit or pull request
- Before merging into main or deploying
These checks are non-invasive and safe to run anytime. They don’t require cloud credentials or modify infrastructure.
Tools & Commands¶
tool | purpose | importance |
---|---|---|
terraform fmt |
Automatically formats .tf files to canonical style. |
Prevents formatting drift and improves readability across teams. |
terraform validate |
Validates syntax and internal consistency of Terraform files. | Catches typos, missing arguments, and structural issues before runtime. |
TFLint |
Lints Terraform code for best practices, deprecated syntax, and provider-specific issues. | Goes beyond validate to enforce style and catch subtle bugs. |
Checkov |
Static analysis for security misconfigurations and compliance violations. | Identifies insecure defaults, missing encryption, overly permissive IAM policies, etc. |
Infracost |
Estimates cost impact of infrastructure changes. | Adds financial awareness to infrastructure decisions. |
What Happens After You Commit¶
- Trigger: A push to any branch except main with changes to .tf files activates the workflow.
- Checkout: The workflow checks out your branch and compares it to origin/main.
- Change Detection: It identifies which .tf files were modified.
- Validation Steps:
- terraform fmt ensures formatting.
- terraform validate checks syntax and structure of the root module.
- tflint runs linting on each changed file using --filter.
Sample GitHub Actions Workflow¶
name: "Terraform File Commit Reaction"
permissions:
contents: write
pull-requests: write
on:
push:
branches-ignore:
- main
paths:
- "**/*.tf"
jobs:
terraform-checks:
runs-on: ubuntu-latest
steps:
# Step 1: Checkout the pushed branch
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
# Step 2: Detect changed .tf files
- name: Find Changed .tf Files
id: changed-files
run: |
CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep '\.tf$' || true)
echo "Changed .tf files:"
echo "$CHANGED_FILES"
echo "files=$CHANGED_FILES" >> $GITHUB_OUTPUT
- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
# Step 4: Run Terraform fmt on changed files
- name: Format Check
if: steps.changed-files.outputs.files != ''
run: |
for file in ${{ steps.changed-files.outputs.files }}; do
terraform fmt "$file"
done
# Step 5: Run Terraform validate (only if root module changed)
- name: Validate Terraform
if: steps.changed-files.outputs.files != ''
continue-on-error: true
run: |
if ! terraform validate; then
echo "::error::❌ Terraform validation failed. Check root module syntax."
else
echo "✅ Terraform validation passed."
fi
# Step 6: Run TFLint setup
- name: Run TFLint
if: steps.changed-files.outputs.files != ''
uses: terraform-linters/setup-tflint@v4
with:
tflint_version: latest
- name: TFLint Execution
if: steps.changed-files.outputs.files != ''
continue-on-error: true
run: |
for file in ${{ steps.changed-files.outputs.files }}; do
if ! tflint --filter="$file"; then
echo "::error file=$file::❌ TFLint failed on $file. Check syntax and formatting."
else
echo "✅ TFLint passed for $file"
fi
done
🧠 Best Practices¶
- Use pre-commit hooks to run these checks locally before pushing.
- Fail CI builds if any check fails—this enforces discipline.
- Customize rules via
.tflint.hcl
and.checkov.yml
to suit your environment. - Run checks recursively to cover all modules and subdirectories.
Would you like me to draft a pre-commit config next, or help you set up .tflint.hcl
and .checkov.yml
for your environment?