Infrastructure as Code (IaC) is one of those topics that sounds straightforward in theory but quickly becomes complicated in practice. The perils of too much choice certainly hit home when we talk about IaC and Azure. Walk into any organisation and you’ll find multiple teams, each with their own views on the “right” IaC choice. A bit of Terraform here, some Bicep there, and then the dreaded ARM templates that no one really wants to touch or own.
The result? Fragmentation, inconsistency, and a maintenance burden that only grows over time.
In this post, I’ll walk through how to choose the right IaC tool for your needs, establish standards that actually stick, and implement version control practices that prevent your automation from becoming a liability.
Choosing Your IaC Tool
So how do you decide on the right approach? It’s simpler than you might think. Rather than chasing the “best” tool, focus on what fits your environment and team.
ARM Templates
Cloud-native and the original Azure IaC solution, but not very user-friendly. ARM is pretty dated and difficult to manage, with verbose JSON syntax that makes even simple deployments hard to read and maintain.
Best for: Legacy automation or when using features that only exist in ARM (which is increasingly rare).
Bicep
Microsoft’s replacement for ARM, aligning with more modern toolsets like Terraform. Bicep is much simpler to develop with, offering a human-readable code approach that compiles down to ARM templates behind the scenes.
Best for: Pure Azure deployments where simplicity and maintainability matter. If you’re all-in on Azure and want something Microsoft-native, Bicep is the way forward.
Terraform
Similar to Bicep in readability and approach, but with the significant benefit of being cloud and provider-agnostic. Terraform can manage Azure, AWS, GCP, and even on-premises resources like VMware. It has a vast community of enthusiasts and is fully supported by Azure through the AzureRM provider.
Best for: Hybrid or multi-cloud environments, or where governance via code is key. If you need to manage infrastructure across multiple platforms, Terraform is hard to beat.
Ansible
While Ansible can be used for IaC, it’s more commonly used—and much stronger—in configuration management. Ansible ensures infrastructure resources, servers, and networking equipment conform to configuration settings after they’ve been deployed.
Best for: Managing VMs, patching, and post-deployment setup. Think of Ansible as the tool that configures what IaC provisions.
Making the Decision
The key is to stick to just one IaC tool (perhaps with the addition of Ansible for configuration management), and make sure everyone on the team adopts the same approach. Tool proliferation creates technical debt, increases the learning curve for new team members, and makes it nearly impossible to maintain consistent standards. Choose the tool that best fits your environment, then commit to it.
Setting Standards That Stick
We’ve all been there: the team starts strong with a few good IaC templates, but over time, things drift. Team members move on, new people join, and before you know it, you’re left with a graveyard of half-used templates, inconsistent naming, and questionable quality.
No one’s quite sure which version to trust, so the cycle continues—new templates are created, and the sprawl grows.
It’s an easy trap to fall into, but it doesn’t have to be this way. With a few simple principles, you can turn that chaos into a structured, reusable IaC platform that scales with your teams.
Five Steps to Building an IaC Platform
Set the standards upfront
Define your naming conventions, agree on variable patterns and descriptions, and make sure every template has a clear README. Naming matters—and so does consistency. Document these standards in a shared wiki or repository guideline that all contributors can reference.
Centralise your modules
Store all reusable modules in a master Git repository (GitHub, Azure DevOps, GitLab, etc.). This becomes your single source of truth for IaC assets. Having one place to look eliminates confusion and reduces duplication.
Tag your commits
Use semantic versioning (v1.0.0, v1.1.0, etc.) and update the README with each release. Combine this with clear commit messages and branch policies to enforce version control discipline. Tagging isn’t optional—it’s what makes your modules trustworthy and safe to consume.
Never copy and paste
Always reference modules directly from the repository. Use version tags to ensure consistency between deployments. Copying code only creates drift—and future pain. When you need to update something, fix it at the source, version it, and roll it out properly.
Review regularly
Schedule periodic reviews to ensure standards are followed and modules remain up to date. This prevents reactive firefighting later down the line. Set quarterly or bi-annual reviews where the team assesses module quality, removes deprecated code, and updates documentation.
Building an IaC platform isn’t about templates—it’s about creating trust in your automation. When teams have confidence in the platform, they reuse, contribute, and innovate.
Version Control: The Key to Consistent Deployments
One of the biggest challenges I’ve faced with IaC is ensuring repeatable deployments. Terraform providers (like the Azure provider) release regular updates, which sometimes include breaking changes. When I updated my modules to add new features, I’d occasionally discover that re-running an older deployment would fail or conflict because the module had changed underneath it.
What I needed was a way to lock module versions, just like we do with providers in Terraform. That way, I could guarantee consistent, repeatable deployments—no surprises.
The Solution: Git Tags
It turns out, the fix was surprisingly simple: Git tags.
By tagging each stable module release—ideally following Semantic Versioning—you can safely reference a specific version in Terraform without worrying about future changes breaking your deployments.
Here’s how to tag a release:
git tag -a v1.1.0 -m "Module version 1.1.0"
git push origin --tags
Remember: tags don’t get pushed automatically with a normal git push, so you’ll need that second command to publish them.
You can find the full Git reference guide for tagging here: Git Basics – Tagging
Using Tagged Versions in Terraform
Once tagged, it’s easy to lock your Terraform module to a specific release:
module "network" \{
source = "git\::https://github.com/org/network-module.git?ref=v1.1.0"
}
Now, every deployment that uses this module will pull the same version, no accidental updates, no hidden drift. If you need to test a newer version, you can do so in a controlled environment by simply changing the ref parameter. When you’re confident the new version works, update your production deployments deliberately.
This approach gives you:
- Predictability: Deployments behave consistently over time
- Safety: Changes are intentional, not accidental
- Traceability: You can see exactly which version was used in any deployment
- Rollback capability: If something breaks, reverting to a known-good version is trivial
Conclusion
Infrastructure as Code is powerful, but only when it’s managed with discipline. Choosing the right tool, establishing clear standards, and implementing proper version control aren’t optional extras—they’re the foundation of sustainable automation.
Start small. Pick your IaC tool, document your standards, and tag your first release. Build from there. The goal isn’t perfection from day one; it’s creating a platform that teams can trust and build upon.
When your IaC platform is reliable, teams stop working around it and start working with it. That’s when automation becomes a true enabler rather than just another source of technical debt.