In the previous course, you created a managed database, a bucket and a container service by clicking in a console or typing commands. That works once. But how do you recreate exactly the same thing for staging? How do you know who changed a firewall rule, and when? Infrastructure as code (IaC) answers these questions: the infrastructure is described in text files, versioned in Git, reviewed in code review and applied by a tool.
Terraform (and its open source fork OpenTofu, compatible for most of this course) is the most widely used IaC tool for driving several clouds with a single language, HCL (HashiCorp Configuration Language).
A declarative approach
You do not describe the steps (“create a bucket, then enable versioning”), but the desired state (“a versioned bucket with this name exists”). Terraform compares this desired state with what exists and works out the actions itself. Re-running the same configuration does not create duplicates: if everything already matches, there is nothing to do.
A first main.tf file
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-3" # Paris
}
resource "aws_s3_bucket" "exports" {
bucket = "atelier-nova-exports-prod"
tags = {
projet = "exports"
gere_par = "terraform"
}
}
- The
terraformblock pins the version of Terraform and of the providers. - A provider is a plugin that translates your resources into API calls (AWS, Google Cloud, Azure, Scaleway, GitHub…). There are also utility providers such as
hashicorp/local(local files) andhashicorp/random(random values), perfect for practicing without a cloud account. - A
resource "TYPE" "NAME"block declares an object. Its address isTYPE.NAME, hereaws_s3_bucket.exports. The local name is used to reference it elsewhere:aws_s3_bucket.exports.arn.
The workflow
terraform init # downloads the providers, creates .terraform/ and .terraform.lock.hcl
terraform plan # computes what will change, without modifying anything
terraform apply # shows the plan, asks for "yes", then executes
terraform destroy # removes all resources managed by this configuration
The plan is the key moment. Read its symbols:
| Symbol | Meaning |
|---|---|
+ |
creation |
~ |
in-place modification |
-/+ |
destroy then recreate (replacement) |
- |
destruction |
It ends with a summary such as Plan: 1 to add, 0 to change, 0 to destroy. An unexpected replacement (-/+) on a database means data loss: this is exactly what reading the plan should catch. The # forces replacement note tells you which attribute causes the replacement.
The .terraform.lock.hcl file records the exact provider versions: it is committed to Git, so that the whole team and CI use the same ones. The .terraform/ folder is not committed.
The state file
After an apply, Terraform writes terraform.tfstate, a JSON file that maps each address (aws_s3_bucket.exports) to the real object (its identifier at AWS) and its attributes. Without it, Terraform would not know the bucket already exists and would try to create it again.
terraform state list # managed addresses
terraform state show aws_s3_bucket.exports # known attributes of a resource
Three rules:
- Never edit the state by hand: use the
terraform statecommands or themovedandimportblocks (lesson 5). - Never commit it to Git: it often contains sensitive values (generated passwords, keys) in plain text.
- Never lose it: it is the memory of your infrastructure.
A locked remote backend
On a single machine, the state is a local file. In a team, you put it in a shared remote backend, encrypted and versioned, with a lock: if Alice runs an apply, Bruno cannot run a second one at the same time and corrupt the state.
terraform {
backend "s3" {
bucket = "atelier-nova-tfstate"
key = "plateforme/prod/terraform.tfstate"
region = "eu-west-3"
encrypt = true
use_lockfile = true # native lock in S3 (recent Terraform versions)
}
}
Older configurations use a DynamoDB table for the lock (dynamodb_table); the Google Cloud gcs backend locks natively. The state bucket itself is created once, separately (often by a small dedicated configuration), with versioning enabled so you can restore a previous state. After adding or changing a backend, run terraform init again: Terraform offers to migrate the existing state.
Exercises
Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.
First full cycle with the local provider
In an empty folder
tf-intro, createmain.tfwith thehashicorp/localprovider (version~> 2.5) and alocal_fileresource namedhello, with path${path.module}/hello.txtand content"Bonjour Terraform\n". Runterraform init,terraform plan,terraform apply(answeryes), thencat hello.txtandterraform state list.- `terraform init` ends with "Terraform has been successfully initialized!" and creates `.terraform.lock.hcl`
- The plan shows the exact line `Plan: 1 to add, 0 to change, 0 to destroy.`
- `cat hello.txt` prints `Bonjour Terraform`
- `terraform state list` prints exactly `local_file.hello`
- A `terraform.tfstate` file has appeared in the folder
Read a replacement, then destroy everything
In
tf-intro, change the content oflocal_file.helloto"Bonjour Terraform, version 2\n". Runterraform planand spot the symbol used, then apply. Finally runterraform destroyand confirm.- The plan marks `local_file.hello` with `-/+` and the note `# forces replacement` on `content`
- The plan summary is exactly `Plan: 1 to add, 0 to change, 1 to destroy.`
- After apply, `cat hello.txt` prints `Bonjour Terraform, version 2`
- `terraform destroy` ends with `Destroy complete! Resources: 1 destroyed.` and hello.txt no longer exists
- `terraform state list` prints nothing anymore
What should be committed to Git?
Among these elements of a Terraform project, which one should be committed to Git?
- The `.terraform/` folder
- The `terraform.tfstate` file
- The `.terraform.lock.hcl` file
- The `terraform.tfstate.backup` file
End-of-lesson quiz
What does
terraform plando?- It creates the resources
- It computes and displays the necessary actions without modifying anything
- It downloads the providers
What does the
-/+symbol mean in a plan?- In-place modification
- Destruction then recreation of the resource
- Resource ignored
Why lock the state in a remote backend?
- To make it faster
- To prevent two simultaneous applies from corrupting the state
- To encrypt it automatically in Git
The rest of the course
- Variables, outputs, locals and loops18 min
- Modules: reuse and compose18 min
- Managing multiple environments16 min
- Plan review, CI and evolving existing infrastructure20 min