Configuration Management vs Provisioning
When people start learning Infrastructure as Code (IaC) — managing servers and networks with code instead of clicking around in a web console — two words get mixed up all the time: provisioning and configuration management. They sound similar, and some tools blur the line, but they answer two very different questions. Provisioning asks “what machines and networks should exist?” Configuration management asks “what software should run on those machines, and how should it be set up?” Getting this distinction clear early will save you a lot of confusion when you choose tools like Terraform and Ansible.
What provisioning means
Provisioning is the act of creating the raw infrastructure — the things that did not exist before you ran your code. This includes:
- Virtual machines (a server running in the cloud), often called instances.
- Networks, subnets, and firewall rules.
- Load balancers (a server that spreads incoming traffic across several machines).
- Managed databases, storage buckets, DNS records, and more.
Before provisioning, you have nothing — just a cloud account. After provisioning, you have an empty Ubuntu server with an IP address, but no application on it yet. The tool most associated with provisioning is Terraform, which talks to cloud APIs (Application Programming Interfaces — the way programs ask a cloud provider to do things) and brings resources into existence.
When to use this: any time you need to create or destroy infrastructure — spinning up a new server, building a network, adding a database. Provisioning is about the existence and shape of the infrastructure itself.
When NOT to use it: provisioning tools are clumsy at installing packages or editing files line by line on a running server. That is not their job.
What configuration management means
Configuration management picks up where provisioning leaves off. The server now exists, but it is empty. Configuration management is the act of installing software and setting it up so the machine does something useful. This includes:
- Installing packages with
apt(Ubuntu’s package manager). - Writing config files like
/etc/nginx/sites-available/myapp. - Creating users, setting file permissions, and managing
systemdservices (Linux’s service/startup manager). - Making sure a service is running and starts on boot.
The tool most associated with this is Ansible, which connects to your existing servers over SSH (Secure Shell — an encrypted remote-login protocol) and changes their inside state — what is installed and how it is configured.
When to use this: any time you need to change what runs inside a server that already exists — install Nginx, configure PostgreSQL, deploy your app, harden security.
When NOT to use it: Ansible does not create the server for you in a clean, state-tracked way. You can call cloud APIs from Ansible, but it is not built around tracking infrastructure state the way Terraform is.
A simple way to remember it
Think of building a house:
- Provisioning is pouring the foundation and putting up the walls — making the building exist.
- Configuration management is the plumbing, wiring, and furniture — making the building livable.
You need both, and you do them in that order.
Comparison table
| Question | Provisioning | Configuration management |
|---|---|---|
| Core question | ”What infrastructure should exist?" | "What runs on it and how?” |
| Typical tool | Terraform, OpenTofu, CloudFormation | Ansible, Chef, Puppet, Salt |
| Acts on | Cloud APIs (creates resources) | Existing servers (over SSH) |
| Example task | Create an Ubuntu VM + firewall | Install Nginx, write its config |
| Tracks state? | Yes — a state file of what exists | No persistent infra state |
| Result | An empty server with an IP | A fully configured, running server |
| Runs | Once to build, again to change shape | Repeatedly to keep config correct |
How they work together
In a real workflow, you use both tools in sequence. Terraform builds the server; Ansible configures it. Here is the typical hand-off.
First, Terraform provisions an Ubuntu 24.04 server and prints its public IP:
terraform apply
Output:
aws_instance.web: Creating...
aws_instance.web: Creation complete after 32s [id=i-0a1b2c3d4e5f]
Outputs:
public_ip = "203.0.113.42"
Now the server exists but is empty. You hand that IP to Ansible, which installs and configures Nginx. A small playbook (an Ansible file describing the desired state) looks like this:
- name: Configure web server
hosts: 203.0.113.42
become: true
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: true
- name: Ensure Nginx is running and enabled
systemd:
name: nginx
state: started
enabled: true
Run it:
ansible-playbook -i inventory.ini site.yml
Output:
PLAY [Configure web server] ****************************************
TASK [Install Nginx] ***********************************************
changed: [203.0.113.42]
TASK [Ensure Nginx is running and enabled] ************************
changed: [203.0.113.42]
PLAY RECAP ********************************************************
203.0.113.42 : ok=2 changed=2 unreachable=0 failed=0
The server now serves web traffic. Terraform owns that the machine exists; Ansible owns what is installed on it.
Gotcha: Do not try to install packages or edit
/etc/nginxfrom inside Terraform using aremote-execprovisioner. It runs only once at create time, is not idempotent (re-running may not give the same result), and is officially discouraged. Use a proper configuration management tool for in-server changes.
Where the line blurs
Some setups skip configuration management entirely using immutable infrastructure — instead of changing a running server, you bake a complete machine image (a pre-built disk with all software installed) and provision a brand-new server from it every time. Here Terraform alone provisions the image-based server, and a tool like Packer builds the image up front. This trades in-place configuration for replace-and-redeploy, which is cleaner but requires rebuilding images for every change.
Best Practices
- Use Terraform for provisioning and Ansible for configuration — pick the right tool per job rather than forcing one to do both.
- Keep your Terraform state file safe and shared (for example in an S3 bucket with locking) so your team agrees on what infrastructure exists.
- Make Ansible playbooks idempotent — running them twice should leave the server in the same correct state with no errors.
- Pass server IPs or hostnames from Terraform outputs into Ansible automatically rather than copying them by hand.
- Pin versions: lock your Terraform provider versions and your Ansible collection versions so builds are repeatable.
- Avoid SSH
remote-execin Terraform for software setup; reserve Terraform for resource lifecycle only. - Consider immutable infrastructure when you want predictable, repeatable servers and can tolerate replacing instances instead of patching them.