cd ../blog

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.

infrastructure-as-codeopentofuansible

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.

related case study From hand-built servers to multi-country infrastructure, entirely as code read case study →

$ ./book-call.sh

Dealing with something like this?

Let's talk it through. 30 minutes, free, and you'll leave with a clear picture of what to fix first.

Book a free 30-min callOr take the free 3-minute scorecard

next post

Do you actually need Kubernetes? Kubernetes is powerful and widely used. For many small teams it's also the most expensive way to run a handful of services. Here's how I decide. Read post →