Skip to content
StackPractices
intermediate Por Mathias Paulenko

Política de Gestión de Estado de Terraform

Una política para gestionar Terraform state files: backend configuration, locking, isolation, access control, versioning y disaster recovery.

Temas: testing

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

Terraform state es el source of truth para infrastructure. Un state management policy define cómo state se storea, accede, lockea, aísla y recupera. Sin un policy, state files se vuelven un liability: concurrent modifications causan conflicts, lost state causa infrastructure drift y unencrypted state expone secrets.

When to Use

  • For alternatives, see CI/CD Pipeline Design Template.

  • Seteando up Terraform para un new project o team

  • Migrando de local a remote state

  • Estableciendo IaC governance across teams

  • Preparándote para compliance audits

  • Definiendo disaster recovery procedures para infrastructure

Solution

# Terraform State Management Policy — `<Organization Name>`

## Policy Overview

| Field | Value |
|-------|-------|
| Organization | Example Corp |
| Policy Version | 1.0 |
| Last Updated | 2026-07-05 |
| Policy Owner | DevOps Team |
| Approved By | Head of Infrastructure |
| Review Cycle | Quarterly |
| Terraform Version | 1.7+ |
| Backend | S3 + DynamoDB (AWS) |
| State Encryption | AES-256 (S3 server-side) |

## 1. Backend Configuration

### Required Backend: S3 + DynamoDB

| Component | Purpose | Configuration |
|-----------|---------|---------------|
| S3 bucket | State file storage | Encrypted, versioned, private |
| DynamoDB table | State locking | Prevent concurrent modifications |
| KMS key | Encryption key | Customer-managed, rotated annually |
| IAM policy | Access control | Least privilege per environment |

### Backend Configuration Template

```hcl
terraform {
  required_version = ">= 1.7.0"

  backend "s3" {
    bucket         = "example-tfstate-prod"
    key            = "infrastructure/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "tfstate-locks"
    kms_key_id     = "alias/terraform-state-key"
  }
}

Backend Naming Convention

EnvironmentS3 BucketState KeyDynamoDB Table
Productionexample-tfstate-prod<project>/terraform.tfstatetfstate-locks-prod
Stagingexample-tfstate-staging<project>/terraform.tfstatetfstate-locks-staging
Developmentexample-tfstate-dev<project>/terraform.tfstatetfstate-locks-dev
Sharedexample-tfstate-shared<project>/terraform.tfstatetfstate-locks-shared

S3 Bucket Configuration

SettingValueRationale
VersioningEnabledRecover desde accidental overwrites
EncryptionAES-256 (SSE-S3) o AWS KMS (SSE-KMS)Protect state contents
Public access blockAll enabledPrevenir public exposure
Bucket policyDeny non-SSL requestsEncriptá in transit
LifecycleTransition a Glacier después de 90 daysCost optimization para old versions
MFA deleteEnabled (production)Prevenir accidental deletion
Access loggingEnabledAudit access a state

DynamoDB Lock Table

SettingValueRationale
Table nametfstate-locksOne table per environment
Partition keyLockID (string)Required por Terraform
Billing modePAY_PER_REQUESTNo capacity planning needed
EncryptionAWS KMSEncriptá lock data
Point-in-time recoveryEnabledRecover desde accidental deletion

2. State Isolation

Isolation Strategy

LevelIsolation MethodRationale
EnvironmentSeparate S3 bucketsProd y dev nunca share state
ProjectSeparate state keysProjects no dependen el uno del otro
TeamSeparate AWS accountsFull isolation para compliance
RegionSeparate state per regionRegional infrastructure isolation

Workspace Policy

RuleEnforcement
No Terraform workspaces para environment isolationUsá separate state files
Workspaces allowed para parallel developmentWithin same environment only
Default workspace no debe usarse para productionNamed workspaces only

State File Structure

example-tfstate-prod/
├── networking/
│   └── terraform.tfstate
├── database/
│   └── terraform.tfstate
├── application/
│   └── terraform.tfstate
├── monitoring/
│   └── terraform.tfstate
└── security/
    └── terraform.tfstate

3. Access Control

IAM Policy: Read-Only (Plan)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::example-tfstate-prod",
        "arn:aws:s3:::example-tfstate-prod/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:*:table/tfstate-locks-prod"
    }
  ]
}

IAM Policy: Read-Write (Apply)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::example-tfstate-prod",
        "arn:aws:s3:::example-tfstate-prod/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:*:table/tfstate-locks-prod"
    }
  ]
}

Access Matrix

RolePlan (Read)Apply (Write)State PullState PushLock/Unlock
Developer✅ (dev/staging)✅ (dev/staging)✅ (dev/staging)
Senior Developer✅ (all)✅ (dev/staging)✅ (all)✅ (dev/staging)
DevOps Engineer✅ (all)✅ (all)✅ (all)✅ (dev/staging)✅ (all)
CI/CD Service Account✅ (all)✅ (all)✅ (all)✅ (all)✅ (all)
Auditor✅ (read-only)✅ (read-only)

4. State Operations

Allowed Operations

OperationCommandWhoWhen
Planterraform planDevelopers, CIAntes de apply
Applyterraform applyCI/CD, DevOpsAfter plan approval
State listterraform state listDevelopers, CIDebugging
State showterraform state show <resource>Developers, CIDebugging
State pullterraform state pullDevOps, CIDebugging, backup
State pushterraform state pushDevOps onlyEmergency recovery
State mvterraform state mvDevOps onlyResource renaming
State rmterraform state rmDevOps onlyRemove deleted resources
Force unlockterraform force-unlockDevOps onlyStuck locks

Prohibited Operations

OperationReasonAlternative
Local stateNo locking, no encryptionRemote backend
Manual state editingCorrupte stateUsá terraform state commands
State sharing entre environmentsCross-environment dependenciesSeparate state files
terraform apply sin planNo review de changesSiempre corré plan first
terraform force-unlock sin investigationConcurrent modificationsInvestigá lock owner first

State Migration Procedure

# 1. Pulleá current state
terraform state pull > backup.tfstate

# 2. Disableá old backend
terraform init -backend=false

# 3. Configurá new backend en terraform.tf
# (updateá backend block)

# 4. Initializeá con new backend
terraform init -migrate-state

# 5. Verify state
terraform state list
terraform plan  # Should show no changes

# 6. Clean up old state
# (removeá old S3 objects después de verification)

5. State Security

Sensitive Data in State

Data TypeStored en State?Mitigation
Resource IDsYesNot sensitive
Resource attributesYesSome pueden ser sensitive
Passwords/secretsYes (if no usa sensitive flag)Mark as sensitive, usá vault
Private keysYes (if created por Terraform)Usá vault, no Terraform
TLS certificatesYes (if created por Terraform)Usá ACM, no Terraform

Marking Resources as Sensitive

resource "aws_db_instance" "main" {
  username = "admin"
  password = var.db_password
  # Mark variable as sensitive
}

variable "db_password" {
  type      = string
  sensitive = true
}

# Sensitive values se hide en plan output
# pero still stored en state file

State File Access Audit

Log SourceWhat to LogRetention
S3 access logsGetObject, PutObject en state bucket1 year
CloudTrailAll API calls a S3 y DynamoDB1 year
Terraform logsPlan y apply operations90 days
CI/CD logsWho triggereó apply y cuándo1 year

6. Disaster Recovery

Backup Strategy

ComponentBackup MethodFrequencyRetention
State fileS3 versioningEvery applyAll versions retained
State fileManual exportWeekly90 days
Lock tableDynamoDB PITRContinuous35 days
Backend configGit repositoryEvery commitPer Git history

Recovery Procedures

Recover desde Accidental State Deletion

# 1. Listé previous versions
aws s3api list-object-versions \
  --bucket example-tfstate-prod \
  --prefix infrastructure/terraform.tfstate

# 2. Restoreá previous version
aws s3api copy-object \
  --copy-source example-tfstate-prod/infrastructure/terraform.tfstate?versionId=<version-id> \
  --bucket example-tfstate-prod \
  --key infrastructure/terraform.tfstate

# 3. Verify state
terraform state list
terraform plan  # Should show no changes

Recover desde Corrupted State

# 1. Pulleá current state
terraform state pull > corrupted.tfstate

# 2. Restoreá desde S3 version
aws s3api copy-object \
  --copy-source example-tfstate-prod/infrastructure/terraform.tfstate?versionId=<version-id> \
  --bucket example-tfstate-prod \
  --key infrastructure/terraform.tfstate

# 3. Importá missing resources
terraform import aws_instance.web i-1234567890abcdef0

# 4. Verify
terraform plan

Recover desde Stuck Lock

# 1. Checkeá lock info
terraform force-unlock -force <lock-id>

# 2. Verify que no otro Terraform process esté running
# (checkeá CI/CD pipelines, developer terminals)

# 3. Re-run plan
terraform plan

7. CI/CD Integration

Pipeline State Operations

StageState OperationPermissionsDuration
Initterraform initRead backend config30 sec
Planterraform planRead state, acquire lock2-5 min
Applyterraform applyRead/write state, lock5-15 min
Destroyterraform destroyRead/write state, lock5-15 min

CI/CD Best Practices

PracticeImplementationRationale
Usá service accountsIAM role para CI, no user credentialsAuditable, revocable
Plan en PR pipelineterraform plan en every PRReview changes antes de merge
Apply on mergeterraform apply después de merge a mainAutomated deployment
No manual applyAll applies through CI/CDConsistent, auditable
Plan output como PR commentBot posts plan summaryReview sin terminal
State lock timeout10 min en CI configPrevenir stuck pipelines

## Explanation

Terraform state trackea el mapping entre Terraform configuration y real infrastructure. Contiene resource IDs, attributes y a veces sensitive data. Manage state correctly es critical: lost state significa que Terraform no puede track infrastructure, corrupted state causa drift y exposed state leakea secrets.

El backend configuration define dónde state se storea. S3 con DynamoDB locking es el standard para AWS. S3 provee durable, encrypted storage con versioning. DynamoDB provee locking para prevenir concurrent modifications. Sin locking, two `terraform apply` runs pueden corrupt state.

State isolation previene cross-environment dependencies. Production y development deben tener separate state files, ideally en separate S3 buckets. Share state entre environments crea coupling: un change en dev puede romper prod. Separate state files ensure que cada environment es independent.

Access control restringe quién puede read y write state. Developers necesitan read access para `terraform plan` pero no write access para `terraform apply`. CI/CD service accounts necesitan full access para automated deployments. Auditors necesitan read-only access. IAM policies enforce estas restrictions.

State security addressea sensitive data. Terraform storea all resource attributes en state, incluyendo passwords y keys. Mark variables como sensitive para hide them en plan output, pero recordá que still están en el state file. Usá external secret stores (Vault, AWS Secrets Manager) para secrets, no Terraform variables.

Disaster recovery procedures handle state loss y corruption. S3 versioning es el primary recovery mechanism: every `terraform apply` crea un new version. Si state se accidentalmente deletea o corrupe, restoreá desde un previous version. El DynamoDB point-in-time recovery protege el lock table.

CI/CD integration automatiza state operations. Plans corren en every PR para review. Applies corren on merge a main. Manual `terraform apply` es prohibited — all changes van through CI/CD. Esto ensure consistency, auditability y previene accidental state corruption.

## Variants

| Context | Approach | Notes |
|---------|----------|-------|
| Multi-cloud | Usá Terraform Cloud o Spacelift | Managed backend, no S3/DynamoDB |
| Azure | Usá Azure Storage backend | Blob container con lease locking |
| GCP | Usá GCS backend | Bucket con object versioning |
| Self-hosted | Usá Consul o PostgreSQL backend | Para air-gapped environments |
| Large-scale | Usá Terraform Cloud workspaces | Team-based access, RBAC |
| Compliance | Addeá KMS encryption + CloudTrail | SOC 2, HIPAA requirements |

## What Works

1. Usá remote state desde day one — migrar local state es painful
2. Enableá S3 versioning — es el simplest recovery mechanism
3. Usá DynamoDB locking — concurrent applies corrupe state
4. Aislá state per environment — nunca share prod y dev state
5. Marké sensitive variables — hide secrets en plan output
6. Automatizá through CI/CD — no manual terraform apply
7. Auditá state access — sabé quién cambió infrastructure y cuándo

## Common Mistakes

1. Local state en Git — no locking, secrets en version control
2. No state locking — concurrent applies corrupe state
3. Shared state across environments — dev changes rompe prod
4. No state versioning — no podés recover desde accidental deletion
5. Secrets en state sin sensitive flag — visible en plan output
6. Manual terraform apply — inconsistent, not auditable
7. No backup strategy — state loss significa infrastructure drift

## Frequently Asked Questions

### ¿Por qué no usar Terraform workspaces para environment isolation?

Workspaces share el same state backend y configuration. Un change al backend configuration affecta all workspaces. Workspaces no proveen true isolation: un bug en un workspace puede affectar otros. Usá separate state files (different S3 keys o buckets) para environment isolation. Workspaces son fine para parallel development within el same environment.

### ¿Cómo handleamos state cuando spliteamos un monolithic Terraform project?

Usá `terraform state mv` para move resources entre states. Creá el new state file con su own backend configuration. Moveé resources desde el old state al new state. Verify both states con `terraform plan` (should show no changes). Removeé los resources desde el old configuration. Este process es reversible hasta que deleteás el old state.

### ¿Qué deberíamos hacer si terraform state está locked?

Primero, checkeá si otro Terraform process está running (CI/CD pipeline, otro developer). Si yes, esperá a que termine. Si no (el process crasheó sin release el lock), usá `terraform force-unlock`. Investigá por qué el lock no se releaseó para prevenir recurrence. Nunca force-unlock sin checkear — concurrent applies corrupe state.

### ¿Cómo migramos state entre backends?

Configurá el new backend en tu Terraform configuration. Corré `terraform init -migrate-state`. Terraform pullea el state desde el old backend y lo pushea al new backend. Verify con `terraform plan` (should show no changes). Updateá tu CI/CD configuration con el new backend. Clean up el old backend después de verification.

### ¿Deberíamos store Terraform state en Git?

No. State files contienen sensitive data (passwords, keys), cambian en every apply (creando noisy diffs) y no soportan locking (concurrent modifications). Usá un remote backend (S3, Terraform Cloud, etc.) para state. Storeá Terraform configuration (`.tf` files) en Git, no state files.