Introduction
Infrastructure as Code (IaC) has become a foundational pillar in modern DevOps and cloud-native operations. As organizations scale and migrate toward microservices, containers, and multi-cloud environments, manual provisioning and configuration are no longer viable. IaC enables teams to define, provision, and manage infrastructure using code, ensuring consistency, versioning, automation, and repeatability. Two of the most widely adopted tools in this landscape are Terraform and Ansible.
Terraform excels at orchestrating infrastructure provisioning across cloud providers using a declarative language, while Ansible specializes in configuration management and automation using an imperative playbook-based approach. When combined, they deliver a powerful tooling stack that streamlines end-to-end infrastructure lifecycle management.
This blog will explore IaC principles, demonstrate real-world Terraform configurations for cloud infrastructure, illustrate Ansible’s role in post-provisioning configuration, cover best practices for versioning infrastructure, and present strategies for achieving fully reproducible and automated deployments.
Core Principles of Infrastructure as Code (IaC)
Modern infrastructure isn’t about manually launching VMs or clicking through UIs. IaC promotes control, scale, and efficiency.
Key principles include:
- Declarative vs Imperative: Declarative tools (Terraform) describe what the infrastructure should be, while imperative tools (Ansible) define how to get there.
- Idempotency: Multiple executions yield the same predictable result, reducing risk in repeat deployments.
- Version Control: Entire infrastructure defined and managed in Git to allow collaboration, change tracking, and rollback.
- Automation and CI/CD: Seamless integration into pipelines for safe, continuous delivery of infrastructure changes.
These principles enable stability, reliability, collaboration, and traceability in modern infrastructure operations.
Provisioning Cloud Resources with Terraform
Terraform, a tool by HashiCorp, uses HCL (HashiCorp Configuration Language) to create, manage, and update infrastructure across major cloud platforms.
Writing Terraform Configurations
A minimal set of files might look like:
provider.tf
provider "aws" {
region = "us-east-1"
}
main.tf
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "MyWebServer"
}
}
Terraform Workflow
terraform init- Initializes the working directoryterraform plan- Previews changesterraform apply- Applies configurationterraform destroy- Tears down infrastructure
Modular Terraform Architecture
Break configuration into reusable modules:
module "network" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
}
Best practices for structure:
main.tf- core resourcesvariables.tf- input parametersoutputs.tf- output valuesprovider.tf- cloud provider config
State Management & Version Control
Terraform uses a local or remote state file to track infrastructure changes. For team environments:
- Store state remotely (S3 backend)
- Lock state using DynamoDB
- Encrypt state-at-rest
- Limit access using IAM roles and policies
Example remote backend config:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "env/dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock"
}
}
Store Terraform code in Git using branches, tagging, and pull request reviews for controlled changes.
Configuration Management with Ansible
While Terraform builds resources, Ansible manages configuration after boot-up. Think “day two” operations: installing packages, setting permissions, deploying code.
What Is Ansible?
- Agentless over SSH/WinRM
- YAML-based playbooks
- Idempotent automation
- Strong inventory system, including dynamically from AWS, GCP, etc.
Ansible is ideal for tasks like:
- Software installs & upgrades
- OS configuration
- Application deployments
- Security hardening
- Environment setup
Sample Playbook
- name: Install and start NGINX
hosts: web
become: true
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Start nginx
service:
name: nginx
state: started
enabled: true
Best Practices with Ansible
- Use roles to organize complex tasks
- Maintain inventory dynamically
- Secure secrets using Ansible Vault
- Use handlers and tags to control execution
- Test changes locally with Molecule
Orchestrating IaC Workflows: Terraform + Ansible
Terraform and Ansible play well together in structured workflows.
Scenario: Provision + Configure a Web Server
- Provision EC2 instance using Terraform
- Output IP to Ansible inventory
- Run Ansible to configure NGINX
Terraform Output
output "web_ip" {
value = aws_instance.web.public_ip
}
Script to Glue the Tools
#!/bin/bash
# Provision infrastructure
terraform apply -auto-approve
# Extract IP for inventory
IP=$(terraform output -raw web_ip)
echo "[web]" > inventory.ini
echo "${IP} ansible_user=ubuntu" >> inventory.ini
# Run Ansible playbook
ansible-playbook -i inventory.ini playbooks/nginx.yml
This pipeline ensures consistent environment creation and post-configuration automation.
Reproducibility and Environment Parity
To prevent “works on my machine” issues:
- Use parameterized modules for environment-specific values
- Create separate workspaces (
terraform workspace) or folders per environment - Pin versions in Terraform with
required_providersand create aversions.tffile - Pin Ansible roles and use explicit collections
Example Folder Structure
/iac/
└── terraform/
├── modules/
└── envs/
├── dev/
├── staging/
└── prod/
└── ansible/
├── inventories/
├── dev/
├── prod/
├── roles/
└── playbooks/
Track everything in Git and tag releases like code deployments (v1.0.0-infra).
Advanced Tips
Common Mistakes
- Local Terraform state: Breaks in team environments. Always use remote backends.
- Interleaved provisioning/configuration: Avoid logic mix. Terraform = infra; Ansible = config.
- Missing version pins: Causes drift and unintended updates.
- Skipping validation: Use
terraform validate,tflint,ansible-lint
Troubleshooting: Common Issues & Fixes
| Issue | Cause | Resolution |
|---|---|---|
| Terraform drift | Manual changes outside code | Use terraform plan regularly; audit differences |
| Ansible SSH timeout | Instance not ready | Add SSH wait logic or depend on AWS status check |
| Idempotency failure | Bad Ansible task implementation | Ensure proper state values and use conditionals |
| Dynamic inventory fails | Missing AWS credentials | Set up AWS plugin and export keys or use roles |
Best Practices Checklist
- Version infrastructure in Git
- Use remote Terraform state with locking
- Write reusable Terraform modules
- Use Ansible roles and handlers
- Extract variables to avoid hardcoding
- Secure credentials using Vault or SSM
- Integrate IaC into automated pipelines
- Validate, lint, and test before deploying
Resources & Next Steps
- Terraform Docs
- Ansible Docs
- Terragrunt for Terraform wrappers
- Ansible Galaxy Roles and Collections
- CI/CD Integration via GitHub Actions
Next Steps:
- Build a Terraform module repo and publish outputs.
- Add Ansible auto-configuration post-deploy hook.
- Secure and manage secrets with Vault or SSM.
- Set up CI/CD (GitHub Actions/GitLab/Jenkins) to automate.
- Apply monitoring and alerts using IaC (e.g., CloudWatch, Prometheus).
Conclusion
Managing modern infrastructure is impossible without automation and consistency. Infrastructure as Code with Terraform and Ansible allows teams to provision, version, and configure environments with speed and precision. Terraform handles provisioning predictably. Ansible configures reliably. Combined, they deliver automation excellence.
Key takeaways:
- Terraform = Provisioning resource infrastructure declaratively
- Ansible = Configuration and post-install automation
- Versioning, testing, and remote state are essential for stability
- Reproducible deployments improve consistency and scale
- CI-CD pipelines empower teams to deliver infrastructure like software
Now is the time to bring software engineering discipline to systems engineering.
Happy coding!