D2 · Terraform Workflow

The Write → Plan → Apply cycle

Terraform's core workflow consists of three phases you need to understand in depth for the exam: what happens internally in each one, what files are generated, and how to manage the working directory.

The Write → Plan → Apply cycle

✏️

1. Write

You write .tf files in HCL describing the desired state of the infrastructure.

  • •You edit main.tf, variables.tf, outputs.tf, etc.
  • •No cloud resource is created yet
  • •It only exists on your disk as text
  • •It is versioned in Git like any other code

🔍

2. Plan

Terraform compares the desired state (code) with the and calculates the differences.

  • •Refreshes the state by querying the cloud APIs
  • •Builds the dependency graph
  • •Shows what will be created (+), destroyed (-), changed (~)
  • •Optionally saves the plan to a binary file

🚀

3. Apply

Executes the necessary cloud API calls and updates the state file with the results.

  • •Calls the 's APIs (AWS, Azure, GCP…)
  • •Creates/modifies/destroys resources in order
  • •Updates terraform.tfstate with the new state
  • •Asks for confirmation (yes) unless -auto-approve is used

Write phase: authoring configuration

During the write phase, you only edit text files. Terraform does not communicate with any cloud API during this phase. What matters is that the code is syntactically valid (checked by terraform validate) and well formatted (terraform fmt).

# main.tf — describes the DESIRED state
resource "aws_s3_bucket" "data" {
  bucket = "mi-empresa-datos-prod"
}

resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id   # reference creates an implicit dependency
  versioning_configuration {
    status = "Enabled"
  }
}

💡 Before the first init

After writing the configuration, you must run terraform init to download the providers and modules. Without init, plan and apply will fail because the plugins don't exist.

Plan phase: what is going to change

is the most important phase for the exam. Internally it performs several steps:

1

Loads the configuration

Reads every .tf file in the working directory and parses it.

2

Loads the current state

Reads terraform.tfstate (local or remote) to learn the last known real state.

3

Refreshes the state

Queries the cloud APIs to verify that the state in tfstate still matches reality (can detect ).

4

Builds the graph

Generates a DAG (directed acyclic graph) with the dependencies between resources.

5

Calculates the diff

Compares the desired configuration vs the current state and determines: + create, - destroy, ~ modify, -/+ replace.

6

Shows the plan

Prints the changes on screen and/or saves the binary plan (-out=plan.out).

SymbolMeaningExample
+Create a new resourceaws_vpc.main will be created
-Destroy an existing resourceaws_vpc.old will be destroyed
~Modify a resource in-placeaws_instance.web will be updated in-place
-/+Destroy and recreate (replace)aws_instance.web must be replaced
<=Read a data sourcedata.aws_ami.ubuntu will be read

Apply phase: real execution

executes the API calls defined in the plan. If a plan file is passed (terraform apply plan.out), Terraform uses exactly that plan without recalculating. If none is passed, it generates a new plan and asks for confirmation.

# Recommended flow in production:
terraform plan -out=tfplan    # saves the exact plan
terraform apply tfplan        # applies exactly that plan (no manual confirmation)

# Quick flow (development):
terraform apply               # generates a plan + asks for "yes"
terraform apply -auto-approve # generates a plan + applies without asking

⚠️ Apply modifies real infrastructure

Use -auto-approve only in CI/CD pipelines where you already reviewed the plan in a previous step. Never in production from your own machine without reviewing the plan first.

Working directory and .terraform/

After terraform init, the .terraform/ directory is created with everything needed to run plan and apply.

.terraform/providers/

Provider binaries downloaded from the registry. Heavy, not committed to VCS.

.terraform/modules/

Modules downloaded from remote sources (registry, GitHub, S3…).

.terraform/terraform.tfstate

Backend state (only if you use a remote backend). Not the main tfstate.

terraform.tfstate

The main state (local backend). Contains the real state of the managed infra.

terraform.tfstate.backup

Automatic backup of the tfstate prior to each apply.

.terraform.lock.hcl

for provider versions. Committed to VCS.

Lock file and what to commit to VCS

The .terraform.lock.hcl file pins the exact versions of the installed providers so that every team member uses exactly the same versions.

# .terraform.lock.hcl (generated by terraform init)
provider "registry.terraform.io/hashicorp/aws" {
  version     = "5.31.0"
  constraints = "~> 5.0"
  hashes = [
    "h1:ABC123...",   # binary hash for integrity verification
    "zh:XYZ789...",
  ]
}
File / DirectoryCommit?Reason
*.tf✅ YesIt is the source code of the infrastructure
.terraform.lock.hcl✅ YesPins exact provider versions for reproducibility
terraform.tfstate❌ NoSensitive data in plain text; use a remote backend
terraform.tfstate.backup❌ NoSame reason as tfstate
.terraform/❌ NoHeavy binaries, regenerable with terraform init
*.tfvars with secrets❌ NoCredentials, tokens, passwords
*.auto.tfvars without secrets✅ YesNon-sensitive environment variables (region, sizes, etc.)

Did you get this topic?

Test what you just learned

A developer runs terraform plan and sees a resource aws_instance marked with -/+. What does this mean and what will happen in the next apply?

Ready to test "The Write → Plan → Apply Cycle"?

Put what you learned to the test with original Terraform Associate exam questions.

Start Terraform Associate mock exam

Unlimited mock exams with the Pro plan

Log in to track your progress.