Creas un recurso en la nube desde la consola, lo anotas en un documento y nadie actualiza el documento. Seis meses después, staging diverge del repositorio y nadie sabe qué cambió ni cuándo. Terraform declara la infraestructura deseada en HCL, guarda el estado real en un backend remoto y reconcilia la diferencia en cada terraform apply. La documentación de HashiCorp (2025) trata el state file como la fuente de verdad de lo que existe.

Estado remoto y trava de lock

El state file contiene IDs, direcciones y valores sensibles; no entra al Git. Un backend S3 con lock en DynamoDB, o GCS con lock nativo, serializa el apply: dos ingenieros al mismo tiempo pelean por la trava, no por el entorno. Los entornos separan state por prefijo y por cuenta cloud: dev, staging y producción no comparten backend. Un recurso de base de datos de producción recibe prevent_destroy; el destroy se vuelve un cambio que exige quitar la protección en un PR aparte.

Módulos con dueño y versión

Un módulo encapsula de 10 a 30 recursos detrás de una interfaz corta de input y output. Kief Morris, en Infrastructure as Code (O'Reilly, 2ª ed., 2021), recomienda un módulo por capacidad: red, base de datos, servicio de cola. Un módulo con 200 inputs se vuelve un cuello de botella de revisión y una versión que nadie se atreve a subir. El registro interno publica el módulo con tag semver; el equipo consumidor fija la versión y sube por PR, con el diff de infra pasando por la misma revisión que el código.

Pipeline: plan en el PR, apply en el merge

El pull request corre terraform plan y publica el diff como comentario del PR. El merge a main corre apply. Sin ese paso, el plan vive solo en la máquina de quien aplicó, y el resto del equipo revisa de fe. Un job programado corre plan y falla cuando encuentra drift, lo que expone cambios hechos desde la consola o por otro pipeline. Usar -target fuera de emergencia esconde cambios pendientes en el state; los equipos lo evitan.

Import y migración de legado

Un recurso preexistente entra al state mediante terraform import. Un bloque moved renombra la dirección de un recurso sin destroy-and-recreate. La migración corre por partes: red crítica primero, base de datos después, capa de aplicación al final. Cada apply de migración se vuelve un PR con diff revisable. El drift que aparece en el import revela el tamaño de la deuda antes de cualquier cambio de arquitectura.