Skip to main content

Remote State

เก็บ state ไว้ที่ remote backend (S3, GCS, Azure Blob, Terraform Cloud) แทน local file — เป็น มาตรฐานของทุก production

ทำไมต้องใช้ Remote State?

❌ Local State Problems

  • ทีมหลายคน → conflict (ใครจะมี source of truth?)
  • Local disk พัง = state หาย = infrastructure ไม่ recover ได้
  • Push เข้า Git = secret leak
  • ไม่มี locking → 2 คน apply พร้อมกัน → state corrupt

✅ Remote State Solutions

  • Shared — ทีมเข้าถึงได้
  • Durable — Cloud storage redundant
  • Encrypted — at rest + in transit
  • Locking — ป้องกัน concurrent apply
  • Versioned — rollback ได้

Backend Types

BackendDescriptionLocking
s3AWS S3 ⭐ (popular)✅ DynamoDB
gcsGoogle Cloud Storage✅ built-in
azurermAzure Blob✅ built-in
remoteTerraform Cloud / Enterprise✅ built-in
consulHashiCorp Consul✅ built-in
localLocal file (default)

S3 Backend (AWS)

Setup ทีละขั้นตอน

1. สร้าง S3 Bucket (สำหรับเก็บ state)

bootstrap/main.tf
resource "aws_s3_bucket" "tfstate" {
bucket = "my-company-tfstate"
}

resource "aws_s3_bucket_versioning" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
versioning_configuration {
status = "Enabled"
}
}

resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
bucket = aws_s3_bucket.tfstate.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

resource "aws_s3_bucket_public_access_block" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

2. สร้าง DynamoDB Table (สำหรับ locking)

resource "aws_dynamodb_table" "tfstate_lock" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"

attribute {
name = "LockID"
type = "S"
}
}

3. Configure Backend ใน Project

backend.tf
terraform {
backend "s3" {
bucket = "my-company-tfstate"
key = "prod/network/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}

4. Init

terraform init

→ Terraform จะถามว่าจะ migrate state จาก local ไป S3 ไหม → yes

State Key Convention

key ใน backend = path ใน S3 bucket

key = "prod/network/terraform.tfstate"

แนะนำ pattern:

<env>/<component>/terraform.tfstate

prod/network/terraform.tfstate
prod/database/terraform.tfstate
prod/application/terraform.tfstate

staging/network/terraform.tfstate
staging/database/terraform.tfstate

dev/...

→ เห็นโครงสร้าง infra จาก S3 ตรงๆ

Backend Config — Partial Configuration

ห้าม hard-code bucket name + region ใน backend block (จะ commit เข้า Git) → ใช้ partial config

backend.tf
terraform {
backend "s3" {
# ค่อยส่งตอน init
}
}
terraform init \
-backend-config="bucket=my-company-tfstate" \
-backend-config="key=prod/network/terraform.tfstate" \
-backend-config="region=ap-southeast-1" \
-backend-config="dynamodb_table=terraform-locks"

หรือใช้ file:

prod.backend.hcl
bucket         = "my-company-tfstate"
key = "prod/network/terraform.tfstate"
region = "ap-southeast-1"
dynamodb_table = "terraform-locks"
terraform init -backend-config=prod.backend.hcl

Cross-State Reference (terraform_remote_state)

อ่าน output จาก state อื่น:

application/main.tf
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "my-company-tfstate"
key = "prod/network/terraform.tfstate"
region = "ap-southeast-1"
}
}

resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.network.outputs.subnet_ids[0]
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
}

→ Application state อ้างอิง network state ได้

Other Backends

GCS (Google Cloud Storage)

terraform {
backend "gcs" {
bucket = "my-tfstate"
prefix = "prod/network"
}
}

GCS มี locking + versioning built-in (ไม่ต้องตั้ง DynamoDB)

Azure Blob

terraform {
backend "azurerm" {
resource_group_name = "tfstate"
storage_account_name = "myorgtfstate"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}

Terraform Cloud

terraform {
cloud {
organization = "my-org"
workspaces {
name = "prod-network"
}
}
}

Migrate State

Local → Remote

  1. เพิ่ม backend block
  2. terraform init → ตอบ yes
  3. ลบ local .tfstate ทิ้ง

Remote → Remote (e.g., S3 → GCS)

  1. เปลี่ยน backend block
  2. terraform init -migrate-state

Best Practices

✅ DO:
- ใช้ remote backend ตั้งแต่ project แรก
- Enable versioning + encryption
- Setup locking (DynamoDB สำหรับ S3)
- IAM policy ที่จำกัด — เฉพาะคนที่ apply ได้
- Backup state file (S3 versioning ทำให้)

❌ DON'T:
- ห้าม commit state file
- ห้าม hard-code credentials ใน backend
- ห้าม disable encryption
- ห้าม share bucket กับ data ทั่วไป

ตัวอย่าง: Production-Grade Setup

bootstrap.tf (run ครั้งเดียว)
# S3 bucket for state
resource "aws_s3_bucket" "tfstate" {
bucket = "my-company-tfstate-${data.aws_caller_identity.current.account_id}"
}

resource "aws_s3_bucket_versioning" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
versioning_configuration {
status = "Enabled"
}
}

resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.tfstate.arn
}
}
}

resource "aws_kms_key" "tfstate" {
description = "Encryption for Terraform state"
deletion_window_in_days = 30
enable_key_rotation = true
}

# DynamoDB for locking
resource "aws_dynamodb_table" "tfstate_lock" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"

attribute {
name = "LockID"
type = "S"
}

point_in_time_recovery {
enabled = true
}
}

# Block public access
resource "aws_s3_bucket_public_access_block" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

data "aws_caller_identity" "current" {}

สรุป

  • Remote state = production standard
  • S3 + DynamoDB สำหรับ AWS, GCS สำหรับ GCP, Azure Blob สำหรับ Azure
  • ใช้ terraform_remote_state อ้างอิง state อื่น
  • Setup: bucket + versioning + encryption + locking + IAM
  • Backend config ส่วน sensitive ใช้ -backend-config

ต่อไป → State Locking