Skip to content

Infrastructure Automation — Ansible, Terraform & Modern IaC

DodaTech Updated 2026-06-22 9 min read

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
â„šī¸ Info

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