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 Focus | PowerShell / Ansible (Imperative) | Terraform (Declarative) |
|---|---|---|
| How you express intent | Series of steps/commands | Desired end state (resources + relationships) |
| Idempotence | You build it in (modules/handlers/guards) | Native; engine calculates drift & changes |
| Ordering | You specify order | Terraform builds a plan from dependencies |
| Drift detection | Manual scripts, CMDB/inventory, runbooks | Built-in via plan and state |
| Preview | Dry-run scripts / -WhatIf / --check | terraform plan with precise add/change/destroy |
| Rollback | Re-run scripts to undo | Revert code; plan → apply reconciles |
| Convergence speed | Depends on script quality | Engine parallelizes independent resources |
End-to-End Example: Deploy a VNet with PowerShell (Imperative)
Validate your prerequisites:
- Azure PowerShell module installed
- Logged into your Azure account (
Connect-AzAccount) - 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