Skip to main content

terraform plan

terraform plan = preview การเปลี่ยนแปลงก่อน apply จริง — สำคัญที่สุดของ workflow

Plan ทำอะไร?

  1. อ่าน config (.tf files)
  2. อ่าน state (.tfstate)
  3. เรียก provider API → ดูสถานะ reality
  4. คำนวณ diff: config vs state vs reality
  5. แสดง list ของ create/update/replace/destroy

Usage

terraform plan

ต้อง init ก่อน:

terraform init
terraform plan

Plan Output

Terraform will perform the following actions:

# aws_s3_bucket.data will be created
+ resource "aws_s3_bucket" "data" {
+ bucket = "my-data-bucket"
+ bucket_domain_name = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ tags_all = (known after apply)
}

# aws_instance.web will be updated in-place
~ resource "aws_instance" "web" {
id = "i-1234567890abcdef0"
~ instance_type = "t2.micro" -> "t2.small"
# (other unchanged attributes)
}

# aws_security_group.old will be destroyed
- resource "aws_security_group" "old" {
- id = "sg-12345" -> null
- name = "old-sg" -> null
}

Plan: 1 to add, 1 to change, 1 to destroy.

Action Symbols

SymbolAction
+Create (new)
~Update in-place
-/+Replace (destroy + recreate)
-Destroy
<=Read (data source)

Save Plan to File

# Save plan
terraform plan -out=tfplan

# Apply เฉพาะ plan ที่ save
terraform apply tfplan
Production Pattern
  1. terraform plan -out=tfplan (ใน CI)
  2. Save plan file as artifact
  3. Reviewer อ่าน plan ใน PR
  4. Merge → terraform apply tfplan (ใช้ plan เดิม)

→ ไม่มี surprise ระหว่าง plan และ apply

Useful Flags

-var / -var-file

terraform plan -var="environment=prod"
terraform plan -var-file="prod.tfvars"

-target (ระวังใช้)

# Plan เฉพาะ resource เดียว
terraform plan -target="aws_s3_bucket.data"

# หลายตัว
terraform plan -target="aws_vpc.main" -target="aws_subnet.public"
-target = last resort

ใช้ -target ตอน emergency เท่านั้น — ทำให้ state ออกจากสภาพปกติได้

ดีกว่า: split state files หรือ refactor code

-refresh-only

# ดูว่า reality เปลี่ยนไปจาก state มั้ย (ไม่ดู config diff)
terraform plan -refresh-only

ใช้ตรวจ drift — มีคนแก้ใน console

-destroy

# Plan การลบทุก resource
terraform plan -destroy

→ ใช้ก่อน terraform destroy

-detailed-exitcode

terraform plan -detailed-exitcode
# Exit 0 = no changes
# Exit 1 = error
# Exit 2 = changes detected

ใช้ใน CI:

- name: Plan
id: plan
run: terraform plan -detailed-exitcode -out=tfplan
continue-on-error: true

- name: Has changes?
if: steps.plan.outputs.exitcode == 2
run: echo "Changes detected — review needed"

-parallelism

# Default = 10 concurrent operations
terraform plan -parallelism=20

-no-color

terraform plan -no-color   # ปิด ANSI color (เหมาะ log)

Plan Output Formats

JSON

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
plan.json
{
"format_version": "1.2",
"resource_changes": [
{
"address": "aws_s3_bucket.data",
"type": "aws_s3_bucket",
"name": "data",
"change": {
"actions": ["create"],
"before": null,
"after": {...}
}
}
]
}

ใช้ใน CI parse:

terraform show -json tfplan | jq '.resource_changes | length'

Plan Review: สิ่งที่ต้องสังเกต

1. Total Counts

Plan: 5 to add, 3 to change, 1 to destroy.

ตัวเลข match กับที่คาดไว้ไหม?

2. Replace (-/+)

~ instance_type = "t2.micro" -> "t2.large" # forces replacement

Replace = downtime! — เช็คว่า expected ไหม

3. Destroy

- resource "aws_db_instance" "prod"

ถ้าเห็นนี่ = ALARM — ลบ database โดยไม่ตั้งใจ?

4. Sensitive Changes

~ password = (sensitive value)

ดูว่ามี secret rotated ไหม

Drift Detection

# เช็คว่า reality ตรงกับ state ไหม (ไม่อ่าน config diff)
terraform plan -refresh-only

# Output:
~ resource "aws_instance" "web" {
~ tags = {
~ "Manual" = "I added this in console!" -> null
}
}

→ มีคนเพิ่ม tag ใน console manually

ถ้าอยาก accept drift → terraform apply -refresh-only ถ้าอยาก revert drift → terraform apply (re-apply config)

ตัวอย่าง: PR Workflow

.github/workflows/terraform-plan.yml
name: Terraform Plan

on:
pull_request:
paths: ["**.tf", "**.tfvars"]

jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3

- name: Init
run: terraform init

- name: Validate
run: terraform validate

- name: Plan
id: plan
run: terraform plan -out=tfplan

- name: Comment PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const output = require('child_process').execSync('terraform show -no-color tfplan').toString();
const body = `### Terraform Plan\n\`\`\`\n${output}\n\`\`\``;
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});

Common Errors

Stale State

Error: Resource not found

→ มีคน destroy resource ใน console แล้ว state ยังมี

แก้:

terraform refresh
# หรือ
terraform state rm <RESOURCE>

Lock Error

Error: Error acquiring the state lock

→ มีคนกำลังรัน apply อยู่ หรือ lock ค้าง

แก้:

# รอให้คนนั้นเสร็จ — หรือถ้าแน่ใจว่า lock ค้าง:
terraform force-unlock <LOCK_ID>

สรุป

  • terraform plan = preview ก่อน apply
  • 4 actions: create (+), update (~), replace (-/+), destroy (-)
  • ใช้ -out=tfplan save → apply ทีหลัง = no-surprise
  • -refresh-only ตรวจ drift
  • -detailed-exitcode ใน CI ดูว่ามี change ไหม
  • เช็ค Replace + Destroy เสมอก่อน apply

ต่อไป → terraform apply