Managing Infrastructure and Configuration Together: Terraform Meets Ansible
Terraform is great for provisioning infrastructure. Ansible is great for configuring it. Together, they cover the full automation spectrum — but traditional integrations using local-exec
can get messy and hard to scale.
Enter the Terraform Ansible Provider: a cleaner, more seamless way to connect both tools.
In this article, we’ll deploy an NGINX server on DigitalOcean using this provider — bridging infrastructure provisioning and configuration into an unified workflow.
Prerequisites
- Terraform
- Ansible
- A DigitalOcean account and a configured token
Install the cloud.terraform
Ansible collection :
ansible-galaxy collection install cloud.terraform
Linking Terraform and Ansible: Inventory Configuration Made Easy
The cloud.terraform.terraform_provider plugin bridges Terraform and Ansible by enabling Ansible to interact directly with infrastructure defined in your Terraform state. This integration ensures consistency between both tools by dynamically generating an Ansible inventory from the current Terraform state.
Here’s an example of what the inventory.yml file looks like:
---
plugin: cloud.terraform.terraform_provider
Let’s Write the Terraform Code
The following Terraform configuration provisions infrastructure on DigitalOcean and seamlessly integrates it with Ansible using the Terraform Ansible provider. It defines:
- The required Terraform providers (DigitalOcean and Ansible)
- A Droplet provisioned via the DigitalOcean API
- An Ansible inventory entry for the Droplet, directly from the Terraform state
terraform {
required_providers {
ansible = {
source = "ansible/ansible"
version = "1.3.0"
}
digitalocean = {
source = "digitalocean/digitalocean"
version = "2.45.0"
}
}
}
variable "do_token" {
description = "Token to authenticate to DigitalOcean"
type = string
sensitive = true
}
resource "digitalocean_ssh_key" "default" {
name = "Terraform Example"
public_key = file(pathexpand("~/.ssh/id_rsa.pub"))
}
resource "digitalocean_droplet" "web1" {
name = "web1"
image = "ubuntu-24-10-x64"
region = "nyc2"
size = "s-1vcpu-1gb"
backups = false
ssh_keys = [digitalocean_ssh_key.default.fingerprint]
}
resource "ansible_host" "web1" {
name = digitalocean_droplet.web1.ipv4_address
groups = ["webservers"]
variables = {
ansible_user = "root"
ansible_ssh_private_key_file = "~/.ssh/id_rsa"
ansible_python_interpreter = "/usr/bin/python3"
ansible_ssh_common_args = "-o StrictHostKeyChecking=no"
}
}
output "ipv4_address" {
value = digitalocean_droplet.web1.ipv4_address
}
- Providers Setup : The
required_providers
block defines dependencies on the ansible and digitalocean providers with explicit versioning for reproducibility. - API Token Input : The
do_token
variable is marked as sensitive, ensuring secure handling of your DigitalOcean credentials. - SSH Key Configuration : Uploads your local SSH public key to DigitalOcean, enabling SSH access to the new droplet.
- Droplet Provisioning : A single web1 Droplet is created with Ubuntu 24.10 in the NYC2 region using a small instance type.
- Ansible Inventory Resource : The
ansible_host
resource dynamically maps the Droplet’s IP into an Ansible inventory. This ensures your configuration management is directly aligned with the Terraform state. - IP Output : Displays the provisioned server’s IP after a successful apply, making it easy to connect or verify.
Apply the Terraform code with your DigitalOcean token :
$ read -s DO_TOKEN
$ terraform apply -var "do_token=$DO_TOKEN"
Visualizing the Ansible Dynamic Inventory Generated by Terraform
After provisioning your infrastructure and generating the Ansible inventory via Terraform, you can visualize its structure and associated variables using this command:
$ ansible-inventory -i inventory.yml --graph --vars
What you see explained
@all
: Represents all hosts defined in the inventory.@ungrouped
: Hosts that are not assigned to any specific group.@webservers
: This group includes all hosts labeled under “webservers,”.
Under the @webservers
group, the host 162.243.103.221 has several variables attached:
ansible_python_interpreter = /usr/bin/python3
. Specifies the Python interpreter path on the target machine.ansible_ssh_common_args = -o StrictHostKeyChecking=no
. SSH option to skip the host key verification when connecting.ansible_ssh_private_key_file = ~/.ssh/id_rsa
. Path to the SSH private key used for authentication.ansible_user = root
. User to connect as over SSH.
Why this matters
This graphical output helps you:
- Understand your inventory structure at a glance.
- Verify that all necessary variables are properly set on each host.
- Debug and review your Ansible configuration before running playbooks.
Apply the Ansible Playbook Using the Terraform-Generated Inventory
Once your infrastructure is provisioned and the dynamic inventory is ready, you can run your Ansible playbook directly using the Terraform-generated inventory.yml
file:
ansible-playbook playbook.yml -i inventory.yml
This command executes the playbook against the provisioned infrastructure. In this example, the playbook:
- Gathers facts about the target host.
- Installs the NGINX package if it’s not already present.
- Ensures the NGINX service is started and enabled on boot.
Sample outputs
PLAY [Webservers] *************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************
ok: [162.243.103.221]
TASK [Ensure NGINX package is present.] ***************************************************************************
changed: [162.243.103.221]
TASK [Ensure NGINX is started and enabled.] ***********************************************************************
ok: [162.243.103.221]
PLAY RECAP ********************************************************************************************************
162.243.103.221 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Verifying the Deployment
You can SSH into the server and check that NGINX is running correctly:
HOSTNAME=$(terraform output -json | jq -r .ipv4_address.value)
ssh root@$HOSTNAME
Once connected:
systemctl status nginx
Sample Status Output:
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Wed 2024-12-04 10:51:51 UTC; 1min 29s ago
Main PID: 2441 (nginx)
Tasks: 2 (limit: 1110)
Memory: 2.1M (peak: 2.3M)
CPU: 26ms
CGroup: /system.slice/nginx.service
├─2441 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
└─2442 "nginx: worker process"
This confirms that your playbook was applied successfully, and the NGINX service is now running on your newly provisioned server.
Conclusion
In this article, we explored how to seamlessly integrate Terraform and Ansible using the Terraform Ansible provider to automate both infrastructure provisioning and configuration management.
By leveraging this integration:
- Terraform takes care of creating cloud resources (like VMs, networks, etc.)
- Ansible handles the configuration of those resources (like installing packages, starting services, etc.)
- The Terraform Ansible provider bridges the gap, automatically generating dynamic inventories from your Terraform state
This approach eliminates the need for manual inventory management and ensures consistency between provisioning and configuration.
📦 Want to try it out? You can find the full example code on GitHub: 👉 terraform-ansible-integration-101