Riadh Mnasri
← Back to blog
3 min read

Getting started with Terraform: what the tutorial doesn't cover

Terraform's HCL syntax is learned in an hour: declaring a resource, a provider, a variable. What takes much longer to truly understand, and what most tutorials barely touch on, is state: the file that links the declaration to reality.

What Terraform does, in one sentence

Terraform compares three things on every run: what you declared (the code), what it thinks it already created (the state), and what actually exists at the cloud provider. A terraform plan computes the difference between these three sources; a terraform apply applies it.

resource "aws_s3_bucket" "frontend" {
  bucket = "my-app-frontend"
}

This block doesn't create anything on its own: it declares an intent. It's the state that turns that intent into a record of what actually exists.

   code (.tf)              state (.tfstate)          cloud provider
  ┌─────────────┐         ┌─────────────────┐         ┌─────────────────┐
  │ declared     │  plan  │ last state       │         │ resources       │
  │ intent       │◄──────►│ known to         │◄───────►│ that actually   │
  │              │        │ Terraform        │  apply  │ exist           │
  └─────────────┘         └─────────────────┘         └─────────────────┘

plan compares the three columns and shows the gap; apply makes the state and the cloud converge toward what the code says.

The real subject: state, not syntax

State is a JSON file that maps every declared resource to its real identifier at the cloud provider. The problem isn't understanding it in theory, it's understanding what happens when it drifts from reality: someone manually changes a resource in the AWS console, and the state doesn't know. On the next plan, Terraform proposes to "fix" that manual change, potentially by destroying it.

That's why state should never stay local on a developer's machine, even on a solo project: a crashed disk, a file accidentally committed, and the infrastructure's history is lost or inconsistent.

Remote storage from day one

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "env/prod/terraform.tfstate"
    region         = "eu-west-3"
    dynamodb_table = "terraform-locks"
  }
}

The S3 bucket stores the state; the DynamoDB table manages the lock that prevents two simultaneous runs from corrupting the same state. On MissionMatch, I set up this configuration from day one, even solo: it isn't a question of scale, it's a question of discipline. It removes "works on my machine" from the equation as soon as infrastructure is involved.

What remains a real trap for a beginner

The most common trap isn't the syntax, it's confusing plan and apply under pressure: applying a change without carefully reading the displayed plan, especially the lines indicating a destruction rather than a simple modification.

SymbolMeaningVigilance level
+Creating a new resourceLow, generally low risk
~In-place modification, no recreationMedium, depends on the attribute changed
-/+Destroy then recreate (often an immutable attribute changing)High, the resource is briefly gone during replacement
-Pure deletionMaximum, especially on a stateful resource

The -/+ case is the one that surprises people most while learning Terraform: a change that looks harmless in the code (say, an EC2 instance's AMI id) can force a full destroy-and-recreate if that attribute can't be modified in place at the cloud provider, with nothing in the code itself signaling that before reading the plan.

On a stateful resource (a database, a bucket with data), an unanticipated destruction isn't recoverable like a code rollback.

What I take from learning it

Learning Terraform seriously isn't about memorizing provider syntax: it's about understanding that every apply modifies a shared, persistent system, unlike an application deployment you can redo at will. That difference in nature is what justifies reading every plan before applying it, even on a personal project where "it doesn't matter." On MissionMatch, every module and both environments pass terraform validate, but nothing has been apply'd yet, precisely because I prefer validating the structure before running anything against a real cloud account.