Configuration Management: Ansible and the Line with IaC
What This Concept Is
Two different problems that get conflated:
- Infrastructure as Code (Terraform, CloudFormation, Pulumi, CDK) -- provision the boxes: servers, VPCs, databases, DNS, IAM.
- Configuration management (Ansible, Chef, Puppet, Salt) -- configure what is inside the boxes: packages, files, users, services.
Ansible is the dominant modern config-management tool. It uses SSH (or WinRM, or direct API plugins) to push a desired state onto a target system, driven by YAML playbooks.
An Ansible playbook is declarative about task outcomes (idempotent module calls like apt, file, service) but ordered -- tasks run top-to-bottom. It has no native equivalent of a state file; it refreshes facts each run and relies on modules to be idempotent.
Why It Matters Here
Teams often reach for Terraform to install nginx on an EC2 instance and wonder why it feels awkward. The awkwardness is the line. Terraform created the instance; nginx lives inside it. Making Terraform SSH in to install nginx is pushing the tool across a boundary it was not designed for.
The modern answer is often "neither, use an AMI baked with Packer" or "use a container image." But Ansible is still the right tool when:
- you operate long-lived VMs (on-prem, regulated environments, legacy Windows)
- you have to apply changes across hundreds of existing hosts without rebuilding them
- you have drift inside the box (package upgrades, user changes, policy updates)
Concrete Example
A tiny Ansible playbook that installs and configures nginx:
---
- name: Configure web tier
hosts: web
become: true
vars:
nginx_port: 80
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy nginx config
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
mode: "0644"
notify: Reload nginx
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Observations:
- Each task is idempotent at the module level (
apt state=present,template,service state=started). - Playbook order matters: the config must be deployed before the service tries to reload.
become: trueruns with sudo privileges.- The inventory (not shown) is a separate file listing which hosts are in the
webgroup. Ansible has no state file; the inventory is the closest analog.
The Line, Drawn
| Task | IaC (Terraform) | Config management (Ansible) |
|---|---|---|
| Provision EC2 instance | Yes | No |
| Create DNS record | Yes | No |
| Install nginx on the instance | Usually no | Yes |
Configure /etc/nginx/nginx.conf | No | Yes |
| Create S3 bucket | Yes | No |
| Rotate user passwords on 200 servers | No | Yes |
| Build an AMI / container image | Use Packer, not either | Use Packer or Dockerfile |
| Roll a Kubernetes Deployment | Yes (K8s provider) | No |
| Bootstrap a one-off VM | Minimal Terraform + user_data | Ansible |
Modern pattern: Terraform creates the box; the box is immutable (baked by Packer from an Ansible playbook, or built from a container image). No in-place configuration at all. Change = replace.
Legacy pattern: Terraform creates the box; Ansible configures it; both run as part of the same pipeline.
Anti-pattern: Terraform creates the box and also SSHes in via remote-exec to install things. The provisioner has no idempotency guarantee and breaks every assumption about plan/apply.
Common Confusion / Misconception
"Ansible is IaC." It can manage some infrastructure (via cloud modules), but its strength is inside-the-box configuration. Using Ansible to manage VPCs makes you long for terraform plan by week two.
"Terraform can replace Ansible because it has provisioners." It cannot. Provisioners in Terraform are a documented escape hatch, not a feature. HashiCorp's own guidance calls them a last resort.
"Immutable infrastructure makes Ansible obsolete." For cloud-native workloads, largely yes -- images are built once and replaced on change. For long-lived VMs or heterogeneous fleets, Ansible still pays rent.
"An Ansible playbook run twice is safe because modules are idempotent." Module-level idempotency is local. Playbook-level idempotency requires that every task (including command and shell) is idempotent -- which is on you.
How To Use It
- Draw the line explicitly in your architecture: "Terraform manages [X list]. Ansible manages [Y list]. Packer builds images from [Z playbooks]."
- Prefer immutable infrastructure for cloud-native workloads. Use Ansible to build images, not to run against live production boxes.
- If you must run Ansible against live hosts, put it in a pipeline, not on a laptop. Source of truth is the playbook, not the engineer running it.
- Do not use Terraform
local-exec/remote-execto do Ansible's job. It creates a workflow nobody can reason about.
Check Yourself
- Why is "Terraform +
remote-exec+ install-nginx.sh" fragile? - What is the difference between Ansible's inventory and Terraform's state file?
- Give one task you would use Ansible for today, and one you would use Terraform for, on the same server.
Mini Drill or Application
Sketch a pipeline in 15 minutes:
- Packer builds an AMI using an Ansible playbook that installs a web server and deploys your app artifact.
- Terraform creates an ASG that uses that AMI, plus a load balancer.
- Deploys are "bake new AMI -> Terraform updates the launch template -> ASG rolls."
Identify where config drift becomes impossible in this design (and where it is merely mitigated). This is the logic behind immutable infrastructure.
See also (external)
- Ansible: Getting started -- control node, inventory, modules, and idempotency semantics.
- Terraform Language: Providers -- useful for understanding where Terraform's ownership boundary stops (the provider API).
Source Backbone
Infrastructure-as-code details are tool-specific, but these local books provide the operational backbone for shell, Git, and change discipline.
- Pro Git - versioned infrastructure changes, branching, review, and rollback habits.
- Git from the Bottom Up - mental model for stateful change history.
- The Linux Command Line - shell and automation grounding for infrastructure work.