Infrastructure as Code: Terraform, Ansible & Pulumi Guide
Infrastructure as Code (IaC) is the practice of managing infrastructure — servers, networks, databases, and configuration — through machine-readable definition files rather than manual processes. Instead of SSHing into a server to install packages or clicking through a cloud console to provision resources, you write code that defines the desired state and let tools apply it. This transformation, pioneered by companies like HashiCorp, Chef, and Puppet, has become the foundation of modern DevOps practice. According to the 2024 Stack Overflow Developer Survey, over 65% of professional developers now use some form of IaC in their workflows, and the market for IaC tools is projected to exceed $3.5 billion by 2028.
Why Infrastructure as Code?
The benefits of IaC extend far beyond convenience. Organizations that adopt IaC report 40% fewer deployment failures and 60% faster environment provisioning compared to manual processes, according to the 2024 State of CloudOps Report.
- Repeatability — the same code produces the same infrastructure every time, eliminating manual errors and the snowflake server problem
- Version control — infrastructure changes go through code review, just like application code, with full audit trail
- Auditability — every change is tracked in Git with author, timestamp, and diff
- Automation — provisioning and configuration happen without manual intervention, enabling self-service environments that developers can spin up in minutes
- Consistency — no more “works on my machine” for infrastructure; development, staging, and production environments are defined identically
Declarative vs Imperative IaC
# Imperative: "How to do it" — step by step
gcloud compute instances create web-server \
--zone=us-central1-a \
--machine-type=e2-medium \
--image=ubuntu-2204
# Declarative: "What I want" — desired state (Terraform)
resource "google_compute_instance" "web" {
name = "web-server"
zone = "us-central1-a"
machine_type = "e2-medium"
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2204-lts"
}
}
---Declarative IaC describes the desired end state. The tool figures out how to reach that state from the current state, including what to create, update, or destroy. Imperative IaC describes the exact steps, which means the same script may behave differently depending on the starting state. Declarative approaches reduce the risk of unexpected side effects because the tool computes the minimal changes needed.
Terraform
Terraform is the most widely adopted declarative IaC tool for provisioning infrastructure across any cloud provider. It uses HashiCorp Configuration Language (HCL) and maintains state to track managed resources. Terraform was the first tool to popularize the concept of infrastructure provisioning as declarative code, and its provider ecosystem now covers over 3,000 infrastructure services.
Core Concepts
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
---
provider "aws" {
region = "us-west-2"
---
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "WebServer"
Env = "production"
}
---Variables and Outputs
variable "environment" {
description = "Deployment environment"
type = string
default = "development"
validation {
condition = contains(["development", "staging", "production"], var.environment)
error_message = "Must be one of: development, staging, production."
}
---
output "instance_ip" {
value = aws_instance.web.public_ip
description = "Public IP of web server"
---Terraform Workflow
terraform init # Download providers and modules
terraform plan # Show what will change
terraform apply # Provision infrastructure
terraform destroy # Tear down everything
terraform fmt # Format code
terraform validate # Validate syntaxState Management
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
---State files map Terraform configuration to real-world resources. Always use remote state with locking for team collaboration. Without locking, two team members applying changes simultaneously can corrupt the state file. Terraform Cloud and Terraform Enterprise provide managed state backends with policy enforcement and collaboration features.
Terraform Modules
Modules encapsulate reusable infrastructure patterns:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
---The Terraform Registry hosts over 7,000 modules published by HashiCorp, partners, and the community. Using well-tested modules reduces boilerplate and incorporates best practices for security and high availability.
Ansible
Ansible is a configuration management and automation tool. It uses push-based, agentless architecture over SSH, making it the simplest IaC tool to adopt for server configuration. While Terraform provisions infrastructure, Ansible configures it — the complementary pairing is the most common production IaC pattern.
Inventory
[webservers]
web1.example.com
web2.example.com
[database]
db1.example.com
[production:children]
webservers
databasePlaybooks
---
- name: Configure web servers
hosts: webservers
become: yes
vars:
nginx_port: 80
tasks:
- name: Install nginx
apt:
name: nginx
state: present
- name: Configure nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
handlers:
- name: restart nginx
service:
name: nginx
state: restartedAd-Hoc Commands
ansible all -i inventory.ini -m ping
ansible webservers -i inventory.ini -a "uptime"
ansible-playbook -i inventory.ini site.yml --checkPulumi
Pulumi lets you write IaC using general-purpose programming languages, which unlocks loops, conditionals, functions, and type safety:
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
const environment = config.require("environment");
const securityGroup = new aws.ec2.SecurityGroup("web-sg", {
ingress: [
{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
],
egress: [
{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] },
],
tags: { Name: "web-sg", Environment: environment },
---);
const instance = new aws.ec2.Instance("web", {
instanceType: "t3.micro",
ami: aws.ec2.getAmi({ ... }).then(a => a.id),
vpcSecurityGroupIds: [securityGroup.id],
tags: { Name: "WebServer", Environment: environment },
---);
export const publicIp = instance.publicIp;Pulumi supports TypeScript, Python, Go, C#, Java, and YAML. This language flexibility makes it particularly attractive for teams that want to reuse existing testing frameworks, IDE features, and package ecosystems.
Comparison
| Feature | Terraform | Ansible | Pulumi |
|---|---|---|---|
| Language | HCL | YAML | TypeScript, Python, Go, C# |
| Paradigm | Declarative | Declarative | Imperative + Declarative |
| Agent | State-based | SSH (agentless) | State-based |
| Provisioning | Excellent | Limited | Excellent |
| Configuration Mgmt | No | Excellent | No |
| State Management | Required | Not required | Required |
| Cloud Support | All major clouds | Via modules | All major clouds |
IaC Testing and Validation
Testing infrastructure code is as important as testing application code. Each IaC tool provides validation mechanisms that should be integrated into CI/CD pipelines.
Terraform Validation
# Syntax and configuration validation
terraform validate
# Policy as code with Sentinel or OPA
terraform plan -out=tfplan
terraform show -json tfplan | opa eval --data policies -i
# Compliance testing with Terratest
go test -v -run TestTerraformAwsBasicAnsible Validation
# Syntax check
ansible-playbook site.yml --syntax-check
# Dry run
ansible-playbook site.yml --check
# Diff mode shows what changes
ansible-playbook site.yml --check --diff
# Idempotency test
ansible-playbook site.yml
ansible-playbook site.yml | grep -c "changed="Pulumi Preview
# Preview changes before applying
pulumi preview
# Policy as code enforcement
pulumi policy publishIaC Security Scanning
# Scan Terraform code for security issues
checkov -d terraform/
tfsec terraform/
# Scan Ansible playbooks
ansible-lint site.ymlTools like Checkov, tfsec, and Terrascan analyze IaC files for misconfigurations — open security groups, unencrypted storage, IAM policy violations — before they reach production. Integrate these scans into CI/CD pipelines as mandatory gates.
IaC and GitOps Workflow
GitOps extends IaC principles by using Git as the single source of truth for declarative infrastructure and application configuration. Changes are made via pull requests, and a reconciliation operator (Argo CD, Flux) automatically applies the desired state from Git to the infrastructure.
Key GitOps principles:
- Declarative description — the entire system is described declaratively in Git
- Automated convergence — a controller continuously reconciles actual state with the desired state in Git
- Pull-based deployment — the operator pulls from Git rather than pushing from CI/CD
- Observability — drift from the desired state is detected and reported
GitOps is particularly popular in Kubernetes environments but applies to any infrastructure that can be described declaratively. Tools like Crossplane extend GitOps to cloud infrastructure beyond Kubernetes.
FAQ
Should I use Terraform or Ansible for infrastructure?
Use both together. Terraform provisions infrastructure (VMs, networks, databases) and Ansible configures servers (installing packages, deploying apps, managing configs). Terraform handles the “create and destroy” lifecycle; Ansible handles the “configure and maintain” lifecycle. This complementary pairing is the most common production IaC architecture.
What is Terraform state and why is it important?
State is Terraform’s mapping between configuration and real-world resources. It records resource IDs, attributes, and dependencies. Without state, Terraform cannot know what it manages. Always store state remotely (S3, Azure Storage, Terraform Cloud) with locking to prevent concurrent modification. State files contain sensitive information — encrypt them at rest and control access with IAM policies.
Can I use Pulumi if I don’t know TypeScript?
Yes. Pulumi supports Python, Go, C#, Java, and YAML. Choose the language your team is most comfortable with. The IaC concepts are the same regardless of language — the language choice affects expressiveness and type safety. Python is the second most popular Pulumi language after TypeScript.
How do I handle secrets in IaC?
Never hardcode secrets in configuration files. Use dedicated secret management: Terraform Vault provider, Ansible Vault, Pulumi Secrets, or a secrets manager (AWS Secrets Manager, HashiCorp Vault). Reference secrets by name in your IaC code and resolve them at apply time. Implement secret rotation policies and audit access to secrets regularly.
What is the best IaC workflow for a team?
Use Git-based workflows with code review. Terraform: create a branch, edit HCL, run terraform plan, submit PR, merge, apply in CI/CD. Ansible: create a branch, edit playbooks, run --check in CI, merge, apply in CI/CD. Always review infrastructure changes before they reach production. Include policy checks and security scanning as automated gates in the CI pipeline.
Conclusion
IaC is the foundation of modern DevOps. Terraform excels at provisioning infrastructure across clouds, Ansible handles server configuration and application deployment, and Pulumi offers the flexibility of general-purpose programming languages. Start with one tool, master it, and add others as your needs grow. Store everything in version control, review changes, and automate deployments through CI/CD.
For practical next steps, see our CI/CD pipeline tools and monitoring and observability guide.