Skip to content
Pipelines and Pizza 🍕
Go back

Terraform vs. PowerShell & Ansible: Thinking in Orders not Recipes

4 min read

Thinking in Terraform: Declarative IaC vs. PowerShell & Ansible

Most infrastructure teams come from a process & script mindset: you write steps, run them, and verify results. Terraform flips that on its head. You declare the end state and let the providers plan how to reach it. Today we’ll discuss how to think in Terraform, contrast that with PowerShell and Ansible, and show examples for deploying a simple Azure resource using both approaches.

TL;DR

PowerShell & Ansible: imperative, process-driven. Terraform: declarative, stateful, drift-aware. Terraform + Ansible: A match made in IT heaven.


Imperative vs. Declarative (and why it matters)

  • Imperative (process/script-driven): Do these steps in this order. “Follow this recipe and directions”

    • PowerShell, most Ansible playbooks (even if idempotent), shell scripts.
    • Great for ad-hoc tasks, orchestration, and config changes that require sequencing.
  • Declarative (desired state): This is what I want. Figure out the steps. “I’ll have a mushroom and pepperoni with extra cheese”

    • Example: Terraform.
    • Great for reproducible, reviewable infrastructure changes with plans, state, and drift detection.

Key differences at a glance

Area of FocusPowerShell / Ansible (Imperative)Terraform (Declarative)
How you express intentSeries of steps/commandsDesired end state (resources + relationships)
IdempotenceYou build it in (modules/handlers/guards)Native; engine calculates drift & changes
OrderingYou specify orderTerraform builds a plan from dependencies
Drift detectionManual scripts, CMDB/inventory, runbooksBuilt-in via plan and state
PreviewDry-run scripts / -WhatIf / --checkterraform plan with precise add/change/destroy
RollbackRe-run scripts to undoRevert code; planapply reconciles
Convergence speedDepends on script qualityEngine parallelizes independent resources

End-to-End Example: Deploy a VNet with PowerShell (Imperative)

Validate your prerequisites:

  1. Azure PowerShell module installed
  2. Logged into your Azure account (Connect-AzAccount)
  3. The desired subscription selected

Build your Azure resources

# Set your variables
$location = "eastus"
$rgName   = "rg-example-vnet"
$vnetName = "vnet-example"
$vnetCIDR = "10.1.0.0/16"

# 1) Resource Group
New-AzResourceGroup -Name $rgName -Location $location

New-AzVirtualNetwork `
    -Name $vnetName `
    -ResourceGroupName $rgName `
    -Location $location `
    -AddressPrefix $vnetCIDR

Retire or destroy your Azure resources

# Remove only the virtual network
Remove-AzVirtualNetwork -Name $vnetName -ResourceGroupName $rgName -Force
# Remove the resource group and all contained resources
Remove-AzResourceGroup -Name $rgName -Force

End-to-End Example: Deploy the same VNet with Terraform (Declarative)

providers.tf

terraform {
  required_version = ">= 1.6.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
    }
  }
}
provider "azurerm" { features {} }

main.tf

resource "azurerm_resource_group" "example" {
  name     = "rg-example-vnet"
  location = "eastus"
}

resource "azurerm_virtual_network" "example" {
  name                = "vnet-example"
  address_space       = ["10.1.0.0/16"]
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

Run:

terraform init
terraform plan
terraform apply

Retire or destroy your Azure resources with Terraform

terraform destroy

Config Drift

Scripts: manual detection, custom tooling, extra code. Terraform: state file + terraform plan shows drift.


Project Retirement

If you deploy with PowerShell or Ansible, I hope you document and remember every component, every config, every service account, EVERYTHING that is part of that project. With Terraform, all that is declared and managed in the project’s .tf and state files. A terraform destroy removes almost everything that is part of that project.


Terraform and Ansible: a wonderful partnership

Terraform and Ansible work in a complementary, almost symbiotic way to manage infrastructure and configuration. Terraform excels at provisioning and orchestrating infrastructure across multiple providers in a declarative, immutable fashion, while Ansible shines in configuring and managing the software and services on that infrastructure in an agentless, procedural way. When used together, Terraform handles the “what” and “where” of the infrastructure, and Ansible delivers the “how” of system setup and application deployment, creating a streamlined, end-to-end automation pipeline.

Terraform

  • VM deployment and management
  • Network config
  • DNS records

Ansible

  • Apache installs
  • Monthly server patching
  • AV installs and upgrades
  • Software audits
  • Local admin creds
  • Disk drive cleanups

Bonus: Ansible can consume Terraform outputs and execute on that second step and begin managing day two operations

outputs.tf

output "vm_public_ip" {
  value = azurerm_public_ip.pip.ip_address
}

Export and run with Ansible:

terraform output -json > tf_outputs.json
jq -r '.vm_public_ip.value' tf_outputs.json > hosts.ini
ansible-playbook -i hosts.ini configure.yml