Skip to content
ExponaBLOG
TERRAFORM, IAC & CLOUD AUTOMATION · DAY 003 / 100

The Terraform workflow: init, plan, apply for production

Three commands you will run every day.

Expona Admin7 min read

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:

  1. terraform init downloads providers and initializes the working directory
  2. terraform fmt and validate ensure your code is formatted and syntactically correct
  3. terraform plan shows you exactly what will change
  4. Review the plan with your team or manually inspect it
  5. 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:

hcl
terraform init

This 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:

hcl
terraform fmt
terraform validate

Format the code, check for syntax errors, and ensure required arguments are present. This catches typos before they reach the plan stage. Next, you plan:

hcl
terraform plan

This 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:

hcl
terraform plan -out=tfplan
terraform apply tfplan

When 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:

  1. Developer pushes a branch with Terraform changes
  2. CI system runs terraform init, terraform fmt -check, terraform validate, and terraform plan -out=tfplan
  3. CI posts the plan as a comment on the pull request
  4. Team reviews the plan
  5. On merge, CD system runs terraform apply tfplan against 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:

hcl
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:

hcl
~ aws_db_instance.primary
    ~ allocated_storage = 100 -> 200

Good news: ~ means AWS will expand storage in-place. Your database stays online.

But change the engine from postgres to mysql, and you'll see:

hcl
-/+ 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:

hcl
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:

hcl
terraform init
terraform fmt
terraform validate
terraform plan -out=tfplan

The plan shows:

hcl
+ 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:

hcl
terraform apply tfplan

Done. 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.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles