For years, I saw a recurring debate in the DevOps community: “Should I use Terraform or Ansible?” It’s a false dichotomy. After managing dozens of production environments, I’ve found that the real magic happens when you stop treating them as competitors and start using them as a duo. If you’re wondering how to automate infrastructure with Ansible and Terraform, the secret lies in understanding the boundary between provisioning and configuration management.

In my experience, Terraform is the undisputed king of the “outer shell”—the networks, the load balancers, and the virtual machines. Ansible, however, is the master of the “inner life”—the packages, the users, and the application configs. When combined, you get a fully automated pipeline from a blank cloud account to a running production app.

Prerequisites

Before we dive into the code, ensure you have the following installed and configured in your local environment:

If you’re still deciding on your toolstack for containers, I recommend checking out my guide on the best IaC tools for Kubernetes to see where this fits into a larger orchestration strategy.

Step-by-Step Implementation

Step 1: Provisioning with Terraform

First, we define our infrastructure. I prefer using a modular approach to keep things clean. In my setup, I use a dedicated module for the VPC and another for the EC2 instances. If you’re scaling this, definitely follow terraform module best practices to avoid the “monolith state file” nightmare.


# main.tf
resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  key_name      = "my-ssh-key"

  tags = {
    Name = "Web-Server-Ansible"
    Role = "webserver"
  }
}

output "web_server_public_ip" {
  value = aws_instance.web_server.public_ip
}

Run terraform apply to spin up your instance. At this point, you have a server, but it’s an empty shell.

Step 2: Bridging the Gap (The Dynamic Inventory)

The hardest part of learning how to automate infrastructure with Ansible and Terraform is handling the IP addresses. You shouldn’t hardcode IPs into your Ansible inventory. Instead, use a dynamic inventory or pass the output of Terraform to Ansible.

For simple setups, I use a local shell script to generate a hosts file. For enterprise setups, I use the terraform-inventory plugin. Here is the manual approach for clarity:


# Extract IP and add to Ansible inventory
terraform output -raw web_server_public_ip > hosts
```

Step 3: Configuring with Ansible

Now that the server exists and we have the IP, we use Ansible to turn that raw Linux box into a web server. I've found that using a playbook.yml is the most maintainable way to handle this.


---
- name: Configure Web Server
  hosts: all
  become: yes
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes

    - name: Start Nginx
      service:
        name: nginx
        state: started
        enabled: yes

Run the playbook with: ansible-playbook -i hosts playbook.yml. As shown in the architecture flow described earlier, Terraform handled the "where" and Ansible handled the "what".

Terminal output showing successful Ansible playbook execution on a Terraform-provisioned server
Terminal output showing successful Ansible playbook execution on a Terraform-provisioned server

Pro Tips for Production

  • Use Terraform Local-Exec: If you want a one-click deployment, you can use a local-exec provisioner in Terraform to trigger the Ansible playbook immediately after the instance is created.
  • State Management: Always store your Terraform state in an S3 bucket with DynamoDB locking to prevent state corruption during team collaborations.
  • Ansible Vault: Never commit passwords or API keys in your playbooks. Use ansible-vault to encrypt sensitive data.

Troubleshooting Common Issues

In my experience, 90% of failures when combining these tools come from SSH connectivity. If your Ansible playbook hangs at "Gathering Facts", check these three things:

  1. Security Groups: Ensure your Terraform code opened port 22 (SSH) to your specific IP.
  2. SSH Key Permissions: Run chmod 400 my-key.pem. Ansible will fail if your private key is too "open".
  3. Usernames: Remember that AWS Ubuntu images use the user ubuntu, while Amazon Linux uses ec2-user. Specify this in your Ansible inventory: ansible_user=ubuntu.

What's Next?

Now that you've mastered the basics of how to automate infrastructure with Ansible and Terraform, the next step is moving toward Immutable Infrastructure. Instead of configuring servers on the fly, you can use HashiCorp Packer to create a pre-configured image (AMI) using Ansible, and then use Terraform to deploy that image. This reduces deployment time from minutes to seconds.

Ready to optimize your workflow? Start by refining your Terraform modules to handle multi-region deployments.