Ansible vs Terraform: Which One You Really Need
Terraform dey create your VPS, while Ansible dey configure am. See the real split, why provisioners cause wahala, handoff commands, and when Ansible alone fit do.
Ansible vs Terraform for one sentence
Ansible vs Terraform no be choice between two tools wey dey do the same work. Terraform dey declare the infrastructure wey exist: servers, disks, networks, DNS records. Ansible dey declare wetin suppose dey true inside machine wey already exist: packages, users, config files, running services. Terraform dey create the VPS. Ansible dey turn that VPS into web server.
Both dey declarative, and people dey call both infrastructure as code (IaC). The real difference na wetin dem dey remember. Terraform dey write state file wey map every resource for your code to real object wey e create through API, so e fit know say deleting five lines mean say one server suppose destroy. Ansible no dey remember anything between runs. E dey connect through SSH, inspect the machine, and change only wetin no already match the playbook.
That one difference explain the rest of this guide, including why mixing both jobs inside one tool dey cause problem.
Wetín Terraform actually dey do
Terraform dey talk to an API through provider plugin. Your provider registry page dey define the resource types wey you fit write. So server for one host and server for another host na different resource names with different arguments.
resource "cloud_server" "web" {
name = "web1"
image = "ubuntu-24.04"
type = "small"
}
output "web_ip" {
value = cloud_server.web.ipv4_address
}Replace cloud_server with the resource type wey your provider document. The output block na the important part for this guide, because na through am address dey comot from Terraform.
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplanterraform init dey download the provider and write lock file. terraform plan dey print the difference between your code and the state file, and e go end with line like Plan: 1 to add, 0 to change, 0 to destroy. Read that line every time. Some arguments no fit change in place. The plan go show this with # forces replacement beside the attribute, followed by 1 to add, 0 to change, 1 to destroy. If you apply that plan, e go delete the server and build new empty one. Na so people dey lose data wey dem think say safe.
If you save the plan to file and apply the file, instead of running bare terraform apply, the thing wey you review na the same thing wey go run. Between the two commands, another person fit don change the infrastructure.
terraform.tfstate na the memory. If you lose am, Terraform no go know say those servers belong to you, so the next apply go try create duplicates. Keep am for remote backend as soon as more than one person dey run the commands, because if two people apply at the same time, e go produce this:
Error: Error acquiring the state lockOpenTofu na fork of Terraform wey get the same commands and same file format. As of July 2026, everything for this guide go work if you type tofu instead of terraform.
Wetin Ansible dey actually do
Ansible no need agent or API. E go open SSH connection, copy small Python module go the target, run am, then delete am. Anything wey you fit reach with SSH and sudo password, Ansible fit configure.
- name: Base web server
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure nginx is running at boot
ansible.builtin.service:
name: nginx
state: started
enabled: trueansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.yml --check --diff
ansible-playbook -i inventory.ini site.ymlThe ping module dey confirm SSH, Python and sudo before you start debug playbook. Healthy result na web1 | SUCCESS => {"ping": "pong"}. The --check --diff run na the nearest thing Ansible get to plan: e report wetin go change without changing am. But tasks wey depend on earlier tasks fit report wrong for check mode, because the earlier change never actually happen.
Every run dey end with recap like ok=6 changed=2 unreachable=0 failed=0. Run the same playbook two times. The second run suppose report changed=0. Task wey report changed for every run no be idempotent. E usually be command or shell task wey suppose use real module. If this area new to you, start with your first Ansible playbook for one VPS and build from there.
Where the two tools overlap, and where they fight
Terraform fit run commands for new server with the remote-exec provisioner. HashiCorp own documentation call provisioners last resort. Good reasons dey for this.
Provisioner dey run only when e create the resource. If you edit the script, nothing go happen for existing server, because for Terraform side, the resource already match the code. Provisioner steps no dey appear for terraform plan, so your review no go show any sign of dem. If the script fail, Terraform go mark the resource as tainted, and the next apply go destroy and rebuild server wey probably dey fine.
The failure timing sef bad. Provider go report say server don create immediately API talk so, while operating system still dey boot and sshd never dey listen yet.
Error: remote-exec provisioner error
timeout - last error: dial tcp 203.0.113.10:22: connect: connection refusedAnsible get the opposite temptation. Cloud modules fit create servers, and for small number of machines, this fit work. But you lose dependency graph and state file. Ansible go happily create resource, but if you remove the task from your playbook, the resource go remain running and billing go continue, because nothing record say na your own resource.
The rule wey come from this be say: make Terraform own objects wey API dey create and destroy, while Ansible own everything inside booted operating system.
Handoff wey work
Handoff na boundary, e no be integration. Terraform finish, publish one address, then stop. Ansible start from that address.
terraform apply -auto-approve
terraform output -raw web_ip
printf '[web]\n%s ansible_user=root\n' "$(terraform output -raw web_ip)" > inventory.ini
ansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.ymlterraform output -raw dey print one value without quotes and without JSON wrapper. Na wetin you want inside shell substitution. If servers plenty, use terraform output -json and build inventory from am, because -raw fit handle only one string, number, or boolean.
The ping step between the two tools worth keeping. E separate “Terraform give me wrong address” from “my playbook get bug”. The two problems look the same when na the playbook first thing wey ever touch the new box.
Terraform state read as Ansible inventory
If you no wan write inventory file at all, the cloud.terraform collection go read the state directly.
ansible-galaxy collection install cloud.terraformWrite terraform.yml beside your playbook:
plugin: cloud.terraform.terraform_provider
project_path: /home/deploy/infraansible-inventory -i terraform.yml --graph
ansible-playbook -i terraform.yml site.ymlTwo things dey important to know before you rely on am. The plugin dey run terraform show against project_path, so that directory must don initialize already or the plugin go fail. E no dey create hosts from your server resources by itself: e dey read ansible_host and ansible_group resources, wey you declare for your Terraform code with the Ansible provider. Nothing go show for ansible-inventory --graph until you add dem.
Plain generated inventory file dey easier to debug and e dey work with any provider. The plugin dey useful when the inventory don pass a few machines and hand editing start dey cause typos. Na for this same point managing several Linux servers from one control machine don become real workflow instead of just habit.
You really need Terraform?
Most people wey dey read this no need am, at least for now. Terraform worth the cost when creating and destroying infrastructure dey happen repeatedly. If you order one VPS through control panel and plan to keep am for two years, Terraform go describe something wey happen only once, plus e go add state file wey you must not lose.
Use Terraform when you dey rebuild environments often, when staging must match production exactly, when several people dey change infrastructure and you want plan wey people fit review before anything delete, or when the things you manage pass servers and include DNS records, load balancers, and firewall rules wey dey live for provider API.
Use Ansible alone when servers dey long-lived and dem few, and when the daily question na “this box dey configured correctly?” instead of “this box exist?” One playbook wey harden fresh server cover the same work as the first ten minutes for new VPS, and e get advantage say e go run the same way for the next server.
The learning order follow from this. Ansible go pay back for the first server wey you own. Terraform go pay back for the third environment wey you rebuild.
Handoff na wetin fit break
The server no ready. Terraform succeed, but Ansible fail immediately.
fatal: [web1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.10 port 22: Connection refused", "unreachable": true}API return address before sshd start listening. Wait for the port instead of adding fixed sleep. Ansible get ansible.builtin.wait_for_connection for exactly this purpose; run am as the first task for the play.
The host key don change. You destroy and recreate the server, and the new one answer for the same address with new key.
Host key verification failed.Remove the stale entry with ssh-keygen -R 203.0.113.10. This one dey happen often when Terraform dey rebuild things. Na good reason to keep rebuilds rare for machines wey dey hold data.
Sudo fail. fatal: [web1]: FAILED! => {"msg": "Missing sudo password"} mean say become: true need password for that host. Either configure passwordless sudo for the deploy user, or pass --ask-become-pass.
Terraform wan destroy something wey you no touch. The plan show changes wey you never write. This mean say real infrastructure don drift from the code, usually because person change setting for the provider web panel. Run terraform plan -refresh-only to see only that difference, then decide whether na the code or the live resource get problem. Never apply destructive plan wey you no fit explain line by line.
Ansible report changed for every run. A shell task wey no get creates or when guard dey run unconditionally. This no be cosmetic problem, because e mean say you no fit use changed=0 again as the signal say server don reach the state wey you request.
FAQ
Terraform fit replace Ansible?
No, no be for configuration inside server. Terraform fit call scripts with the remote-exec provisioner, but dem dey run only when resource dey create, dem no dey show for terraform plan, and if dem fail, dem go taint the resource. This one go schedule destroy and rebuild for the next apply. Terraform no get equivalent of module wey go check whether nginx don already install and do nothing if e dey there. Use Terraform create the machine, then hand over to Ansible.
Ansible fit replace Terraform?
For small number of long-lived servers, yes. Ansible get cloud modules wey fit create servers. If you order two VPS instances and keep dem, that one fit enough. Wetin you lose na state file and dependency graph. If you remove task from playbook, the resource go continue running and continue billing, because Ansible never record say na e create am. Terraform for don plan destroy.
Which one I suppose learn first?
Ansible, if you get servers today. E go pay back from the first machine, e need only SSH, and the skill fit work for server wey you order by hand. Terraform go pay back later, when you dey rebuild environments again and again or manage provider resources beyond servers, like DNS records and firewall rules.
How I fit pass the new server IP from Terraform enter Ansible?
Declare an output for your Terraform code, then read am after apply. terraform output -raw web_ip go print the bare value for shell substitution, while terraform output -json go give you all outputs at once when several hosts dey. Write the value enter inventory file, or install the cloud.terraform collection and point ansible-inventory -i terraform.yml --graph to the project directory.
Why my playbook dey fail immediately after Terraform finish?
The provider dey report say e create the server as soon as the API talk so, while the operating system still dey boot. Because of this, SSH dey refuse connection for the first few seconds. The error na UNREACHABLE! with Connection refused. Make ansible.builtin.wait_for_connection the first task for the play instead of guessing sleep duration, because boot time dey vary based on the image and the plan.