Deployment Workflow
Workflow patterns สำหรับ deploy Terraform อย่างปลอดภัย — ใน team ใหญ่/หลาย environment
Workflow Components
Standard Workflow
1. Local Development
# Branch
git checkout -b feature/add-monitoring
# Edit
vim main.tf
# Format + Validate
terraform fmt
terraform validate
# Plan locally
terraform plan
2. Open PR
git add .
git commit -m "feat: add CloudWatch monitoring"
git push origin feature/add-monitoring
gh pr create
3. CI Runs
- ✅ Format check
- ✅ Validate
- ✅ Lint (TFLint)
- ✅ Security scan (Checkov)
- ✅ Plan → comment on PR
4. Code Review
Reviewer ดู:
- Code quality
- Plan output (most important!)
- Security implications
- Cost impact (Infracost)
5. Merge → Auto Apply Dev
.github/workflows/deploy.yml
on:
push:
branches: [main]
jobs:
apply-dev:
runs-on: ubuntu-latest
environment: dev
steps:
- run: terraform apply -auto-approve
6. Smoke Tests Dev
# Auto run after apply
curl -f https://dev-app.example.com/health
7. Manual Approval → Prod
apply-prod:
needs: apply-dev
environment: production # ← require approval
steps:
- run: terraform apply -auto-approve
GitHub: Settings → Environments → production → Required reviewers
Promotion Strategy
Strategy 1: Same Code, Different Vars
infra/
├── main.tf # shared
├── envs/
│ ├── dev.tfvars
│ ├── staging.tfvars
│ └── prod.tfvars
# Same plan logic, different config
terraform apply -var-file=envs/dev.tfvars
terraform apply -var-file=envs/staging.tfvars
terraform apply -var-file=envs/prod.tfvars
Strategy 2: Same Module, Different Caller
infra/
├── modules/
│ └── web-app/
└── envs/
├── dev/main.tf # uses module
├── staging/main.tf # uses module
└── prod/main.tf # uses module
→ More flexibility per env
Strategy 3: Workspaces (Same Code)
terraform workspace select dev
terraform apply
terraform workspace select prod
terraform apply
Pull Request Patterns
Atlantis (Open-Source TF Pull Request Bot)
Atlantis = bot ที่ run plan/apply ตาม PR comment
atlantis.yaml
version: 3
projects:
- name: prod-network
dir: prod/network/
workflow: prod
- name: dev-network
dir: dev/network/
workflow: dev
workflows:
prod:
plan:
steps: [init, plan]
apply:
steps: [apply]
ใน PR comment:
atlantis plan -p prod-network
atlantis apply -p prod-network
Terraform Cloud
UI-based workflow:
- VCS connection
- Auto plan on PR
- Manual apply
- State management built-in
Locking & Coordination
State Lock (DynamoDB)
ป้องกัน concurrent apply (ดูเพิ่ม)
Branch Protection
GitHub: Settings → Branches
- Require PR before merge
- Require status checks (CI must pass)
- Require approval count (1+ reviewers)
- Restrict who can push to main
Environment Protection
- Require manual approval for prod
- Restrict deploy to specific reviewers
- Wait timer before deploy
Rollback Strategy
Terraform ไม่มี built-in rollback — กลยุทธ์:
Option 1: Git Revert + Apply
git revert <commit>
git push
# CI: terraform apply (กลับสู่ state ก่อนหน้า)
Option 2: Apply Old Plan
# Save plan ทุก apply
terraform plan -out=plan-$(date +%s).tfplan
# ถ้าจะ rollback — apply plan เก่า
terraform apply plan-1234567890.tfplan
Option 3: Restore State Version
# S3 versioning
aws s3api list-object-versions --bucket my-tfstate
aws s3api copy-object \
--copy-source "my-tfstate/prod.tfstate?versionId=abc" \
--bucket my-tfstate \
--key prod.tfstate
terraform apply
Restore = Drift Risk
Restore state เก่า ≠ rollback resources ต้อง re-apply เพื่อให้ reality ตรง state เก่า
Blue/Green Deployment
resource "aws_alb_target_group" "blue" {
name = "app-blue"
}
resource "aws_alb_target_group" "green" {
name = "app-green"
}
resource "aws_alb_listener" "main" {
default_action {
type = "forward"
target_group_arn = var.active_color == "blue" ? aws_alb_target_group.blue.arn : aws_alb_target_group.green.arn
}
}
Switch traffic:
terraform apply -var="active_color=green"
Canary Deployment
resource "aws_alb_listener_rule" "canary" {
listener_arn = aws_alb_listener.main.arn
action {
type = "forward"
forward {
target_group {
arn = aws_alb_target_group.stable.arn
weight = var.canary_percentage == 0 ? 100 : 100 - var.canary_percentage
}
target_group {
arn = aws_alb_target_group.canary.arn
weight = var.canary_percentage
}
}
}
}
terraform apply -var="canary_percentage=10" # 10% to canary
# Monitor metrics
terraform apply -var="canary_percentage=50" # promote 50%
terraform apply -var="canary_percentage=100" # full rollout
Pipelines for Multiple Stacks
.github/workflows/multi-stack.yml
jobs:
network:
runs-on: ubuntu-latest
steps:
- run: |
cd network
terraform apply -auto-approve
data:
needs: network
runs-on: ubuntu-latest
steps:
- run: |
cd data
terraform apply -auto-approve
compute:
needs: data
runs-on: ubuntu-latest
steps:
- run: |
cd compute
terraform apply -auto-approve
apps:
needs: compute
runs-on: ubuntu-latest
steps:
- run: |
cd apps
terraform apply -auto-approve
Drift Detection
on:
schedule:
- cron: '0 9 * * *' # daily at 9 AM
jobs:
drift:
runs-on: ubuntu-latest
steps:
- run: terraform plan -refresh-only -detailed-exitcode
- name: Notify if drift
if: failure()
run: |
curl -X POST $SLACK_WEBHOOK \
-d '{"text": "⚠️ Drift detected in prod"}'
Cost Estimation
ใช้ Infracost ใน PR:
- name: Generate Cost Estimate
uses: infracost/actions/setup@v3
- run: |
infracost breakdown --path . --format json --out-file infracost.json
infracost comment github --path infracost.json \
--pull-request ${{ github.event.pull_request.number }}
→ PR แสดงค่าใช้จ่ายต่อเดือน
Best Practices
✅ DO:
- Plan ใน PR, apply on merge
- Manual approval สำหรับ prod
- Smoke tests หลัง deploy
- Drift detection daily
- Cost estimation in PR
- Document deploy order
- State lock + branch protection
❌ DON'T:
- ห้าม direct apply ใน prod จาก laptop
- ห้าม skip plan review
- ห้าม -auto-approve ใน prod terminal
- ห้าม commit credentials
- ห้าม destructive ops โดยไม่ approval
ตัวอย่าง Mature Workflow
.github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths: ["**.tf"]
push:
branches: [main]
paths: ["**.tf"]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform fmt -check -recursive
- run: terraform init -backend=false
- run: terraform validate
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: terraform-linters/setup-tflint@v4
- run: tflint --init && tflint --recursive
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bridgecrewio/checkov-action@master
cost:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: infracost/actions/setup@v3
- run: |
infracost breakdown --path .
infracost comment github --pull-request ${{ github.event.pull_request.number }}
plan:
runs-on: ubuntu-latest
needs: [validate, lint, security]
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -out=tfplan
- run: terraform show -json tfplan > plan.json
- uses: actions/upload-artifact@v4
with:
name: plan
path: tfplan
apply-dev:
runs-on: ubuntu-latest
needs: plan
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment: dev
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with: { name: plan }
- uses: hashicorp/setup-terraform@v3
- run: terraform apply -auto-approve tfplan
smoke-dev:
runs-on: ubuntu-latest
needs: apply-dev
steps:
- run: curl -f https://dev-app.example.com/health
apply-prod:
runs-on: ubuntu-latest
needs: smoke-dev
environment: production # require manual approval
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform apply -auto-approve
smoke-prod:
runs-on: ubuntu-latest
needs: apply-prod
steps:
- run: curl -f https://app.example.com/health
สรุป
- Workflow: dev (auto) → smoke → prod (manual approval) → smoke
- ใช้ Atlantis หรือ Terraform Cloud สำหรับ PR-driven workflow
- Branch protection + state lock + environment approval
- Rollback: git revert + apply (Terraform ไม่มี built-in rollback)
- Drift detection scheduled
- Cost estimation in PR
ต่อไป → Version Management