Lesson 1 of 10 · Fundamentals
Infrastructure as code: why, and with which tools
Objective
Understand what "describing your infrastructure as code" means, what it changes in a team's daily work, and where the main tools fit: OpenTofu and Terraform, Ansible, Pulumi, CloudFormation. Then you will write your very first OpenTofu configuration.
Key points
- Infrastructure as code (IaC) means describing networks, servers, firewalls, databases or DNS records in text files, versioned in Git, and then letting a tool create and maintain them.
- A declarative approach describes the desired state ("three web servers"); the tool works out the actions itself. An imperative approach describes the steps ("create a server, then a second one…").
- We distinguish provisioning (creating resources: OpenTofu, Terraform, CloudFormation, Pulumi) from configuration management (installing and tuning software inside machines: Ansible, Puppet, Chef).
- OpenTofu is an open source fork of Terraform, created in 2023 after Terraform's license change; it keeps its language (HCL) and almost all of its commands.
- A declarative tool is idempotent: run it ten times, and it only makes changes when there is a gap between the code and reality.
The problem: hand-made infrastructure
Imagine a web agency in Casablanca that hosts the websites of twenty clients. For five years, every server has been created by hand in the provider's console: one click for the machine, another for the firewall, a rule added "temporarily" one night during an outage.
The result:
- nobody knows exactly how the main client's server is configured; the administrator who built it has left;
- creating a test environment identical to production takes two days, and it is never truly identical;
- after an incident (accidental deletion, hacking), rebuilding takes a week;
- changes are not reviewed: a firewall rule open to the whole Internet goes unnoticed.
These are called "snowflake servers": each one is unique and impossible to reproduce.
The solution: describe everything in files
# reseau.tf — an extract of what you will write in this course
resource "aws_security_group" "web" {
name = "web-client-atlas"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
What you gain:
| Before (by hand) | After (IaC) |
|---|---|
| Documentation is wrong or missing | The code is the documentation |
| A change = an invisible click | A change = a commit reviewed by a colleague |
| Test does not look like production | The same code creates test and production |
| Rebuilding takes days | tofu apply recreates everything in minutes |
| "Who opened this port?" | git log answers, with the date and the reason |
IaC applies software development practices to infrastructure: version control, code review, testing, continuous integration.
Declarative or imperative
Let's compare two ways of asking for three web servers.
Imperative approach, a script:
# creer-serveurs.sh — each run CREATES three more servers
for i in 1 2 3; do
fournisseur-cli server create --name "web-$i" --type petit --image debian-12
done
If you run this script twice, you have six servers. If it fails on the second one, you have to clean up by hand. To go from three to five servers, you have to write another script.
Declarative approach, OpenTofu:
resource "fournisseur_server" "web" {
count = 3
name = "web-${count.index + 1}"
type = "petit"
image = "debian-12"
}
You describe the result. On the first run, OpenTofu creates three servers. On the second, it does nothing: reality already matches the code. If you write count = 5, it creates two. If you write count = 2, it deletes one. The tool calculates the difference.
To do this, OpenTofu keeps a state: the list of what it has created, with the real identifiers at the provider. You will study it in detail in lesson 2.1.
The tool landscape
| Tool | Category | Language | Targets | License |
|---|---|---|---|---|
| OpenTofu | declarative provisioning | HCL | any provider with an API (AWS, Azure, GCP, OVHcloud, Scaleway, Proxmox, Kubernetes, DNS…) | MPL 2.0 (open source), Linux Foundation |
| Terraform | declarative provisioning | HCL | the same | BUSL 1.1 since version 1.6 (2023) |
| Ansible | configuration management, orchestration | YAML (playbooks) | systems (Linux, Windows, network), over SSH, agentless | GPL v3 |
| Pulumi | declarative provisioning | Python, TypeScript, Go, C#… | any provider | Apache 2.0 (engine) |
| CloudFormation | declarative provisioning | YAML or JSON | AWS only | AWS service |
| Bicep / ARM | declarative provisioning | Bicep, JSON | Azure only | Microsoft service |
| Puppet, Chef | configuration management | their own DSLs | systems, with an agent | various |
| cloud-init | first-boot configuration | YAML | a new virtual machine | Apache 2.0 / GPL v3 |
A few guidelines for choosing:
- Several providers, or a local provider (OVHcloud, a Moroccan hosting company, Proxmox): OpenTofu or Terraform, thanks to their thousands of providers.
- Everything on AWS and a team that wants to stay in that ecosystem: CloudFormation is possible, but you will be locked in to AWS.
- A team of developers who prefer a real programming language (loops, unit tests): Pulumi.
- Configuring what is inside the machines (packages, files, services): Ansible. It is very often combined with OpenTofu (lesson 3.1): OpenTofu creates the machines, Ansible configures them.
OpenTofu is not a configuration management tool: it creates and deletes resources, but it does not install nginx inside a machine (it can only pass a startup script, cloud-init for example).
OpenTofu and Terraform: a little history
- 2014: HashiCorp releases Terraform under the open source MPL 2.0 license. It becomes the reference tool for multi-cloud IaC.
- August 2023: HashiCorp moves its products, including Terraform from version 1.6, to the Business Source License (BUSL 1.1). The code remains readable, but some uses that compete with HashiCorp are no longer allowed. Companies and contributors then start a fork of the MPL code, first called OpenTF.
- September 2023: the project joins the Linux Foundation under the name OpenTofu.
- January 2024: release of OpenTofu 1.6, the first stable version, compatible with Terraform 1.5/1.6.
- Since then: the two projects evolve separately. OpenTofu adds its own features (state encryption,
enabledinlifecycle,.tofufiles…), Terraform adds its own. HashiCorp was also acquired by IBM (deal completed in 2025).
In practice, for this course:
| OpenTofu | Terraform | |
|---|---|---|
| Command | tofu init, tofu plan, tofu apply |
terraform init, terraform plan, terraform apply |
| Language | HCL, .tf files (and .tofu) |
HCL, .tf files |
| Settings block | terraform { … } (same name, for compatibility) |
terraform { … } |
| Public registry | registry.opentofu.org |
registry.terraform.io |
| Providers | the same (hashicorp/aws, hashicorp/local…) |
the same |
What you learn here therefore also applies to an existing Terraform project. Moving a recent Terraform project to OpenTofu usually means backing up the state, installing tofu, then running tofu init and a tofu plan that must show no changes.
The OpenTofu workflow
write (*.tf) ──▶ tofu init ──▶ tofu plan ──▶ review ──▶ tofu apply
▲ downloads calculates the (you, applies the
│ the providers differences a colleague) changes
└──────────────── git commit, push, merge request ◀───────────────┘
- Write: you describe the resources in
.tffiles. - Plan:
tofu plancompares the code, the state and reality, then shows what would be created (+), modified (~) or destroyed (-). - Apply:
tofu applyexecutes the plan, after your confirmation.
The next lesson covers these commands in detail.
Your first file
The terraform_data resource type is provided by OpenTofu itself, with no download at all: it creates nothing at a provider, it simply keeps a value in the state. It is perfect for learning.
# /root/iac/premier/main.tf
resource "terraform_data" "bonjour" {
input = "Bonjour depuis Rabat"
}
# /root/iac/premier/outputs.tf
output "message" {
value = terraform_data.bonjour.output
}
cd /root/iac/premier
tofu init # no provider to download here
tofu apply # answer "yes"
# Outputs:
# message = "Bonjour depuis Rabat"
And what does not go into Git:
# .gitignore (in this file, a comment takes up the whole line)
# downloaded providers, recreated by tofu init
.terraform/
# the state can contain secrets: it goes into a remote backend
*.tfstate
# its backups (terraform.tfstate.backup)
*.tfstate.*
crash.log
# saved plans
*.tfplan
The .terraform.lock.hcl file, on the other hand, must be committed: it pins the exact provider versions for the whole team.
Common pitfalls
- Mixing clicks and code: a change made by hand in the console will be reverted (or cause an error) on the next
tofu apply. Once you use IaC, everything goes through the code. - Versioning the state (
terraform.tfstate) in Git: it can contain passwords in clear text, and two people overwrite each other's changes. - Believing that OpenTofu configures the software inside machines: for that, use Ansible or cloud-init.
- Going ahead without a plan: an
applywithout reading the plan can destroy a database because an argument changed.
Lab
The lab container includes OpenTofu but has no network access: you write files, and the grader reads them back. The terraform_data resource requires no download: you can even try tofu init then tofu apply -auto-approve in the folder to see the output.
- a first
terraform_dataresource; - an output that displays its value;
- the
terraformblock that requires a minimum version, with well-formatted files (tofu fmt); - the
.gitignoreof an OpenTofu project.
To write a file without an editor: cat > main.tf <<'EOF' … EOF.
Quiz
- What is the difference between a declarative approach and an imperative approach?
- You change
count = 3tocount = 5and run OpenTofu again. What happens? - OpenTofu or Ansible: which one creates the virtual machine, and which one installs nginx in it?
- Why does OpenTofu exist, and what does it keep from Terraform?
- Should you commit
terraform.tfstateto Git? And.terraform.lock.hcl?
Answers
- Declarative: you describe the desired final state and the tool works out the actions. Imperative: you write the sequence of actions to run.
- OpenTofu compares the code with the state, sees that two instances are missing and creates two, without touching the three existing ones.
- OpenTofu creates the machine (provisioning); Ansible installs and configures nginx in it (configuration management).
- It was created in 2023, under the Linux Foundation, after Terraform moved to the BUSL license; it keeps the HCL language, the commands (with
tofuinstead ofterraform) and the providers. - No for the state: it can contain secrets and must be shared through a remote backend with locking. Yes for
.terraform.lock.hcl, which pins the provider versions for the whole team.
Reference sources: OpenTofu documentation (MPL 2.0). Original MasterLinux text.