For years, I saw developers arguing over whether to use Terraform or Ansible. In my experience, this is the wrong question. The real magic happens when you stop treating them as competitors and start using them as a team. If you’re wondering how to automate infrastructure with Ansible and Terraform, the answer lies in understanding their distinct roles: Terraform handles the where (infrastructure), and Ansible handles the what (software and configuration).
In this tutorial, I’ll walk you through my preferred hybrid workflow. We’ll use Terraform to spin up a virtual machine on a cloud provider and then hand off the baton to Ansible to turn that raw machine into a fully functional web server. This approach allows you to follow terraform module best practices while maintaining the flexibility of agentless configuration management.
Prerequisites
- A cloud account (AWS, GCP, or Azure)—I’ll use AWS for this example.
- Terraform installed on your local machine.
- Ansible installed on your local machine (or a control node).
- An SSH key pair generated and available for your instances.
- Basic familiarity with YAML and HCL (HashiCorp Configuration Language).
Step 1: Provisioning Infrastructure with Terraform
First, we need a place for our code to live. I prefer using Terraform for this because its state management is superior for tracking physical resources. We’ll create a simple main.tf file to provision an EC2 instance.
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
key_name = "my-ssh-key"
tags = {
Name = "Ansible-Managed-Node"
}
}
output "server_ip" {
value = aws_instance.web_server.public_ip
}
Run terraform init and terraform apply. Once the process finishes, Terraform will output the public IP of your new server. This IP is the critical link we need for the next phase.
Step 2: Creating the Ansible Inventory
Ansible needs to know which servers to manage. While you can use static inventory files, I recommend using a dynamic approach or a simple hosts file for small projects. For this tutorial, we will create a hosts.ini file using the IP address provided by Terraform.
[webservers]
1.2.3.4 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa
In larger setups, I usually automate this part using a Terraform local-exec provisioner or by exporting the IP to a JSON file that Ansible can read. If you’re scaling further, you might want to explore the best iac tools for kubernetes to handle orchestration at scale.
Step 3: Configuring the Server with Ansible
Now that the hardware is ready, we need to install our software. We’ll create a playbook called setup.yml to install Nginx and ensure it’s running. This is where the power of idempotency comes in—Ansible will only make changes if the server isn’t already in the desired state.
- name: Configure Web Server
hosts: webservers
become: yes
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Start Nginx service
service:
name: nginx
state: started
enabled: yes
As shown in the output we’ll get in the next step, Ansible will connect via SSH, elevate privileges using sudo, and ensure Nginx is live.
Step 4: Executing the Automation Pipeline
To bring it all together, I usually run these as two separate stages in a CI/CD pipeline. However, for local testing, you can run them sequentially:
# Provision the infra
terraform apply -auto-approve
# Run the configuration
ansible-playbook -i hosts.ini setup.yml
When you run the playbook, you’ll see a series of ok and changed messages. If you run it a second time, everything should show as ok, proving that your infrastructure is now stable and automated.
Pro Tips for Hybrid Automation
- Use Terraform Outputs: Don’t hardcode IPs. Use
terraform output -jsonto feed your Ansible inventory dynamically. - Separate State from Config: Never use Terraform to install software inside a VM. It’s not designed for it and will make your state files messy.
- Use Ansible Roles: As your project grows, move your tasks into roles (e.g.,
roles/webserver) to keep your playbooks clean. - Immutable Infrastructure: For high-scale environments, consider using Packer to bake the Ansible configuration into a machine image (AMI), then use Terraform to deploy that image.
Troubleshooting Common Issues
| Issue | Cause | Solution |
|---|---|---|
| SSH Connection Timeout | AWS Security Group blocking port 22 | Add an ingress rule for SSH from your IP in Terraform. |
| Ansible ‘Unreachable’ | Instance still booting up | Add a sleep timer or a wait-for-ssh task in your pipeline. |
| Sudo Permission Denied | Wrong become setting |
Ensure become: yes is set in your playbook. |
What’s Next?
Now that you’ve mastered the basics of how to automate infrastructure with Ansible and Terraform, I recommend looking into CI/CD integration. Try moving this workflow into GitHub Actions or GitLab CI. You can set it up so that a merge to the main branch triggers the Terraform apply and subsequently the Ansible playbook, creating a fully automated deployment pipeline.