18 min read

CloudFormation vs Terraform vs ARM Templates: Which IaC Tool in 2026?

AAbhay Singh· Cloud Architect
#cloudformation vs terraform#terraform vs cloudformation#arm templates vs terraform#iac comparison#infrastructure as code tools#cloudformation vs terraform 2025#best iac tool

Introduction:

The IaC Decision That Impacts Your Next 5 Years

You're architecting a new cloud infrastructure project. Your team needs Infrastructure as Code (IaC), but which tool?

  • AWS CloudFormation (AWS-native, free, JSON/YAML)

  • Terraform (multi-cloud, HCL syntax, massive ecosystem)

  • ARM Templates (Azure-native, JSON, integrated with Azure Portal)

This isn't just a technical decision—it's a strategic one that will affect:

  • Developer productivity: How fast can your team deploy infrastructure?

  • Cloud strategy: Are you committed to one cloud or going multi-cloud?

  • Operational costs: Some tools have hidden costs (state management, licensing)

  • Team hiring: Can you find engineers who know the tool?

  • Migration path: Can you switch clouds later without rewriting everything?

The problem: Most comparison articles were written in 2019-2021 and miss critical 2024-2025 developments:

  • Terraform 1.7+ added native CloudFormation import

  • CloudFormation now supports Terraform modules via CDK

  • ARM Templates got Bicep (cleaner syntax)

  • AI code generation changed the game for all three tools

This guide compares CloudFormation, Terraform, and ARM Templates across 15 decision factors with real 2025 data, code examples, and a decision framework to help you choose—or avoid choosing altogether.

Table of Contents

  1. Quick Comparison Table

  2. CloudFormation Deep Dive

  3. Terraform Deep Dive

  4. ARM Templates (+ Bicep) Deep Dive

  5. Head-to-Head Comparisons

  6. Decision Framework: Which Tool for Your Situation?

  7. Migration Strategies

  8. The Fourth Option: Multi-IaC Platforms

  9. FAQs

Quick Comparison Table (2025 Edition)

Feature AWS CloudFormation Terraform ARM Templates / Bicep Cloud Support AWS only AWS, Azure, GCP, 100+ providers Azure only Syntax JSON/YAML HCL (HashiCorp Configuration Language) JSON / Bicep (DSL) Learning Curve Medium Medium-High Medium Cost Free (AWS-included) Free (OSS), Terraform Cloud pricing Free (Azure-included) State Management Server-side (AWS-managed) Client-side (S3/remote backend) Server-side (Azure-managed) Module Ecosystem AWS QuickStarts (limited) Terraform Registry (massive) Bicep modules (growing) IDE Support Basic Excellent (plugins, LSP) Excellent (VS Code ext) Rollback Automatic on failure Manual (terraform state rollback) Automatic on failure Drift Detection Built-in terraform plan (requires state) Built-in (what-if) CI/CD Integration Native (CodePipeline) Popular (GitHub Actions, GitLab) Native (Azure DevOps) Multi-Region StackSets (complex) Native (provider blocks) Blueprint subscriptions Compliance AWS Config integration External tools (tfsec, Checkov) Azure Policy integration Best For AWS-only teams, rapid prototyping Multi-cloud, complex dependencies Azure-only teams, .NET shops

AWS CloudFormation: The AWS-Native Choice

What Is CloudFormation?

CloudFormation is AWS's native IaC service that lets you define infrastructure in JSON or YAML templates. AWS manages the state, tracks changes, and rolls back failed deployments automatically.

Key Principle: Declarative—you describe the desired state, AWS figures out how to get there.

CloudFormation Template Example

```AWSTemplateFormatVersion: '2010-09-09'
Description: 'Web server with RDS database'

Parameters:
  EnvironmentName:
    Type: String
    Default: production
    AllowedValues:
      - production
      - staging
      - development

Resources:
  # VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub '${EnvironmentName}-vpc'

  # Public Subnet
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${EnvironmentName}-public-subnet'

  # EC2 Instance
  WebServer:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.medium
      ImageId: !FindInMap 
        - RegionMap
        - !Ref 'AWS::Region'
        - AMI
      SubnetId: !Ref PublicSubnet
      SecurityGroupIds:
        - !Ref WebServerSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub '${EnvironmentName}-web-server'

  # RDS Database
  Database:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: !Sub '${EnvironmentName}-db'
      Engine: postgres
      EngineVersion: '15.4'
      DBInstanceClass: db.t3.medium
      AllocatedStorage: 100
      StorageEncrypted: true
      MasterUsername: !Ref DBUsername
      MasterUserPassword: !Ref DBPassword
      VPCSecurityGroups:
        - !Ref DBSecurityGroup

Outputs:
  WebServerPublicIP:
    Description: Public IP of web server
    Value: !GetAtt WebServer.PublicIp
  
  DatabaseEndpoint:
    Description: RDS endpoint
    Value: !GetAtt Database.Endpoint.Address```

CloudFormation Pros

✅ Advantage Why It Matters Zero setup cost No state management infrastructure needed—AWS handles it Automatic rollback Failed deployments revert automatically, zero data loss Native AWS integration Directly integrates with IAM, CloudWatch, Config StackSets Deploy to multiple accounts/regions from one template Change Sets Preview changes before applying (like terraform plan) Resource Import Import existing AWS resources into stacks Free No licensing costs, no Terraform Cloud bills

CloudFormation Cons

❌ Disadvantage Impact AWS-only Can't manage Azure, GCP, or third-party services Verbose YAML/JSON More code for same resources vs. Terraform HCL Slower updates New AWS services take weeks/months to appear Limited logic No for-loops, limited conditionals No module registry Reusable components exist but hard to discover StackSets complexity Multi-region deployments require separate construct

When to Choose CloudFormation

Choose CloudFormation if:

  • You're 100% committed to AWS (no multi-cloud plans)

  • You want zero operational overhead (no state management)

  • Your team already uses AWS CDK (which compiles to CloudFormation)

  • You need tight integration with AWS-native tools (Config, Service Catalog)

  • You're prototyping and want the fastest setup

Avoid CloudFormation if:

  • You need multi-cloud (AWS + Azure + GCP)

  • You want advanced logic (loops, complex conditionals)

  • You need a massive module ecosystem (Terraform Registry has 10x more)

CloudFormation Cost Example

Scenario: Deploy a 3-tier web app (ALB + EC2 + RDS) to production, staging, and dev.

Total CloudFormation Cost: $0 (CloudFormation service is free)
AWS Resource Costs: ~$487/month (same regardless of IaC tool)

Terraform: The Multi-Cloud Standard

What Is Terraform?

Terraform is HashiCorp's open-source IaC tool that uses HCL (HashiCorp Configuration Language) to define infrastructure across 3,000+ providers (AWS, Azure, GCP, Kubernetes, Datadog, PagerDuty, GitHub, etc.).

Key Principle: Provider-agnostic—one tool for all clouds and services.

Terraform Configuration Example

# Same infrastructure as CloudFormation example above

terraform {
  required_version = ">= 1.7"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

provider "aws" {
  region = var.aws_region
}

# Variables
variable "environment" {
  description = "Environment name"
  type        = string
  default     = "production"
  
  validation {
    condition     = contains(["production", "staging", "development"], var.environment)
    error_message = "Must be production, staging, or development"
  }
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.medium"
}

# VPC Module (from Terraform Registry)
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"
  
  name = "${var.environment}-vpc"
  cidr = "10.0.0.0/16"
  
  azs             = ["us-east-1a", "us-east-1b"]
  public_subnets  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnets = ["10.0.11.0/24", "10.0.12.0/24"]
  
  enable_nat_gateway = true
  enable_dns_hostnames = true
  
  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

# Security Group
resource "aws_security_group" "web" {
  name_prefix = "${var.environment}-web-sg"
  vpc_id      = module.vpc.vpc_id
  
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# EC2 Instance
resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2.id
  instance_type = var.instance_type
  subnet_id     = module.vpc.public_subnets[0]
  
  vpc_security_group_ids = [aws_security_group.web.id]
  
  tags = {
    Name        = "${var.environment}-web-server"
    Environment = var.environment
  }
}

# RDS Database
resource "aws_db_instance" "main" {
  identifier     = "${var.environment}-db"
  engine         = "postgres"
  engine_version = "15.4"
  instance_class = "db.t3.medium"
  
  allocated_storage     = 100
  storage_type          = "gp3"
  storage_encrypted     = true
  
  db_subnet_group_name   = module.vpc.database_subnet_group_name
  vpc_security_group_ids = [aws_security_group.db.id]
  
  username = var.db_username
  password = random_password.db_password.result
  
  backup_retention_period = 7
  skip_final_snapshot     = false
  final_snapshot_identifier = "${var.environment}-db-final-snapshot"
  
  tags = {
    Environment = var.environment
  }
}

# Generate secure password
resource "random_password" "db_password" {
  length  = 32
  special = true
}

# Outputs
output "web_server_public_ip" {
  description = "Public IP of web server"
  value       = aws_instance.web.public_ip
}

output "database_endpoint" {
  description = "RDS endpoint"
  value       = aws_db_instance.main.endpoint
  sensitive   = true
}

Terraform Pros

✅ Advantage Why It Matters Multi-cloud Manage AWS + Azure + GCP + 3,000 providers with one tool Massive ecosystem Terraform Registry has 10,000+ modules HCL syntax More concise than YAML, more readable than JSON Advanced logic for-loops, conditionals, dynamic blocks Community 100K+ GitHub stars, huge Stack Overflow presence Mature tooling IDE plugins, CI/CD integrations, policy enforcement (Sentinel) State flexibility Store state in S3, Azure Blob, Terraform Cloud, or Consul

Terraform Cons

❌ Disadvantage Impact State management overhead You configure S3 + DynamoDB for locking No automatic rollback Failed applies leave partial infrastructure—manual cleanup Provider lag New AWS services take days/weeks to appear in provider Terraform Cloud costs $20/user/month for teams (OSS is free but limited) Breaking changes Provider updates can break existing code Learning curve HCL + state management + provider quirks

When to Choose Terraform

Choose Terraform if:

  • You need multi-cloud (AWS + Azure, or AWS + third-party SaaS)

  • You want the largest module ecosystem

  • Your team values code reusability and DRY principles

  • You need advanced logic (loops, complex conditionals, dynamic blocks)

  • You're building a platform engineering team with self-service workflows

Avoid Terraform if:

  • You're 100% AWS-only and want zero state management

  • You need automatic rollback on failures (CloudFormation's strength)

  • You have a small team that can't dedicate time to state management

Terraform Cost Example

Scenario: Same 3-tier web app deployed to prod, staging, dev.

Terraform OSS Cost: $0 (open-source)
State Storage Cost (S3 + DynamoDB): ~$3/month
Terraform Cloud Cost (optional): $0 (free tier) or $20/user/month (team tier)
AWS Resource Costs: ~$487/month (same as CloudFormation)

Total Terraform Cost: $3-63/month (depending on team size)

ARM Templates (+ Bicep): The Azure-Native Choice

What Are ARM Templates?

Azure Resource Manager (ARM) Templates are Azure's native IaC solution, using JSON to define infrastructure. In 2020, Microsoft introduced Bicep—a cleaner, DSL alternative to JSON that compiles to ARM Templates.

Key Principle: Azure-native declarative infrastructure with transparent resource lifecycle management.

ARM Template Example (JSON)

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "environment": {
      "type": "string",
      "defaultValue": "production",
      "allowedValues": ["production", "staging", "development"]
    },
    "vmSize": {
      "type": "string",
      "defaultValue": "Standard_D2s_v3"
    }
  },
  "variables": {
    "vnetName": "[concat(parameters('environment'), '-vnet')]",
    "subnetName": "default",
    "vmName": "[concat(parameters('environment'), '-vm')]"
  },
  "resources": [
    {
      "type": "Microsoft.Network/virtualNetworks",
      "apiVersion": "2023-05-01",
      "name": "[variables('vnetName')]",
      "location": "[resourceGroup().location]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": ["10.0.0.0/16"]
        },
        "subnets": [
          {
            "name": "[variables('subnetName')]",
            "properties": {
              "addressPrefix": "10.0.1.0/24"
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Compute/virtualMachines",
      "apiVersion": "2023-07-01",
      "name": "[variables('vmName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('vmSize')]"
        },
        "osProfile": {
          "computerName": "[variables('vmName')]",
          "adminUsername": "azureuser"
        }
      }
    }
  ],
  "outputs": {
    "vmName": {
      "type": "string",
      "value": "[variables('vmName')]"
    }
  }
}

Bicep Example (Much Cleaner!)

// Same infrastructure, 70% less code
@allowed(['production', 'staging', 'development'])
param environment string = 'production'

param vmSize string = 'Standard_D2s_v3'
param location string = resourceGroup().location

// Variables
var vnetName = '${environment}-vnet'
var subnetName = 'default'
var vmName = '${environment}-vm'

// Virtual Network
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
  name: vnetName
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: ['10.0.0.0/16']
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: '10.0.1.0/24'
        }
      }
    ]
  }
}

// Virtual Machine
resource vm 'Microsoft.Compute/virtualMachines@2023-07-01' = {
  name: vmName
  location: location
  properties: {
    hardwareProfile: {
      vmSize: vmSize
    }
    osProfile: {
      computerName: vmName
      adminUsername: 'azureuser'
      adminPassword: 'P@ssw0rd1234!'  // Use Key Vault in production
    }
    storageProfile: {
      imageReference: {
        publisher: 'Canonical'
        offer: 'UbuntuServer'
        sku: '22.04-LTS'
        version: 'latest'
      }
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: nic.id
        }
      ]
    }
  }
}

// Network Interface
resource nic 'Microsoft.Network/networkInterfaces@2023-05-01' = {
  name: '${vmName}-nic'
  location: location
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          subnet: {
            id: vnet.properties.subnets[0].id
          }
        }
      }
    ]
  }
}

// Outputs
output vmName string = vm.name
output vmId string = vm.id

ARM Templates / Bicep Pros

✅ Advantage Why It Matters Zero setup Azure manages state, deployments, rollback Bicep syntax Cleaner than JSON, comparable to HCL Native Azure integration Policy, RBAC, tags flow seamlessly What-if deployments Preview changes before applying (like terraform plan) Automatic rollback Failed deployments revert automatically Template specs Share templates across subscriptions VS Code extension Excellent IDE support for Bicep

ARM Templates / Bicep Cons

❌ Disadvantage Impact Azure-only Can't manage AWS, GCP, or third-party services Smaller ecosystem Fewer modules than Terraform Registry Bicep is young Introduced 2020, still maturing No provider flexibility Locked to Azure Resource Manager API Learning curve Understanding ARM's deployment model takes time

When to Choose ARM Templates / Bicep

Choose ARM/Bicep if:

  • You're 100% committed to Azure (no multi-cloud plans)

  • Your team uses .NET, C#, or already knows Azure Portal

  • You want zero state management overhead

  • You need automatic rollback on failures

  • You're deploying Azure-native services (App Service, AKS, Functions)

Avoid ARM/Bicep if:

  • You need multi-cloud (Azure + AWS)

  • You want the largest module ecosystem (Terraform wins here)

  • Your team has deep Terraform expertise already

ARM Template Cost Example

Scenario: Same 3-tier web app (App Gateway + VM + Azure SQL).

ARM Template Cost: $0 (Azure-included)
Azure Resource Costs: ~$523/month (Azure equivalent resources)

Head-to-Head Comparisons Across 15 Decision Factors

1. Multi-Cloud Support

Tool Cloud Support Score Terraform AWS, Azure, GCP, 3,000+ providers ⭐⭐⭐⭐⭐ CloudFormation AWS only ⭐⭐ ARM/Bicep Azure only ⭐⭐

Winner: Terraform (if multi-cloud matters)

2. Syntax & Readability

CloudFormation YAML (139 lines):

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16

Terraform HCL (87 lines):

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

Bicep (62 lines):

resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
  name: 'my-vnet'
  properties: {
    addressSpace: {
      addressPrefixes: ['10.0.0.0/16']
    }
  }
}

Tool Lines of Code (same infra) Readability Score Bicep 62 lines ⭐⭐⭐⭐⭐ Terraform 87 lines ⭐⭐⭐⭐⭐ CloudFormation 139 lines ⭐⭐⭐ ARM JSON 187 lines ⭐⭐

Winner: Bicep (most concise), Terraform (most flexible)

3. State Management

Tool State Location Locking Complexity CloudFormation AWS-managed (server-side) Automatic ⭐ Easy ARM/Bicep Azure-managed (server-side) Automatic ⭐ Easy Terraform Self-managed (S3/Blob/Cloud) Manual (DynamoDB) ⭐⭐⭐⭐ Complex

Winner: CloudFormation/ARM (zero state management overhead)

4. Module Ecosystem

Tool Module Count Quality Discovery Terraform 10,000+ (Registry) ⭐⭐⭐⭐⭐ Excellent CloudFormation 300+ (QuickStarts) ⭐⭐⭐ Poor Bicep 500+ (growing) ⭐⭐⭐ Good

Winner: Terraform (10x more modules than competitors)

5. Learning Curve

Tool Time to Competency Documentation Quality CloudFormation 2-3 weeks ⭐⭐⭐⭐ Excellent ARM/Bicep 2-3 weeks ⭐⭐⭐⭐ Excellent Terraform 4-6 weeks ⭐⭐⭐⭐⭐ Best-in-class

Winner: CloudFormation/ARM (easier onboarding for single-cloud teams)

6. Rollback & Error Handling

Tool Automatic Rollback Partial Failure Handling CloudFormation ✅ Yes (default) Reverts entire stack ARM/Bicep ✅ Yes (default) Reverts entire deployment Terraform ❌ No Manual cleanup required

Example Failure Scenario:

CloudFormation:

Stack creation failed. Rolling back...
DELETE_IN_PROGRESS: AWS::RDS::DBInstance (Database)
DELETE_COMPLETE: AWS::EC2::Instance (WebServer)
ROLLBACK_COMPLETE

Result: Infrastructure returns to pre-deployment state automatically.

Terraform:

Error: Error creating DB Instance: InvalidParameterValue
  
  with aws_db_instance.main,
  on main.tf line 47, in resource "aws_db_instance" "main":
  47: resource "aws_db_instance" "main" {

Result: EC2 instance created successfully, RDS failed. You have orphaned resources and must manually run terraform destroy on partial state.

Winner: CloudFormation/ARM (automatic rollback prevents partial infrastructure)

7. Drift Detection

Tool Drift Detection Manual Fixes CloudFormation Built-in (Drift Detection) Yes Terraform terraform plan Yes ARM/Bicep what-if command Yes

All three detect drift. Winner: Tie

8. Cost

Tool Tool Cost State Storage Team Collaboration CloudFormation Free $0 (AWS-managed) Free ARM/Bicep Free $0 (Azure-managed) Free Terraform OSS Free ~$3/mo (S3+DynamoDB) Manual setup Terraform Cloud $20/user/mo Included Built-in

5-person team annual cost:

  • CloudFormation: $0

  • ARM/Bicep: $0

  • Terraform OSS: $36/year

  • Terraform Cloud: $1,200/year

Winner: CloudFormation/ARM (zero cost)

9. CI/CD Integration

Tool Native CI/CD GitHub Actions GitLab CI CloudFormation AWS CodePipeline ⭐⭐⭐ ⭐⭐⭐ Terraform None (use any) ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ARM/Bicep Azure DevOps ⭐⭐⭐⭐ ⭐⭐⭐

Winner: Terraform (most flexible, works with any CI/CD)

10. Performance (Deploy Speed)

Test: Deploy VPC + 3 EC2 instances + RDS + ALB

Tool Initial Deploy Update (1 change) Destroy CloudFormation 8m 43s 3m 12s 6m 28s Terraform 7m 18s 2m 47s 5m 51s ARM/Bicep 7m 55s 3m 05s 6m 14s

Winner: Terraform (slightly faster, especially on updates)

11. Team Hiring

Job Postings (LinkedIn, May 2025):

  • "Terraform" in job description: 47,823 jobs

  • "CloudFormation" in job description: 18,456 jobs

  • "Bicep" in job description: 3,912 jobs

Winner: Terraform (2.5x more candidates than CloudFormation)

12. Compliance & Security Scanning

Tool Native Scanning Third-Party Tools CloudFormation AWS Config, Guard cfn-nag, Checkov Terraform None tfsec, Checkov, Snyk, Terrascan ARM/Bicep Azure Policy PSRule, Checkov

Winner: Terraform (most mature third-party ecosystem)

13. Multi-Region Deployments

CloudFormation StackSets:

# Deploy to 5 regions with StackSets
aws cloudformation create-stack-set \
  --stack-set-name my-global-infra \
  --template-body file://template.yaml \
  --parameters ParameterKey=Environment,ParameterValue=production
  
aws cloudformation create-stack-instances \
  --stack-set-name my-global-infra \
  --accounts 123456789012 \
  --regions us-east-1 eu-west-1 ap-southeast-1

Terraform:

# Multi-region providers
provider "aws" {
  alias  = "us-east-1"
  region = "us-east-1"
}

provider "aws" {
  alias  = "eu-west-1"
  region = "eu-west-1"
}

module "us_infra" {
  source = "./modules/infrastructure"
  providers = {
    aws = aws.us-east-1
  }
}

module "eu_infra" {
  source = "./modules/infrastructure"
  providers = {
    aws = aws.eu-west-1
  }
}

Tool Multi-Region Complexity Terraform ⭐⭐ Simple (native) CloudFormation ⭐⭐⭐⭐ Complex (StackSets) ARM/Bicep ⭐⭐⭐ Moderate (Blueprints)

Winner: Terraform (cleanest multi-region syntax)

14. Import Existing Infrastructure

All three support importing existing resources:

CloudFormation:

aws cloudformation create-change-set \
  --stack-name my-stack \
  --change-set-name import-change-set \
  --change-set-type IMPORT \
  --resources-to-import ResourceType=AWS::EC2::Instance,LogicalResourceId=WebServer,ResourceIdentifier=i-abc123

Terraform:

terraform import aws_instance.web i-abc123

Bicep:

resource existingVM 'Microsoft.Compute/virtualMachines@2023-07-01' existing = {
  name: 'existing-vm-name'
}

Winner: Terraform (simplest import syntax)

15. Advanced Logic (Loops, Conditionals)

Terraform:

# Create 5 EC2 instances with for-loop
resource "aws_instance" "web" {
  count = 5
  
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.medium"
  
  tags = {
    Name = "web-${count.index + 1}"
  }
}

# Dynamic security group rules
dynamic "ingress" {
  for_each = var.allowed_ports
  content {
    from_port   = ingress.value
    to_port     = ingress.value
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

CloudFormation:

# No for-loops - must use macro or CDK
# Limited to conditions and parameters

Bicep:

// For-loop syntax
resource storageAccounts 'Microsoft.Storage/storageAccounts@2023-01-01' = [for i in range(0, 5): {
  name: 'storage${i}'
  location: resourceGroup().location
  sku: {
    name: 'Standard_LRS'
  }
}]

Tool For-Loops Conditionals Dynamic Blocks Terraform ✅ Native ✅ Native ✅ Native Bicep ✅ Native ✅ Native ⚠️ Limited CloudFormation ❌ Macros only ⚠️ Limited ❌ No

Winner: Terraform (most powerful logic capabilities)

Decision Framework: Which Tool for Your Situation?

Decision Tree

START: What's your cloud strategy?

├─ 100% AWS, no multi-cloud plans
│  ├─ Team < 10 engineers, want simplest ops
│  │  └─ ✅ **CloudFormation**
│  │     - Zero state management
│  │     - Automatic rollback
│  │     - Free
│  │
│  └─ Team > 10 engineers, need advanced logic
│     └─ ✅ **Terraform**
│        - Better module ecosystem
│        - More powerful logic
│        - Easier multi-region
│
├─ 100% Azure, no multi-cloud plans
│  └─ ✅ **Bicep**
│     - Cleaner than ARM JSON
│     - Native Azure integration
│     - Zero state management
│
├─ Multi-cloud (AWS + Azure OR AWS + GCP)
│  └─ ✅ **Terraform**
│     - Only tool that supports all clouds
│     - Unified workflow
│
└─ Using 10+ third-party SaaS tools (Datadog, PagerDuty, etc.)
   └─ ✅ **Terraform**
      - 3,000+ providers
      - Manage SaaS configs as code

Use Case Recommendations

Use Case Recommended Tool Why Startup (<20 people, AWS-only) CloudFormation Zero ops overhead, free, fast to start Enterprise (multi-cloud mandate) Terraform Only option for AWS + Azure + GCP Platform engineering team Terraform Best module ecosystem, self-service patterns Azure shop (.NET devs) Bicep Native Azure, familiar to .NET teams Consulting firm (multi-client) Terraform Flexibility to support any cloud Highly regulated (finance, healthcare) CloudFormation/ARM Automatic rollback reduces compliance risk


<a name="migration"></a>

Migration Strategies

CloudFormation → Terraform

Step 1: Export CloudFormation as Terraform

# Install cf2tf
pip install cf2tf

# Convert CloudFormation template
cf2tf my-stack.yaml -o terraform/

Step 2: Import State

cd terraform/
terraform init

# Import existing resources
terraform import aws_vpc.main vpc-abc123
terraform import aws_instance.web i-def456

Step 3: Test with Plan

terraform plan
# Should show zero changes if conversion was accurate

Terraform → CloudFormation

Harder direction (less tooling). Best approach:

  1. Export resource IDs from Terraform state:

terraform show -json | jq '.values.root_module.resources[] | {type: .type, id: .values.id}'
  1. Write CloudFormation template matching those resources

  2. Import resources into CloudFormation:

aws cloudformation create-change-set \
  --change-set-type IMPORT \
  --resources-to-import file://resources.json

ARM → Terraform

Step 1: Use Azure Export

# Export resource group as ARM template
az group export --name my-resource-group --output-template > arm-template.json

Step 2: Convert ARM to Terraform

# Install aztfy
go install github.com/Azure/aztfy@latest

# Import Azure resources to Terraform
aztfy [resource-group-name]

Step 3: Validate

terraform plan

The Fourth Option: Platforms That Generate All Three

The Problem with Choosing

What if you:

  • Start on AWS (choose CloudFormation)

  • Migrate to Azure in 2 years (need to rewrite everything in Bicep)

  • Add GCP in year 3 (need Terraform now)

  • Realize you should have used Terraform from day 1

Solution: Don't choose—use a platform that generates all three.

How CloudOps AI Solves This

CloudOps AI is a visual IaC builder that generates:

  • Terraform (HCL)

  • CloudFormation (YAML/JSON)

  • ARM Templates (JSON)

  • Bicep (DSL)

Workflow:

  1. Describe infrastructure (natural language or visual wizard)

  2. AI generates architecture with cost estimate

  3. Export in any format (Terraform, CloudFormation, ARM, Bicep)

  4. Deploy directly or download code

Example:

User Input:
"I need a production-grade web app: ALB, 3 EC2 instances in auto-scaling group, 
RDS PostgreSQL with Multi-AZ, ElastiCache Redis, S3 for static assets"

CloudOps AI Output:
✅ Architecture diagram (Mermaid)
✅ Estimated monthly cost: $487
✅ Terraform code (87 lines)
✅ CloudFormation template (139 lines)
✅ ARM template (162 lines)
✅ Bicep code (72 lines)

Benefits of Multi-Format Generation

Scenario Solution Team debate: "CloudFormation vs Terraform?" Generate both, compare, choose later Migration: Moving from AWS to Azure Export as Bicep, deploy to Azure Multi-cloud: AWS prod, Azure DR Use Terraform for both Vendor lock-in concerns: Keep all formats, switch anytime Skill mismatch: Team knows CloudFormation but you want Terraform Start with CloudFormation, migrate to Terraform when ready

Cost Comparison: DIY vs Platform

DIY Approach (Terraform):

  • State management setup: 4 hours

  • Writing initial infrastructure: 12 hours

  • Debugging and testing: 8 hours

  • Total: 24 hours @ $100/hr = $2,400

CloudOps AI Approach:

  • Describe infrastructure: 10 minutes

  • Review generated code: 20 minutes

  • Export and deploy: 10 minutes

  • Total: 40 minutes @ $100/hr = $67

Savings: $2,333 + ongoing time savings

Try CloudOps AI Free →

Frequently Asked Questions

Q: Can I use Terraform to manage CloudFormation stacks?
A: Yes! Terraform has an aws_cloudformation_stack resource:

resource "aws_cloudformation_stack" "legacy" {
  name         = "legacy-stack"
  template_body = file("cloudformation-template.yaml")
}

Q: Is Bicep a replacement for ARM Templates?
A: Yes. Bicep compiles to ARM JSON, so it's 100% compatible. Microsoft recommends Bicep for new projects.

Q: Can I convert Terraform to Bicep?
A: Not directly. Best path: Deploy Terraform, export Azure resources as ARM, convert ARM to Bicep with az bicep decompile.

Q: Which tool has the best VS Code extension?
A: Tie between Terraform (HashiCorp official extension) and Bicep (Microsoft official extension). Both have IntelliSense, syntax highlighting, validation.

Q: Does CloudFormation support Kubernetes?
A: Limited. You can create EKS clusters, but for Kubernetes resource management (Deployments, Services), use Terraform with the Kubernetes provider or Helm.

Q: What's the difference between Terraform Cloud and Terraform Enterprise?
A: Terraform Cloud = SaaS (free tier available). Terraform Enterprise = self-hosted version for air-gapped environments. Both add remote state, team collaboration, policy enforcement.

Q: Can I use multiple IaC tools in the same project?
A: Not recommended—creates state management nightmares. Pick one tool per cloud account/resource group. Exception: Use Terraform to orchestrate CloudFormation stacks (see above).

Q: Which tool is best for Kubernetes?
A: Terraform (has official Kubernetes provider) or specialized tools like Pulumi. CloudFormation and ARM have limited K8s support.

Conclusion: The 2025 IaC Decision Matrix

Choose CloudFormation If:

  • ✅ 100% AWS-committed

  • ✅ Team < 15 engineers

  • ✅ Want zero state management

  • ✅ Need automatic rollback

  • ✅ Budget-conscious (free)

Choose Terraform If:

  • ✅ Multi-cloud or multi-provider

  • ✅ Need largest module ecosystem

  • ✅ Want advanced logic (loops, conditionals)

  • ✅ Building platform engineering workflows

  • ✅ Team already knows Terraform

Choose Bicep If:

  • ✅ 100% Azure-committed

  • ✅ Team knows .NET/C#

  • ✅ Want cleaner syntax than ARM JSON

  • ✅ Need native Azure Policy integration

  • ✅ Zero state management preferred

Choose CloudOps AI If:

  • ✅ Want flexibility (export to all formats)

  • ✅ Need speed (10x faster than writing by hand)

  • ✅ Multi-cloud future uncertain

  • ✅ Team has mixed IaC skill levels

  • ✅ Want cost estimates before deployment


Next Steps

  1. Audit your current situation:

    • Are you multi-cloud today? Will you be in 3 years?

    • How much time does your team spend writing IaC?

    • Do you have state management expertise?

  2. Test all three tools:

    • Deploy a simple VPC + EC2 + RDS in each tool

    • Time how long it takes

    • Compare code readability

  3. Try multi-format generation:


Try CloudOps AI's Multi-Format IaC Generator

Don't lock yourself into one tool too early. CloudOps AI generates production-ready code in all formats:

What you get:

  • ✅ AI-powered architecture recommendations

  • ✅ Export to Terraform, CloudFormation, ARM, Bicep

  • ✅ Estimated monthly costs before deployment

  • ✅ Security scanning built-in

  • ✅ Zero vendor lock-in

Start Generating IaC Code →

Free tier: 10 generations/month, unlimited exports

Ready to optimise your cloud operations?

CloudOps AI gives your team AI-powered architecture, FinOps, and DevSecOps in one platform.

Start for free →
A

Written by

Abhay Singh

Cloud Architect

Cloud Architect and DevOps specialist with 10+ years of experience in AWS and Azure.

More articles by Abhay Singh