You’re probably here because your infrastructure already has that smell.
Not smoke. Not a full fire yet. Just that warm, electrical, slightly cursed smell of systems built through a long chain of “quick changes,” console clicks, sticky notes, old wiki pages, and one senior admin who left six months ago without documenting a damn thing. Someone asks how production was built, and the honest answer is, “Well a server was built originally, we then made a lot of changes over time to mature it and make it better.”
That’s usually where the question starts. What is Terraform used for? In plain English, Terraform is used to define infrastructure in code so you can build it, change it, and rebuild it without relying on memory, screenshots, or luck.
HashiCorp (who made Terraform) describes Terraform as an infrastructure-as-code tool for building, changing, and versioning cloud and on-prem resources, including low-level services like compute, storage, and networking, plus higher-level services like DNS entries and SaaS features, in its Terraform introduction.
👉 What is Terraform? No fluff: Imagine you could build a massive text file with every setting about your Windows laptop. Line 1 has the name of the laptop, line 2 has the IP address, line 3 has the default browser, line 4 has the time zone etc. If you did this thoroughly, eventually you have a text file which is so bullet proof, you can buy a new laptop, run the text file and you are left with an identical laptop.
👉 Why use Terraform? No fluff: People use Terraform to build repeatable systems. Imagine your company has 30 windows servers for printing, file shares, DNS, SCCM, Jira, SIEMs etc. You might then have a development, test, pre-production and production environments. That’s now 120 Windows servers. Clicking through an Azure or VMWare portal to create 120 servers is time consuming – hence Terraform and IaC were born.
If you’ve ever inherited a server that nobody wants to reboot because “we’re not sure what else it’s doing”, you’ve already met the problem Terraform solves.
A lot of companies still build infrastructure the medieval way. Someone opens AWS, Azure, VMware, Cloudflare, or whatever else the company bought after a conference. They click around. They create a VM, a subnet, a firewall rule, a load balancer, a DNS record, and maybe a storage bucket. Then they swear they’ll document it later. They won’t.
A month later, dev and prod are “basically the same”, which is engineer-speak for “we think it’s the same and can’t be bothered spending 2 weeks confirming it”. One environment has an extra rule somebody added during an outage. Another has a different VM tier because Azure no longer supports the BEFv17h51_sH1_Dvsf2 anymore. A third has naming conventions invented by a caffeinated goblin. Now nobody wants to touch anything because touching it might break the one thing keeping payroll, sales, or customer logins alive.
That’s the classic problem – everything is just a little bit unique. Terraform makes IT systems identical and quicker to build, but the catch is stuffing around with enormous configuration files (the text file analogy I used before). Like I said it’s better, but far from perfect.
Practical rule: If rebuilding a critical system would require memory, screenshots, or “Dave probably knows,” you don’t have infrastructure management. You have a hostage situation.
That’s why people stick with it. Not because “infrastructure as code” is a sexy phrase. It isn’t. It sounds like something a consultant says before billing you for a “workshop”. Terraform matters because it cuts down the oh sh*t moments that come from manual drift, hidden dependencies, and changes nobody can fully explain.
At 4:45 p.m. on a Friday, someone says they only changed one network security group. Now production cannot talk to the database, nobody knows what the old rule was and let’s be honest, how committed are you to updating the original build document Visio file after every single change?
Terraform works because it treats infrastructure as a declared target state. You write down what should exist, then Terraform works out how to create, change, or remove it without you playing scavenger hunt through six cloud menus.
Surely by now you see the value of Terraform, but even if you don’t. Let’s consider doing what humans do best, removing ourselves from anything that could be catastrophic.
I didn’t realize how far this had gotten until I began my aviation training back in 2023. Literally everything in aviation is built to be fool proof. An audible alarm screams “RUNWAY TOO SHORT” when you touch down without enough runway remaining. Airbus has Alpha Floor Protection which makes it next to impossible to stall the plane and even crashing into another plane is nearly impossible thanks to ACAS telling you what to do to avoid collision.
So whether we like it or not, it’s time to put some aviation into IT and prevent those of us with sausage fingers from being able to make silly mistakes with enormous consequences.
Terraform cuts out a lot of that mess because the desired setup lives in code. A virtual machine with these settings. A network with these rules. A DNS record pointing here. You review the change before it hits the environment instead of finding out afterward that somebody fat-fingered the wrong region.
As a systems engineer myself, it can be a welcome relief to be in an environment which uses Terraform and IaC because it not only reduces the planned mistakes, but also the mistakes when applying the changes. If we take the big text file analogy from before, you can’t click the wrong button or commit someone else’s firewall changes by accident, you are just typing into a text file and the change is done for you.
The Terraform workflow is simple. The values are not.
WriteYou define the infrastructure in configuration files. This is the record of what should exist.
PlanTerraform compares the code to the existing environment and shows what it intends to change. Many poor ideas are thus prevented from becoming outage reports.
ApplyTerraform makes the approved changes in the right order, so dependencies are handled without you manually babysitting every step.
The middle step is why experienced admins keep coming back to Terraform. A cloud console is great at helping you make changes quickly. It is terrible at forcing you to slow down for thirty seconds and ask, “If I do this, what else breaks?”
New users usually get stuck on the same two things.
A provider is the plugin Terraform uses to interact with a service like AWS, Azure, Google Cloud, Cloudflare, or GitHub. Providers map your configuration to the platform’s API calls.
👉 What is a provider? No fluff: Using the laptop text file example, how do you know what to include in the huge file? We used the example of name, IP, location. What if you build it in Azure where it needs location, region and zones, but then you want to build the same thing in Vultr which only asks for location. The plugin is downloaded from Azure, AWS, Vultr etc and it contains the variables you need to build a server there, in other words it’s a schema. It also acts as the engine with all the nitty gritty web “stuff” so that when you click “go” and the file magically builds it securely.
Terraform keeps a state file so it knows which real resources match the code. Without state, it cannot safely tell what it owns, what changed, or what needs to happen next.
👉 What is a state file? No Fluff: A state file is super simple. It is just a file created to track changes after you execute the file. So if you built a virtual machine in Azure with Terraform, it instantly creates a state file locally on your computer. That file contains the exact copy of what is now published – server name, IP, location, version of operating system etc. It acts as a running copy of what currently lives in the cloud after the most recent change.
Teams create their own pain: an admin runs changes from a laptop with stale state. Another person edits a resource by hand in the console. Now the code says one thing, the cloud says another, and everybody wastes an afternoon sorting out drift that should never have happened.
This is why state files are saved somewhere central like a file share, AWS bucket or Azure storage account. The file must be secure, well maintained and not saved locally where different developers/systems engineers all have a different version.
Terraform gets real value when it solves ordinary, annoying infrastructure problems that teams hit all the time.
A common mess looks like this. Every developer has a slightly different environment. One has a bigger VM. One has an old image. One has an extra package that “shouldn’t matter” but somehow does. Then someone says, “it works on my machine,” and everyone else considers light arson.
Terraform helps because you can define the environment once and stamp out consistent copies.
resource "aws_instance" "dev_vm" { ami = var.base_image instance_type = "t3.small" tags = { Role = "dev" App = "internal-api" } }
That snippet is tiny on purpose. The point isn’t the syntax. The point is consistency. Once the pattern exists, you stop building each machine like a one-off art project.
A lot of teams aren’t “multi-cloud” because of strategy. They’re multi-cloud because reality happened. The app runs in AWS, then we moved to Azure, a specific GPU means we have something in GCP, DNS sits in Cloudflare, there’s Hyper-V on-prem because there are 3 annoying servers on it that need to be migrated.
Terraform is useful here because one codebase can describe resources across multiple providers.
provider "aws" { region = "us-east-1" } provider "cloudflare" { api_token = var.cloudflare_api_token }
One repository, infinite clouds. That beats flipping between browser tabs like a noob playing a first person shooter, flailing between primary and secondary weapons trying to find the nade.
For teams handling credentials and sensitive values in AWS, this guide to Terraform AWS Secrets Manager is worth reading. Secrets are where otherwise decent Terraform setups go feral.
Networking is where “just this once” becomes institutional regret.
A smart Terraform pattern is to create a reusable module for baseline network design. Then every new project gets the same boring, approved foundation instead of whatever someone cobbled together during a deadline panic.
module "app_network" { source = "./modules/network" environment = "staging" app_name = "billing" }
Many environments maintain poor network restrictions – Allow anything from VMWare to Azure for example. Why don’t us IT folks just allow the 13 ports required for the VMWare domain controller on 10.1.1.35 to talk to the Azure domain controller on 172.13.0.35? Because that takes time, fiddling and constant tweaks to resolve issues.
Terraform provides security to the environment by making firewall changes a cookie cutter activity. The domain controller task is executed, VMs are built and then the firewall rules are implemented (regardless of it it’s in a Palo Alto, a FortiGate, Azure NSG’s or a Cisco 9K).
The sneaky answer to ‘what is Terraform used for’ is recovery, even though a lot of my techy readers will be ready to scream disagreements, there is truth to it.
Not backup. Not disaster recovery as a marketing phrase. Actual recovery of the whole ecosystem. When an environment needs to be recreated, Terraform gives you a written definition of what should exist.
When your infrastructure lives only in people’s heads, every resignation becomes an availability risk.
🔥 Don’t confuse Terraform with an alternative to backups.🔥
The simplest way to think of Terraform for restoration is Terraform can restore a system, can NetBackup, Azure Backups or Veeam? No way. These tools restore VMs, databases or data, they don’t help if the business relies on 30 switches, 6 firewalls, Azure, GCP, Snowflake, Jira and servers in 4 datacenters.
If you have the commitment, Terraform can be used to re-build the entire environment from scratch and even do it into a different cloud – that’s just cool.
Terraform gets compared to a lot of tools it shouldn’t always be compared to. Some are competitors. Some are complements. Some are just dragged into the ring because procurement lumped them together under “automation stuff.”
If you live deep inside AWS and have no interest in leaving, CloudFormation can make sense. It’s AWS-native, tightly integrated, and designed around the AWS ecosystem.
Terraform wins when your world isn’t that clean. If you manage AWS plus another cloud, plus DNS, plus SaaS services, Terraform usually feels more cohesive because it gives you one language and one workflow across platforms.
The trade-off is straightforward:
This one confuses people constantly because they overlap just enough to create arguments on the internet.
The short version is this. Terraform builds the house. Ansible arranges the furniture and fiddles with what’s inside.
Terraform is strongest at provisioning infrastructure resources. Think networks, instances, load balancers, DNS, storage, and service definitions.
Ansible shines when you need to configure systems after they exist, especially at the operating system and application layer. That is why I personally prefer using Terraform, it’s a lot easier for my little brain to work out how to build a server with code than how to build and configure IIS with code.
If you want a deeper practical breakdown, this Terraform vs Ansible comparison does a solid job of showing where each tool fits.
Use Terraform to decide what should exist. Use Ansible to decide what should be installed and configured inside it.
Pulumi takes a different approach. Instead of using HCL (HashiCorp Configuration Language – the Terraform langage), it lets you define infrastructure in general-purpose languages like Python or TypeScript.
That sounds attractive, especially to software developers who already live in those languages. The upside is familiarity and more expressive code. The downside is that more expressive code can become more complicated code. Infrastructure definitions don’t always benefit from becoming miniature software projects.
👴Being a systems engineer, I run away from Pulumi like the time a bird got in my house. It’s just not for me and I can take all the brave pills in the world; I’m still not going to bond with it… and I’m still going to need my wife to get rid of the bird.
There’s no knockout winner for every team.
Pick Terraform when you want a strong provisioning workflow, broad provider coverage, and code that ops teams can usually read without developing a side hobby in software architecture. Pick CloudFormation if AWS is your whole universe. Pick Ansible when the hard part is system configuration, not resource creation. Pick Pulumi if your team benefits from general-purpose languages and won’t turn every infrastructure repo into an unreadable abstraction swamp.
Also, infrastructure code doesn’t save you from basic hygiene. If your servers aren’t patched and your update process is chaos, your shiny IaC repo won’t rescue you. Operational discipline still matters, especially for things like patching a Windows vulnerability.
You can use Terraform badly. In fact, many people do.
They keep state file on a laptop. They hardcode secrets. They duplicate the same ugly block of code across five environments and call it “temporary.” Then they act surprised when the new tech ops manager starts interrogating them about their work quality.
The state file is not some optional side artifact. It is central to how Terraform understands the infrastructure it manages.
Leaving state on a local machine is one of those choices that feels harmless until it wrecks your week. Someone loses a laptop. Someone applies from an outdated branch. Two engineers make conflicting changes with different local copies. Suddenly everyone is speaking in a very calm tone because panic is already implied.
Use remote state. Use locking. Treat state like production data, because functionally, that’s what it is.
Terraform code ends up in version control. That’s good.
Git history is useful.
Reviewable changes are useful.
Repeatability is useful.
What is not useful is committing API tokens, passwords, private keys, or secret values into a repository where they will live forever like a statue to bad judgment.
Use proper secret handling. Pull values from dedicated secret stores. Inject them through your pipeline. Reference them indirectly. If you’re working on web-facing systems, these habits belong in the same bucket as broader website security best practices. Sloppy secret management is rarely an isolated problem.
A few habits separate adults from chaos here:
Terraform supports modules, and if you ignore them long enough, you’ll end up maintaining the same logic in fifteen places.
A module is like an app you can run and send it different variables.
module "webserver" { 👈 Run this module which could be hundreds of lines of HCL code. source = "./modules/simple-vm" vm_name = "web01" 👈 Send this variable. environment = "prod" 👈 Send this variable. location = "australiaeast" 👈 Send this variable. vm_size = "Standard_B2s" 👈 Send this variable. }
Terraform starts scaling from “one helpful repo” into “an actual operating model.”
There’s a particular breed of engineer who treats production like a sketchpad. They’ll run manual changes because “it’s faster”.
That’s how drift happens. Terraform says one thing. Reality says another. During the next apply, everyone gets to discover which version wins.
Don’t normalize console edits for managed resources unless you are in a true emergency. And if you are in an emergency, capture the change back into code as soon as the fire is out.
A short anti-chaos checklist helps:
Run plan before applyReview changes like you expect them to be wrong, because sometimes they are.
Use version control for everythingInfrastructure changes without history are just expensive guesses.
Prefer boring patternsClever Terraform tends to age like milk. Readable Terraform survives handoffs.
Good Terraform should feel slightly dull. If your repo looks like wizardry, future-you is going to hate present-you.
Yes.
It’s used to define, change, and manage infrastructure in a repeatable way.
That doesn’t mean Terraform is effortless. It has trade-offs. It needs to be managed far more closely than just building a VM and letting it sit in the cloud. It’s not something to be initiated because it’s now a buzzword.
Plenty of businesses employ a sole DevOps engineer to start building servers and infrastructure with code despite all the engineers and devs refusing to adopt it. If that’s the environment you live it – forget it. You’ll have 500 half-done templates that are never used once the DevOps guy moves onto the next role.
But for most sys admins, cloud engineers, and small infrastructure teams, the upside is obvious. Fewer flakey systems. Fewer mystery differences between environments. Fewer “nobody knows why this exists” resources floating around production like cursed debris.
The best way to start is small. Don’t begin with your most critical environment like a lunatic. Pick something low-risk. A DNS record. A test VM. A simple network object etc and work on building up the IaC culture.
👉 The fastest way to kill IaC/Terraform culture and investment is to employ a DevOps engineer, brag about what an expert they are and hope everyone else jumps onboard. Nurture it, have everyone in the team do one task a month using IaC and reward the big and small wins.
terraform { required_providers { azurerm = { source = "hashicorp/azurerm"; version = "~> 4.0" } } } provider "azurerm" { features {} } # 1. New Resource Group for your Compute and Network Infrastructure resource "azurerm_resource_group" "rg" { name = "rg-prod-win-servers" location = "australiaeast" } # 2. Register your VHD from your specific storage account as an official Azure Managed Image resource "azurerm_image" "win2022_custom" { name = "win-server-2022-custom" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name os_disk { os_type = "Windows" os_state = "Generalized" blob_uri = "https://changethis.blob.core.windows.net/images/server2022-Azure_2.vhd" caching = "ReadWrite" storage_type = "StandardSSD_LRS" } } # 3. Network Infrastructure Setup (Configured to 10.2.0.0/16 to isolate from your 10.1.0.0/16 space) resource "azurerm_virtual_network" "vnet" { name = "vnet-prod-new" address_space = ["10.2.0.0/16"] location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name } resource "azurerm_subnet" "subnet" { name = "subnet-internal" resource_group_name = azurerm_resource_group.rg.name virtual_network_name = azurerm_virtual_network.vnet.name address_prefixes = ["10.2.1.0/24"] } # FIXED: Standard SKU with Static Allocation to meet modern Azure provider requirements resource "azurerm_public_ip" "pip" { name = "pip-win-server-01" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name allocation_method = "Static" sku = "Standard" } # Network Security Group setup mapping inbound rule constraints for RDP access (be careful) resource "azurerm_network_security_group" "nsg" { name = "nsg-win-server-01" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name security_rule { name = "AllowRDP" priority = 1000 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "3389" source_address_prefix = "*" destination_address_prefix = "*" } } # Network Interface binding components together resource "azurerm_network_interface" "nic" { name = "nic-win-server-01" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name ip_configuration { name = "internal" subnet_id = azurerm_subnet.subnet.id private_ip_address_allocation = "Dynamic" public_ip_address_id = azurerm_public_ip.pip.id } } resource "azurerm_network_interface_security_group_association" "nic_nsg" { network_interface_id = azurerm_network_interface.nic.id network_security_group_id = azurerm_network_security_group.nsg.id } # 4. Create the Virtual Machine using your Cost-Optimized Size configuration resource "azurerm_windows_virtual_machine" "my_windows_server" { name = "prod-win-vm01" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location size = "Standard_B2s" admin_username = "enter a username here" admin_password = "enter a password here" network_interface_ids = [ azurerm_network_interface.nic.id, ] source_image_id = azurerm_image.win2022_custom.id os_disk { caching = "ReadWrite" storage_account_type = "StandardSSD_LRS" } # Enables Monitored Boot Diagnostics (No storage account required) boot_diagnostics {} # Fails fast at 15 minutes instead of waiting 40 timeouts { create = "15m" } }
If you’re sorting through infrastructure tools, cloud platforms, or the messy reality between “works in theory” and “works in production,” Monro Cloud is worth a look. It’s built for people who want practical guidance, not recycled vendor fluff, especially if you’re making decisions across hosting, SaaS, security, and day-to-day IT operations.