Terraform Course
Terraform vs CloudFormation vs ARM
Every major cloud provider built their own IaC tool. AWS built CloudFormation. Azure built ARM templates. Google built Deployment Manager. Then Terraform arrived — and most teams switched. This lesson explains exactly why.
This lesson covers
What CloudFormation is and where it falls short → What ARM templates are and where they fall short → How Terraform compares to both → The multi-cloud problem none of the native tools solve → A direct side-by-side comparison → When you might still choose a native tool
AWS CloudFormation
CloudFormation is AWS's native IaC tool, released in 2011 — three years before Terraform. You write templates in JSON or YAML that describe AWS resources, and CloudFormation provisions them for you.
It is deeply integrated with AWS. New AWS services get CloudFormation support on the same day they launch — sometimes before any other tool. AWS manages the rollback logic for you. If a stack fails halfway through, CloudFormation can roll back every resource it already created. For teams that live entirely inside AWS and always will, it is a reasonable choice.
But the moment you step outside those boundaries, CloudFormation breaks down. Here is what a basic CloudFormation template looks like for the same EC2 server from Lesson 1:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Create a basic EC2 instance",
"Parameters": {
"Environment": {
"Type": "String",
"Default": "dev"
}
},
"Resources": {
"MyServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-0c55b159cbfafe1f0",
"InstanceType": "t2.micro",
"Tags": [
{ "Key": "Name", "Value": { "Fn::Sub": "web-server-${Environment}" } },
{ "Key": "ManagedBy", "Value": "CloudFormation" }
]
}
}
},
"Outputs": {
"ServerPublicIp": {
"Value": { "Fn::GetAtt": ["MyServer", "PublicIp"] }
}
}
}
Compare that to the Terraform version from Lesson 1. Both create the same server. The Terraform version is shorter, reads like plain English, and does not require knowledge of CloudFormation-specific functions like Fn::Sub and Fn::GetAtt. And CloudFormation only works on AWS. Full stop.
Azure ARM Templates
ARM — Azure Resource Manager — is Microsoft's native IaC approach for Azure. ARM templates are written in JSON and describe the resources you want to deploy into an Azure resource group.
ARM templates are notoriously verbose. A template that creates a single virtual machine with a network interface and public IP address can easily run to 150 lines of deeply nested JSON. Microsoft later introduced Bicep — a domain-specific language that compiles down to ARM JSON — specifically because ARM templates were so painful to write by hand.
Here is what a minimal ARM template snippet looks like — just the structure for creating a storage account:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"defaultValue": "acmestorageprod"
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-02-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot"
}
}
]
}
That is 30 lines of JSON with Azure-specific syntax — [resourceGroup().location], apiVersion, Azure resource type strings — to create one storage account. And like CloudFormation, it only works on Azure.
The Same Thing in Terraform
Here is how Terraform handles the same Azure storage account — using the exact same HCL syntax you already know from the AWS examples:
New terms:
- azurerm provider — the Terraform provider for Azure. "azurerm" stands for Azure Resource Manager. Switching from the AWS provider to the Azure provider changes the resources available — but the HCL syntax and workflow stay identical.
- azurerm_resource_group — in Azure, all resources must belong to a resource group — a logical container for related resources. This is an Azure concept with no direct AWS equivalent.
- azurerm_storage_account — the Terraform resource type for an Azure Storage Account.
- account_replication_type — how Azure replicates your data for durability.
LRSis Locally Redundant Storage — three copies within one datacenter, lowest cost option.
# main.tf — Create an Azure storage account with Terraform
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "main" {
name = "acme-prod-rg"
location = "East US"
}
resource "azurerm_storage_account" "app_storage" {
name = "acmestorageprod"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "LRS"
tags = {
Environment = "prod"
ManagedBy = "Terraform"
}
}
$ terraform apply
Terraform will perform the following actions:
# azurerm_resource_group.main will be created
+ resource "azurerm_resource_group" "main" {
+ location = "eastus"
+ name = "acme-prod-rg"
}
# azurerm_storage_account.app_storage will be created
+ resource "azurerm_storage_account" "app_storage" {
+ account_replication_type = "LRS"
+ account_tier = "Standard"
+ location = "eastus"
+ name = "acmestorageprod"
+ resource_group_name = "acme-prod-rg"
+ tags = {
+ "Environment" = "prod"
+ "ManagedBy" = "Terraform"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
Enter a value: yes
azurerm_resource_group.main: Creating...
azurerm_resource_group.main: Creation complete after 2s
azurerm_storage_account.app_storage: Creating...
azurerm_storage_account.app_storage: Creation complete after 24s
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.What just happened?
- The provider changed from AWS to Azure — but everything else stayed the same. Same
terraform init, sameterraform plan, sameterraform apply. Your workflow did not change at all. - The resource group was created first. The storage account references
azurerm_resource_group.main.nameand.location— so Terraform knows it must create the resource group before the storage account. Dependency order is handled automatically. - No Azure-specific template syntax. No
[parameters()]functions, noapiVersionstrings, no JSON nesting. The same HCL you use for AWS works for Azure, GCP, and everything else.
The Direct Comparison
Here is every major dimension compared side by side:
| Feature | CloudFormation | ARM Templates | Terraform |
|---|---|---|---|
| Works on AWS | Yes | No | Yes |
| Works on Azure | No | Yes | Yes |
| Works on GCP | No | No | Yes |
| Works on Kubernetes | No | No | Yes |
| Language | JSON / YAML | JSON / Bicep | HCL |
| Readability | Medium | Low | High |
| State management | Managed by AWS | Managed by Azure | You manage it |
| Day-0 support for new services | Yes — always first | Yes — always first | Provider update needed |
| Community and modules | Medium | Small | Largest by far |
The Multi-Cloud Problem
This is the argument that ends the debate for most teams. Modern companies rarely live on a single cloud. They might run their main workloads on AWS, their data warehouse on GCP BigQuery, their Active Directory on Azure, and their DNS on Cloudflare. Plus Kubernetes. Plus Datadog for monitoring. Plus GitHub for source control.
With native tools, you need a different tool for each platform — CloudFormation for AWS, ARM for Azure, Deployment Manager for GCP. Each has its own language, its own concepts, its own way of managing state. Your team needs to learn all of them.
With Terraform, one tool manages all of it. The same engineer who provisions your AWS infrastructure can manage your Cloudflare DNS and your GitHub repositories — without switching tools, without learning a new language, without changing workflow.
Terraform manages all of these from a single configuration — same workflow, same language, same four commands
When Native Tools Still Make Sense
Terraform is the right default choice for most teams. But there are specific situations where the native tools have a genuine advantage:
You need brand-new AWS services on day zero
When AWS launches a new service, CloudFormation supports it immediately. Terraform requires the AWS provider to be updated first — which usually happens within days or weeks, but not always on launch day. If you are working on the bleeding edge of AWS and need every new service the moment it lands, CloudFormation has a structural advantage.
You want managed state with zero setup
CloudFormation and ARM store state entirely inside the cloud provider — you never touch a state file. With Terraform, you are responsible for setting up and managing state storage, especially for teams. This is not difficult, but it is an extra step. For very small teams who want zero infrastructure overhead, native tools remove that concern entirely.
Your organisation has deep platform-specific investment
Some organisations have years of existing CloudFormation templates, internal tooling built around it, and entire teams trained on it. Migrating to Terraform has a real cost. If the existing setup works well and the team has no multi-cloud requirements, that migration may not be worth the disruption.
Common Mistakes
Using CloudFormation and Terraform to manage the same resources
Pick one tool per resource and stick to it. If Terraform creates an S3 bucket and a CloudFormation stack also references it, you have two tools fighting over the same resource. State becomes inconsistent and changes become unpredictable. Migration from one tool to another is a deliberate project — not something that happens gradually and informally.
Assuming Terraform always has the latest provider support
Terraform providers are maintained by HashiCorp, cloud vendors, and the community. Coverage is excellent for mature services but can lag on brand-new or niche features. Before committing to Terraform for a specific service, check the provider documentation to confirm the resource type you need actually exists.
The one-line summary
If you are 100% AWS and always will be — CloudFormation is a legitimate choice. If you touch more than one platform, or want the largest ecosystem and the most readable syntax — Terraform is the right answer. Most teams eventually end up in the second category.
Practice Questions
1. What is the name of AWS's native Infrastructure as Code tool?
2. CloudFormation uses JSON or YAML. ARM uses JSON or Bicep. What language does Terraform use?
3. To manage Azure resources with Terraform instead of AWS, you change the ________ block in your configuration.
Quiz
1. What is the single biggest advantage Terraform has over CloudFormation and ARM templates?
2. What is one genuine advantage that CloudFormation and ARM have over Terraform?
3. A team is migrating from CloudFormation to Terraform gradually. Some resources are managed by CloudFormation and others by Terraform. What is the key rule to follow?
Up Next · Lesson 4
Terraform Architecture
You have been using Terraform as a black box. Lesson 4 opens it up — and what is inside explains every behaviour you will encounter from here on.