23 Oct 2026 · 3 min read · Ravi Sanchala
Build, then configure: why OpenTofu and Ansible stay separate
Provisioning servers and configuring them are different jobs. Keeping them in two phases makes both safer to change and easier to fix.
When teams adopt infrastructure as code, they often try to make one tool do everything: create the servers, install the software, write the config files and start the services. It works until something fails halfway, and then nobody is sure what state anything is in.
I split the work into two phases with two tools, and I keep them strictly apart.
Phase 1: build (OpenTofu)
OpenTofu, the open-source fork of Terraform, creates the cloud resources:
- Virtual machines
- Networks and subnets
- Firewall rules
- IAM roles and service accounts
- DNS records
Its output is a set of facts: server addresses, service account names, DNS names. It doesn’t know or care what software will run on those servers.
Phase 2: configure (Ansible)
Ansible takes the output of phase 1 and configures the running servers:
- Installs packages
- Writes configuration files
- Creates users and permissions
- Starts and enables services
- Sets up monitoring agents
It only runs once the servers exist and are reachable.
Why keep them separate?
Each tool does what it’s best at. OpenTofu is excellent at declaring resources and tracking their state. Ansible is excellent at describing how a machine should be set up. Forcing either one into the other’s job makes both worse.
Failures stay contained. If configuration fails, the servers are still there. Fix the playbook and re-run phase 2. You don’t rebuild infrastructure because a config file had a typo.
Both phases are safe to re-run. Running OpenTofu again changes nothing if nothing changed. Running Ansible again just confirms every server already matches. That’s what lets you apply changes confidently.
Changes are reviewable. Before anything is applied, both phases can show what they would change. You review a plan, the same way you review a pull request.
What day-to-day changes look like
Adding a firewall rule:
1. Edit the OpenTofu code
2. make infra-preview → review what will change
3. make infra-build → apply it
Changing a config file on the app servers:
1. Edit the Ansible playbook
2. make infra-preview → review what will change
3. make infra-config → apply it
Nobody SSHes into a server to make a change. Every change is a commit with an author and a reason.
Same playbooks, many locations
The configuration phase is cloud-agnostic. Ansible doesn’t care whether a server came from a cloud provider or sits in a rack in someone’s data center.
On one fintech project this mattered a lot. Different countries had different data-residency requirements, so the platform ran both on cloud and on on-premises servers. OpenTofu built the cloud side; the on-premises machines were provided separately. The same Ansible playbooks configured all of them, so every location behaved the same way.
Same code, different environments
Dev and production are built from the same code. Only the variables change: machine sizes, log levels, backup settings, deletion protection. Dev is cheap and relaxed. Production is strict. Neither is a hand-made copy of the other.
The result
- Every server can be rebuilt from code, without guessing
- Every change is reviewed before it’s applied
- A failure in one phase never leaves you unsure about the other
- New locations reuse the same configuration instead of starting over
Two phases. Two tools. One source of truth in Git.