Infrastructure Automation â Ansible, Terraform & Modern IaC
In this tutorial, you'll learn about Infrastructure Automation. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Master infrastructure automation with Ansible and Terraform: declarative provisioning, Configuration Management, Immutable Infrastructure, and CI/CD integration for cloud environments.
What You'll Learn
You will learn to automate infrastructure provisioning with Terraform, manage server configurations with Ansible, implement Immutable Infrastructure patterns, and integrate IaC into CI/CD pipelines for repeatable, version-controlled deployments.
Why It Matters
Manual server setup is error-prone, slow, and impossible to reproduce consistently. Infrastructure as Code (IaC) lets you define servers, networks, and services in declarative configuration files that are version-controlled, tested, and automatically applied. A single command can provision a complete production environment identical to staging.
Real-World Use
When Durga Antivirus Pro launched a new data processing pipeline, the team defined the entire infrastructure in Terraform: EC2 instances, load balancers, RDS databases, S3 buckets, and VPC networking. Ansible handled software installation and configuration. The result: a production environment deployed in 12 minutes instead of two days, with zero configuration drift between environments.
Your Learning Path
flowchart LR
A[Monitoring Alerting] --> B[Infrastructure Automation]
B --> C[AI Code Generation]
C --> D["CI/CD Pipelines"]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Prerequisites: Basic understanding of Cloud Computing concepts (servers, networking, storage). Familiarity with Linux command line and YAML syntax is helpful.
Infrastructure as Code: Two Approaches
| Tool | Purpose | Paradigm | State Management |
|---|---|---|---|
| Terraform | Provisioning | Declarative | State files (remote backend) |
| Ansible | Configuration | Procedural / Declarative | Idempotent, no state file |
| Pulumi | Provisioning | General-purpose languages | State files (managed backend) |
| CloudFormation | Provisioning | Declarative (AWS-only) | AWS-managed state |
When to Use Which
Use Terraform to create and manage cloud resources (VMs, networks, databases, load balancers). Use Ansible to configure those resources after provisioning (install software, set configs, start services).
Terraform: Declarative Infrastructure
Provider Configuration
# main.tf
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "myorg-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
}
}
provider "aws" {
region = "us-east-1"
}
Expected behavior: Terraform initializes with the AWS provider and configures remote state storage in S3, ensuring the team shares a single source of truth.
Defining Compute Resources
# compute.tf
resource "aws_instance" "app_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = aws_subnet.main.id
vpc_security_group_ids = [aws_security_group.app.id]
root_block_device {
volume_size = 50
volume_type = "gp3"
}
tags = {
Name = "app-server-${var.environment}"
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_security_group" "app" {
name = "app-server-sg"
description = "Security group for application server"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.admin_cidrs
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Expected behavior: Terraform creates an EC2 instance and its security group when <a href="/devops/terraform/">Terraform</a> apply runs. If the security group already exists, Terraform detects no changes and skips it.
Variables and Outputs
# variables.tf
variable "environment" {
description = "Deployment environment"
type = string
default = "development"
validation {
condition = contains(["development", "staging", "production"], var.environment)
error_message = "Environment must be development, staging, or production."
}
}
variable "admin_cidrs" {
description = "CIDR blocks allowed SSH access"
type = list(string)
default = ["10.0.0.0/8"]
}
# outputs.tf
output "app_server_ip" {
description = "Public IP of the application server"
value = aws_instance.app_server.public_ip
}
output "app_server_id" {
value = aws_instance.app_server.id
}
Expected behavior: <a href="/devops/terraform/">Terraform</a> output displays the server IP and ID after provisioning. Variables allow the same configuration to deploy to multiple environments.
Ansible: Configuration Management
Inventory File
# inventory/hosts.ini
[webservers]
web01 ansible_host=10.0.1.10 ansible_user=ubuntu
web02 ansible_host=10.0.1.11 ansible_user=ubuntu
[databases]
db01 ansible_host=10.0.2.10 ansible_user=ubuntu
[all:vars]
ansible_python_interpreter=/usr/bin/python3
Playbook: Web Server Setup
---
- name: Configure web servers
hosts: webservers
become: yes
vars:
nginx_port: 443
app_directory: /opt/myapp
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install required packages
apt:
name:
- nginx
- certbot
- python3-certbot-nginx
- htop
- fail2ban
state: present
- name: Start and enable nginx
service:
name: nginx
state: started
enabled: yes
- name: Create application directory
file:
path: "{{ app_directory }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Deploy nginx configuration
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/myapp
notify: restart nginx
- name: Enable site configuration
file:
src: /etc/nginx/sites-available/myapp
dest: /etc/nginx/sites-enabled/myapp
state: link
notify: restart nginx
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
Expected behavior: Ansible connects to each web server via SSH, installs packages, creates directories, deploys the nginx config from a Jinja2 template, and restarts nginx if the config changed. The playbook is idempotent -- running it multiple times produces the same result.
Combined Terraform and Ansible
#!/bin/bash
# deploy.sh: Provision infrastructure and configure servers
set -euo pipefail
ENVIRONMENT="${1:-staging}"
echo "=== Step 1: Provision infrastructure ==="
cd terraform
terraform init
terraform workspace select "$ENVIRONMENT" || terraform workspace new "$ENVIRONMENT"
terraform apply -auto-approve -var="environment=$ENVIRONMENT"
# Extract IP addresses from Terraform outputs
APP_IPS=$(terraform output -json app_server_ips | jq -r '.[]')
echo "$APP_IPS" > /tmp/app_ips.txt
echo "=== Step 2: Wait for SSH to be ready ==="
for ip in $APP_IPS; do
until nc -zv "$ip" 22 2>/dev/null; do
echo "Waiting for $ip:22..."
sleep 5
done
done
echo "=== Step 3: Configure servers with <a href="/devops/ansible/">Ansible</a> ==="
cd ../<a href="/devops/ansible/">Ansible</a>
<a href="/devops/ansible/">Ansible</a>-playbook -i inventory/aws_ec2.yml \
playbooks/configure.yml \
--extra-vars "environment=$ENVIRONMENT"
echo "=== Deployment complete ==="
Expected behavior: The script runs Terraform to provision cloud resources, waits for SSH connectivity, then runs Ansible to configure the servers. The entire environment is created and configured in a single automated pipeline.
Immutable Infrastructure Pattern
Instead of updating servers in place (mutable), Immutable Infrastructure replaces the entire server for each change.
# Create a new AMI with Packer, then deploy with Terraform
resource "aws_launch_template" "app" {
name = "app-${var.app_version}"
image_id = data.aws_ami.app_image.id
instance_type = "t3.medium"
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
version = var.app_version
}))
}
resource "aws_autoscaling_group" "app" {
name = "app-asg-${var.app_version}"
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
min_size = 2
max_size = 10
desired_capacity = 2
vpc_zone_identifier = aws_subnet.private[*].id
tag {
key = "AppVersion"
value = var.app_version
propagate_at_launch = true
}
}
Expected behavior: When app_version changes, Terraform creates a new launch template. The ASG rolls out new instances with the updated version and terminates old ones -- zero in-place updates.
Common Infrastructure Automation Mistakes
1. Storing State Files Locally
Terraform state files contain sensitive information and must be shared. Always use remote backends (S3, Terraform Cloud, Azure Storage) with state locking enabled.
2. Hardcoding Environment-Specific Values
A hardcoded instance type in a Terraform file prevents reuse across environments. Use variables with sensible defaults and environment-specific variable files.
3. Not Testing Changes in Isolation
Running <a href="/devops/terraform/">Terraform</a> apply directly on production without a plan review is dangerous. Use <a href="/devops/terraform/">Terraform</a> plan in CI/CD, require approval for production, and run integration tests against staging first.
4. Ignoring Idempotency in Ansible
Ansible tasks should produce the same result regardless of how many times they run. Avoid tasks that unconditionally restart services or overwrite files without checking for changes.
5. Managing Secrets in Plain Text
Terraform and Ansible often need API keys and passwords. Use encrypted variables (Ansible Vault), secret stores (AWS Secrets Manager, HashiCorp Vault), or environment variables instead of plain-text files.
6. Skipping Tagging and Naming Conventions
Untagged resources are impossible to track for cost, ownership, and cleanup. Always tag resources with environment, project, owner, and managed-by metadata.
7. No Rollback Plan
If a Terraform apply destroys critical resources or an Ansible playbook misconfigures a service, you need a rollback Strategy. Use Terraform workspaces, version-controlled state, and snapshots before major changes.
Practice Questions
1. What is the difference between Terraform and Ansible? Terraform provisions cloud resources (VMs, networks, databases) declaratively. Ansible configures those resources after provisioning (installing packages, setting configs, managing services).
2. Why should Terraform state be stored remotely? Remote state enables team collaboration, provides state locking to prevent concurrent modifications, and protects against local disk failures. It is the single source of truth for your infrastructure.
3. What is Immutable Infrastructure? Immutable Infrastructure replaces entire servers instead of modifying them in place. Each deployment creates new instances with the updated configuration, and old instances are terminated. This eliminates configuration drift.
4. What does idempotent mean in Configuration Management? An idempotent operation produces the same result regardless of how many times it is applied. Running the same Ansible playbook 10 times results in the same server state as running it once.
5. Challenge: Write a Terraform configuration that provisions an EC2 instance with an attached security group. Then write an Ansible playbook that installs Nginx on that instance. Combine them in a deployment script.
Mini Project: Two-Tier Application Deployment
Create a complete infrastructure automation project that provisions a two-tier application (web server + database) using Terraform, configures both with Ansible, implements immutable deployment patterns, includes tagging and naming conventions, and provides a single command to deploy the entire stack.
# Example Terraform for the project
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.deploy.key_name
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y python3
EOF
tags = {
Name = "web-${var.environment}"
Environment = var.environment
ManagedBy = "terraform"
Project = "two-tier-app"
}
}
Expected behavior: Running <a href="/devops/terraform/">Terraform</a> apply creates the web server with user data that installs Python (required by Ansible). Ansible then takes over to deploy the actual application.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro