Configuration as Code
Puppet ·
Ansible ·
Terraform
— What's the difference and when to use what? —
FinistDevs · 2026
Arnaud Prémel-Cabic
Tech Lead @ OVHCloud
arnaud.premel-cabic@ovhcloud.com
"It works on my server."
Why doesn't it work here when it works everywhere else?
You have a server. It works.
Great.
You have 10 servers.
- 10 SSH sessions
- 10 manual edits
- 10 chances to make a mistake
Now you have 100 servers.
- Some 2 years old. Some a few months. Some brand new.
- None of them are exactly alike.
Unique. Unreproducible. Undocumented.
Welcome to Snowflake Hell.
Configuration drift is silent…
- Prod breaks on a Tuesday
- Can't reproduce the bug locally
- Can't scale reliably
- Can't onboard a new server without fear
What if your infrastructure was just… code?
Your AI assistant can write it*. You still need to understand what it deploys.
*Like this presentation 🤖
Configuration as Code
Machine-readable files. Version-controlled. Automated.
Meet the three musketeers of infrastructure.
Puppet ·
Ansible ·
Terraform
Each solves a different problem.
Terraform
Start here. Before you configure a server, you need to have one.
What is Terraform?
- Infrastructure as Code tool for provisioning cloud resources
- Created by HashiCorp in 2014
- Written in Go
- BSL license since 2023 (was MPL)
HCL: HashiCorp Configuration Language
Declarative, human-readable — pure JSON works too.
The Terraform workflow
terraform plan — preview what will change
- Review — validate the plan before proceeding
terraform apply — create or update resources
terraform destroy — tear everything down
Terraform remembers what it built.
- The
.tfstate file maps code to real-world resources
- Store it remotely — never commit it to Git
- May contain sensitive values: credentials, tokens, secrets
Handle with care.
One tool. Every API.
- 1000+ providers — AWS, GCP, Azure, Cloudflare, GitHub, Kubernetes…
- Not just cloud — DNS, monitoring, CI/CD, anything with an API
If it has an API, there's a Terraform provider for it.
# main.tf
terraform {
required_providers {
openstack = { source = "terraform-provider-openstack/openstack", version = "~> 3.0" }
ovh = { source = "ovh/ovh", version = "~> 1.0" }
}
}
resource "openstack_compute_instance_v2" "web" {
name = "finistdevs-web"
image_name = "Debian 12"
flavor_name = "b3-8"
network { name = "Ext-Net" }
}
resource "ovh_domain_zone_record" "web" {
zone = "example.com"
subdomain = "finistdevs"
fieldtype = "A"
target = openstack_compute_instance_v2.web.access_ip_v4
}
$ terminal
$ terraform init
Initializing provider plugins...
- Finding terraform-provider-openstack/openstack versions matching "~> 3.0"...
- Finding ovh/ovh versions matching "~> 1.0"...
- Installing terraform-provider-openstack/openstack v3.0.0...
- Installing ovh/ovh v1.3.0...
Terraform has been successfully initialized!
$ terraform plan
openstack_compute_instance_v2.web: Refreshing state...
Plan: 2 to add, 0 to change, 0 to destroy.
$ terraform apply
openstack_compute_instance_v2.web: Creating...
openstack_compute_instance_v2.web: Creation complete after 45s [id=abc-123]
ovh_domain_zone_record.web: Creating...
ovh_domain_zone_record.web: Creation complete after 3s [id=456]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
HashiCorp changed Terraform's license.
- BSL instead of MPL — no longer truly open-source
- The community responded: OpenTofu, by the OpenTF Foundation
Drop-in replacement. Fully compatible. Community-driven.
Terraform at scale needs a platform.
- Terraform Enterprise / HCP Terraform — remote state, RBAC, audit logs
- Spacelift — GitOps-first CI/CD for Terraform and OpenTofu
- Atlantis — open-source, plan & apply from pull requests
- env0, Scalr — SaaS with policy & cost management
Ansible
Your servers are provisioned. Now make them do something.
What is Ansible?
- Agentless automation tool for configuration and orchestration
- Created by Michael DeHaan in 2012
- Acquired by Red Hat in 2015
- Written in Python — Apache 2.0 license
Push-based. Runs over SSH.
- YAML playbooks run tasks in order, across any number of hosts
- Nothing to install on target servers — just Python + SSH
- Idempotent modules — same playbook runs safely again and again
# playbook/webserver.yml
- name: Configure web server
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginx
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
$ terminal
$ ansible-playbook -i inventory playbook/webserver.yml
PLAY [Configure web server] ***************************************************
TASK [Gathering Facts] ********************************************************
ok: [finistdevs-web]
TASK [Install nginx] **********************************************************
changed: [finistdevs-web]
TASK [Deploy nginx config] ****************************************************
changed: [finistdevs-web]
RUNNING HANDLER [Restart nginx] ***********************************************
changed: [finistdevs-web]
PLAY RECAP ********************************************************************
finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
Not just configuration. Operations.
- Patch 200 servers tonight
- Roll out a kernel upgrade with a canary strategy
- Run a compliance audit across your whole fleet
The go-to tool for one-off tasks and recurring operations.
The community does the heavy lifting.
- Ansible Galaxy — 10,000+ ready-made roles and collections
- Don't write a playbook to install Docker from scratch — someone already did
Just ansible-galaxy install geerlingguy.docker.
Ansible at scale: open-source vs enterprise.
- AWX — open-source web UI, API, and scheduler
- Ansible Automation Platform (Red Hat) — enterprise AWX with support
- Semaphore — lightweight open-source alternative
Core engine remains Apache 2.0 — truly open-source.
Puppet
Your servers are configured. Now keep them that way.
What is Puppet?
- Configuration management tool for enforcing system state
- Created by Luke Kanies in 2005
- Puppet Inc. acquired by Perforce in 2022
- Written in Ruby and Clojure
Pull, not push. Agents, not SSH.
- Every 30 minutes, each
puppet-agent polls the Puppet Server
- Compiles a catalog and enforces it locally
Drift is corrected automatically.
# manifests/webserver.pp
class webserver {
package { 'nginx':
ensure => installed,
}
file { '/etc/nginx/nginx.conf':
ensure => file,
content => template('webserver/nginx.conf.erb'),
notify => Service['nginx'],
}
service { 'nginx':
ensure => running,
enable => true,
}
}
$ terminal
$ puppet agent -t
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Caching catalog for finistdevs-web.example.com
Info: Applying configuration version '1713052408'
Notice: /Stage[main]/Webserver/Package[nginx]/ensure: created
Notice: /Stage[main]/Webserver/File[/etc/nginx/nginx.conf]/content:
--- /etc/nginx/nginx.conf
+++ /tmp/puppet-file20260413
Notice: /Stage[main]/Webserver/Service[nginx]/ensure: started
Notice: Applied catalog in 12.34 seconds
Someone SSH'd in and changed something.
- Puppet noticed. Puppet fixed it.
- Continuous compliance — not just at deploy time. Every. 30. Minutes.
- No manual remediation
Puppet: large fleets, zero drift.
- Continuous compliance, auditability, and guaranteed state — at scale
- Best suited for enterprises with hundreds or thousands of long-lived servers
- Fewer SaaS options than Terraform or Ansible
Puppet Enterprise and Foreman are self-hosted. No managed cloud offering.
The ecosystem outlives the company.
- Vox Pupuli — 100+ open-source Puppet modules, community-maintained
- OpenVox — an emerging open-source fork of the Puppet core
The community is strong, with or without Puppet Inc.
They're not competing. They're complementary.
Each solves a different layer of the same problem.
A common production setup:
- Terraform provisions the VM
- Ansible configures it and deploys the app
- Puppet continuously enforces compliance