5 Terraform Best Practices for Production Azure Projects
5 Terraform Best Practices for Production Azure Projects
After three years of intensive work with Terraform in enterprise environments (from H&M Group to smaller FinTech companies), I've identified five key principles that separate amateur IaC code from production-hardened infrastructure.
1. Remote State with Locking – No Exceptions
Never, I mean never, use a local terraform.tfstate in a team setting. An Azure Storage Account with a blob container and state locking is the foundation:
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstprod"
container_name = "tfstate"
key = "landing-zone/prod.tfstate"
use_oidc = true
}
}Why use_oidc = true? Because Managed Identity + OIDC federation eliminates the need to store service principal secrets in pipeline variables. More secure, simpler.
2. Modularization from Day One
The biggest mistake I see at client sites: a massive monolithic main.tf with 2,000+ lines. The solution? Terraform modules:
infrastructure/
├── modules/
│ ├── networking/ # VNet, NSG, Route Tables
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/ # VM, VMSS, AKS
│ ├── security/ # Key Vault, Defender
│ └── monitoring/ # Log Analytics, Alerts
├── environments/
│ ├── dev/
│ │ └── main.tf # Calls modules with dev parameters
│ ├── staging/
│ └── prod/
└── shared/
└── backend.tf
Each module has a clear responsibility. A change in the networking module doesn't affect compute. You test in isolation, deploy safely.
3. Drift Detection in CI/CD Pipeline
Infrastructure changes over time – someone clicks in the portal, someone runs a script. Drift is the silent killer of IaC consistency. The solution:
# Azure DevOps pipeline - nightly drift check
schedules:
- cron: "0 3 * * *"
displayName: "Nightly drift detection"
steps:
- task: TerraformCLI@2
inputs:
command: 'plan'
workingDirectory: 'infrastructure/environments/prod'
commandOptions: '-detailed-exitcode'
continueOnError: true
- script: |
if [ $TERRAFORM_EXITCODE -eq 2 ]; then
echo "##vso[task.logissue type=warning]DRIFT DETECTED!"
# Send notification to Teams/Slack
fi
displayName: 'Evaluate drift'4. Resource Naming via a Naming Convention Module
Consistent naming is the SEO of infrastructure – without it, you'll get lost:
module "naming" {
source = "Azure/naming/azurerm"
suffix = [var.environment, var.location_short]
}
resource "azurerm_resource_group" "main" {
name = module.naming.resource_group.name
location = var.location
}
resource "azurerm_virtual_network" "hub" {
name = module.naming.virtual_network.name
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
address_space = ["10.0.0.0/16"]
}5. Infrastructure Testing – Terratest or terraform validate
Yes, infrastructure needs to be tested. At a minimum:
terraform validate– syntax checkterraform planin a PR pipeline – review changes before mergetflint– static analysis for best practices- Terratest (Go) – integration tests for critical modules
Conclusion
Terraform is a powerful tool, but without discipline it becomes a nightmare. Start with remote state and modularization, add drift detection and naming conventions. Your future self will thank you.
Interested in how to set up a complete Terraform CI/CD pipeline in Azure DevOps? I'm preparing a detailed guide on that topic.
About the author

Martin Rylko
Senior Cloud Architect & DevOps Engineer
14+ years in IT – from on-premises datacenters and Hyper-V clustering to cloud infrastructure on Microsoft Azure. I specialize in Landing Zones, IaC automation, Kubernetes and security compliance.
You might also like
Building an Azure Landing Zone with Bicep
A practical guide on how to effectively structure your Bicep code for deploying an enterprise-ready Azure Landing Zone (ALZ).
ReadAKS for Production: A Checklist for Cloud Architects
What you need to address before deploying Azure Kubernetes Service to production – from networking through RBAC and scaling to monitoring and backup.
ReadNIS2 and Azure: A Practical Compliance Checklist for Architects
How to prepare your Azure environment for the NIS2 directive – concrete steps from Azure Policy through Defender for Cloud to logging and incident response.
Read