Hook: Three commands stand between your infrastructure and disaster
Every day, you'll run the same three Terraform commands: init, plan, and apply. Getting them right is the difference between a controlled, auditable infrastructure change and a production outage that starts at 2 AM. The Terraform workflow: init, plan, apply for production is not just a sequence—it's a discipline that forces you to pause, review, and think before touching infrastructure that serves real users.
The Terraform workflow: always review before applying
The safest way to work with Terraform is to separate the phases of initialization, validation, planning, and execution. This separation gives you checkpoints where you can catch mistakes, collaborate with teammates, and maintain an audit trail. Skip any step, and you risk surprising changes that break production.
Here's how the workflow breaks down:
- terraform init downloads providers and initializes the working directory
- terraform fmt and validate ensure your code is formatted and syntactically correct
- terraform plan shows you exactly what will change
- Review the plan with your team or manually inspect it
- terraform apply makes the changes to your infrastructure
Let's say you're adding a new EC2 instance to your staging environment. You start with init:
terraform initThis downloads the AWS provider (or whichever provider you've declared) and creates a .terraform directory with lock files. On the next run, init recognizes the providers are already installed and prepares your working directory. Then you validate:
terraform fmt
terraform validateFormat the code, check for syntax errors, and ensure required arguments are present. This catches typos before they reach the plan stage. Next, you plan:
terraform planThis queries your cloud provider's API, compares your code to the real infrastructure state, and tells you what will happen. It does not make any changes. You read the output carefully, ask yourself "is this what I expect?", and only then proceed to apply. This discipline prevents accidents.
Save plans: terraform plan -out=tfplan and apply exactly that plan
In a team environment, you should never run terraform plan in one terminal, then terraform apply in another without explicitly linking them. Why? Because between your plan and your apply, a colleague might merge a Terraform change, or your cloud provider might have created a new resource outside of Terraform. The plan you reviewed no longer matches the plan that apply will execute.
The solution is to save the plan to a file and apply that exact plan:
terraform plan -out=tfplan
terraform apply tfplanWhen you run terraform plan -out=tfplan, Terraform writes a binary snapshot of the plan to disk. This file is locked to your current state and code. When you later run terraform apply tfplan, Terraform reads that exact plan and executes it. No surprises, no in-between changes.
In practice, your workflow in a CI/CD pipeline might look like this:
- Developer pushes a branch with Terraform changes
- CI system runs
terraform init,terraform fmt -check,terraform validate, andterraform plan -out=tfplan - CI posts the plan as a comment on the pull request
- Team reviews the plan
- On merge, CD system runs
terraform apply tfplanagainst the saved file
This approach ensures that what you reviewed in the pull request is exactly what gets applied to production. No drift, no guessing.
Read the symbols: + create, ~ update, - destroy, -/+ replace
When you run terraform plan, the output uses symbols to show what Terraform will do to each resource. Learning to read these symbols quickly is essential.
| Symbol | Meaning | Example |
|---|---|---|
+ |
Create | + aws_instance.app — a new EC2 instance will be created |
~ |
Update (in-place) | ~ aws_security_group.web — security group rules will be updated without replacing the resource |
- |
Destroy | - aws_s3_bucket.old_logs — S3 bucket will be deleted |
-/+ |
Replace | -/+ aws_instance.app — instance will be destroyed and recreated (often due to immutable properties) |
The key distinction is between update and replace. When Terraform shows ~, it modifies the resource in place—your EC2 instance stays up, your database keeps running. When Terraform shows -/+, it destroys and recreates. For a stateless service, this might be fine. For a database, this is catastrophic.
Consider this scenario: you have a Terraform block for an RDS database with allocated_storage = 100. You want to expand to 200 GB:
resource "aws_db_instance" "primary" {
allocated_storage = 200 # changed from 100
instance_class = "db.t3.medium"
engine = "postgres"
identifier = "production-db"
skip_final_snapshot = false
# ...
}When you run terraform plan, you'll see:
~ aws_db_instance.primary
~ allocated_storage = 100 -> 200Good news: ~ means AWS will expand storage in-place. Your database stays online.
But change the engine from postgres to mysql, and you'll see:
-/+ aws_db_instance.primary
~ engine = "postgres" -> "mysql"Now you have -/+, which means destroy and recreate. Your production data vanishes. This is when you stop, revert the change, and think of a migration strategy.
Never skip the plan: surprise replacements cause outages
It's tempting to run terraform apply without reviewing the plan, especially when you're in a hurry or confident the change is small. Don't. We've all heard the story: a developer forgot to set skip_final_snapshot = true on a load balancer security group, Terraform noticed a small difference, and decided to replace the security group. Every connection dropped. The site went down for five minutes.
The plan is your last defense. It runs free—no cost, no risk. It takes 30 seconds to read. It has caught countless bugs in production pipelines.
Make it a habit: always run plan first, always read the output, and always ask "why is this resource being replaced?" If the reason surprises you, that's a signal to stop and investigate. Often you'll find:
- A typo in your code that changes an immutable property
- A provider version upgrade that changed how it interprets your configuration
- A resource argument you didn't realize was forcing replacement
- State drift (your real infrastructure differs from your state file)
None of these are catastrophic if you catch them in the plan. They're all catastrophic if you skip straight to apply.
Practical example: rolling out a new ALB listener
Let's walk through a real scenario. Your team wants to add HTTPS support to your Application Load Balancer. You update your Terraform code:
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
certificate_arn = aws_acm_certificate.main.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}You run:
terraform init
terraform fmt
terraform validate
terraform plan -out=tfplanThe plan shows:
+ aws_lb_listener.https
+ arn = (known after apply)
+ load_balancer_arn = "arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/main/1234567890"
+ port = 443
+ protocol = "HTTPS"
+ ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
+ certificate_arn = "arn:aws:acm:us-east-1:123456:certificate/abcdef"
Plan: 1 to add, 0 to change, 0 to destroy.Perfect. You're creating a new listener (the + symbol), not modifying or replacing anything. No existing connections will be affected. You save this plan, share it with your team, get approval, and apply it:
terraform apply tfplanDone. The listener is live, and you have a clear audit trail of what changed and when.
Common mistakes
Mistake 1: Running apply without a saved plan in CI/CD. Between the plan and apply, someone pushes a new change. Now you're applying a different plan than you reviewed. Always save and apply the exact plan file.
Mistake 2: Ignoring replace symbols. You see -/+ and apply anyway, assuming "it's just a small change." Your stateful resource gets destroyed. Check why the replace is happening—often it's a configuration mistake.
Mistake 3: Skipping validation. You type terraform apply directly. Terraform then fails during apply because of a syntax error or missing argument. The state becomes partially modified. Always run format and validate first.
Mistake 4: Not checking the plan count. The plan says "Plan: 50 to add, 100 to change, 10 to destroy." That's huge. But you're just renaming a variable. Something is wrong—investigate before applying.
Key takeaways
- Always follow the sequence: init → fmt/validate → plan → review → apply. Skipping steps invites accidents.
- Save plans to a file and apply the exact file in production. This prevents in-between changes from affecting your deployment.
- Learn the symbols.
+and~are usually safe.-and-/+demand scrutiny. - Never apply without reviewing the plan. It takes 30 seconds and catches 90% of mistakes.
- If the plan surprises you, stop and investigate. The plan is your early warning system.






Comments
Loading comments…