Terraform Lifecycle Rules explicadas con ejemplos reales

Las Terraform Lifecycle Rules son meta-argumentos que controlan cómo Terraform crea, actualiza, reemplaza y destruye cada recurso. Con ellas evitás dos cosas que te arruinan el día: el downtime cuando actualizás un recurso y el borrado accidental de una base de datos de producción. Se declaran dentro de un bloque lifecycle en el propio recurso.

En 30 segundos

  • create_before_destroy = true: crea el recurso nuevo antes de destruir el viejo, cero downtime.
  • prevent_destroy = true: hace fallar el apply si Terraform intenta borrar el recurso. Ideal para RDS y buckets con datos.
  • ignore_changes: le decís a Terraform que ignore atributos que cambian por fuera (autoscaling, versiones que actualiza el proveedor).
  • replace_triggered_by: fuerza el reemplazo de un recurso cuando cambia otro del que depende.
  • Todo se declara dentro del bloque lifecycle, y el orden de precedencia importa cuando combinás reglas.

Los meta-argumentos de lifecycle en Terraform son controles nativos que se escriben dentro de un bloque lifecycle en cualquier recurso. Le indican al motor de Terraform cómo comportarse durante el ciclo de vida del recurso: si crear antes de destruir, si proteger contra borrado, si ignorar cambios externos o si validar condiciones antes y después del apply. Son parte del lenguaje HCL de HashiCorp, sin plugins.

¿Por qué son críticas las reglas de lifecycle en Terraform?

Ponele que cambiás el instance_type de una EC2 o el nombre de un recurso que tiene restricción de unicidad. Terraform mira el plan, decide que ese cambio no se puede hacer “en caliente” y arma un destroy seguido de un create. En otras palabras: tu servicio se cae unos segundos (o minutos) mientras el recurso viejo desaparece y el nuevo se levanta.

Hay tres dolores clásicos acá. El primero es el downtime en cada actualización. El segundo es la destrucción accidental: un terraform apply apurado y adiós base de datos. El tercero es más silencioso: alguien toca un recurso desde la consola de AWS, Terraform lo detecta como “drift” y en el próximo apply lo revierte sin preguntar. Las reglas de lifecycle existen para meterle criterio a ese comportamiento por defecto. Ya lo cubrimos antes en como parte de tu pipeline CI/CD.

¿Cuáles son los meta-arguments de lifecycle disponibles?

Terraform tiene cinco meta-arguments de lifecycle que cubren desde el orden de reemplazo hasta la validación de configuraciones. Cada uno resuelve un problema distinto y algunos se pueden combinar. Esta es la comparativa rápida.

Meta-argumentQué haceCuándo usarloImpacto en el plan
create_before_destroyCrea el nuevo antes de destruir el viejoRecursos que reemplazan seguido y no toleran caídaInvierte el orden de reemplazo
prevent_destroyBloquea cualquier destroy del recursoBases de datos, buckets con estado, storage críticoHace fallar el apply con error
ignore_changesIgnora cambios en atributos específicosAutoscaling manual, versiones que actualiza el proveedorSaca esos atributos del diff
replace_triggered_byFuerza reemplazo si cambia otra referenciaRecrear un recurso cuando cambia su dependenciaAgrega un reemplazo al plan
precondition / postconditionValida supuestos antes y despuésChequear AMIs, tamaños, tags obligatoriosAborta si la condición falla
terraform lifecycle rules diagrama explicativo

¿Cómo usar create_before_destroy para cero downtime?

El problema es simple. Por defecto Terraform destruye primero y crea después, así que hay una ventana en la que el recurso no existe. Con create_before_destroy = true invertís ese orden: primero levanta el nuevo, y recién cuando está listo, mata el viejo.

resource "aws_instance" "web" {
 ami = var.ami_id
 instance_type = "t3.micro"

 lifecycle {
 create_before_destroy = true
 }
}

Ojo con una limitación que engancha a mucha gente: no funciona bien con recursos que exigen nombres únicos, como buckets S3 o roles IAM. ¿Por qué? Porque no podés tener dos con el mismo nombre al mismo tiempo, y el patrón create_before_destroy justamente los solapa. La salida ahí es usar name_prefix en lugar de name, para que Terraform genere un sufijo distinto y ambos convivan durante la transición.

¿Qué hace prevent_destroy y cuándo usarlo?

Esta es la red de seguridad. Con prevent_destroy = true, si Terraform arma un plan que incluye destruir ese recurso, el apply falla con un error explícito en vez de ejecutar el borrado. Más contexto en tu estrategia de automatización.

Va perfecto para bases de datos RDS, buckets con el estado de Terraform o cualquier storage que no querés perder por un descuido. Según la documentación de HashiCorp, el error aparece durante el plan, así que ni siquiera llegás a la parte peligrosa.

Eso sí: prevent_destroy no evita los cambios de configuración que provocan una recreación. Si modificás un atributo que obliga a reemplazar el recurso, el error te va a frenar igual, pero no es la herramienta para “permitir el cambio sin recrear”. Para eso está ignore_changes.

¿Cómo usar ignore_changes para permitir modificaciones externas?

Escenario típico: tenés un Auto Scaling Group y alguien ajustó el desired_capacity a mano desde la consola de AWS porque hubo un pico de tráfico. En el próximo apply, Terraform ve esa diferencia y la revierte al valor del código. Un desastre en plena carga.

resource "aws_autoscaling_group" "app" {
 min_size = 2
 max_size = 10

 lifecycle {
 ignore_changes = [desired_capacity]
 }
}

Con esa línea, Terraform saca desired_capacity del diff y deja que el autoscaling haga su trabajo. El otro caso muy común es RDS con engine_version: si AWS aplica una actualización menor de forma automática, agregás ese atributo a ignore_changes y evitás que Terraform quiera “corregir” la versión en cada corrida.

¿Cuándo usar replace_triggered_by en Terraform?

replace_triggered_by fuerza el reemplazo de un recurso cuando cambia otro del que depende, aunque el recurso en sí no haya cambiado. El ejemplo clásico: recrear una instancia cuando cambia el ID de la AMI, o rotar un recurso cada vez que se actualiza una configuración asociada. Esto se conecta con lo que analizamos en para gestionar múltiples versiones.

resource "aws_instance" "app" {
 ami = var.ami_id

 lifecycle {
 replace_triggered_by = [
 aws_launch_template.app.latest_version
 ]
 }
}

¿En qué se diferencia de depends_on? depends_on solo define el orden en que se crean o destruyen los recursos. replace_triggered_by va más allá: dispara un reemplazo real. Uno ordena, el otro recrea.

Errores comunes al usar lifecycle rules

  • Poner create_before_destroy en un bucket S3. Falla por el nombre único. Usá name_prefix o aceptá una ventana de reemplazo secuencial.
  • Combinar create_before_destroy con prevent_destroy sin pensarlo. Son intenciones opuestas (una reemplaza, la otra bloquea) y el resultado te confunde en el plan.
  • No poner prevent_destroy en producción. Un refactor mal hecho puede meter un destroy de la base sin que te des cuenta hasta que es tarde.
  • Abusar de ignore_changes. Si ignorás demasiados atributos, tu código deja de reflejar la realidad y el estado empieza a divergir. Terraform deja de ser la fuente de verdad.
  • Creer que prevent_destroy frena todo. No frena los cambios que causan recreación. Para eso necesitás ignore_changes sobre el atributo culpable.

Ejemplo de producción: proteger una base de datos RDS

Este es el patrón que conviene tener en cualquier RDS que importe. Protegés contra borrado, permitís que AWS actualice la versión del motor y dejás el cifrado activo.

resource "aws_db_instance" "produccion" {
 identifier = "app-prod"
 engine = "postgres"
 instance_class = "db.t3.medium"
 storage_encrypted = true

 lifecycle {
 prevent_destroy = true
 ignore_changes = [engine_version]
 }
}

La lógica: prevent_destroy evita el borrado accidental, ignore_changes = [engine_version] deja que AWS aplique parches menores sin que Terraform pelee, y storage_encrypted mantiene los datos cifrados. Cuando de verdad necesites destruir la base (una migración, por ejemplo), primero sacás la línea prevent_destroy, corrés el apply y recién ahí ejecutás el destroy. Es un paso extra a propósito, para que borrar nunca sea automático.

Si administrás esta infraestructura para clientes y necesitás hosting o servidores en Argentina donde apoyar tu stack, donweb.com te resuelve la capa de infra local mientras Terraform maneja el ciclo de vida por código.

Preguntas Frecuentes

¿Qué son las reglas de lifecycle en Terraform?

Son meta-argumentos que se declaran dentro de un bloque lifecycle y controlan cómo Terraform crea, actualiza, reemplaza y destruye un recurso. Incluyen create_before_destroy, prevent_destroy, ignore_changes, replace_triggered_by y las validaciones precondition y postcondition. Complementá con ejecutar procesos sin depender de APIs.

¿Cómo proteger una base de datos de producción en Terraform?

Agregá prevent_destroy = true dentro del bloque lifecycle del recurso RDS. Con eso, cualquier plan que intente destruir la base falla con un error antes de ejecutarse. Para permitir cambios de versión automáticos sumá ignore_changes = [engine_version].

¿Cuál es la diferencia entre create_before_destroy y prevent_destroy?

create_before_destroy cambia el orden de reemplazo para lograr cero downtime: crea el recurso nuevo antes de eliminar el viejo. prevent_destroy hace lo contrario a permitir: bloquea todo intento de borrado y aborta el apply con un error.

¿Cómo permitir cambios externos en Terraform sin que se reviertan?

Usá ignore_changes con la lista de atributos que se modifican por fuera, por ejemplo ignore_changes = [desired_capacity]. Terraform saca esos atributos del diff y deja de intentar restaurarlos al valor del código en cada apply.

¿Cómo lograr cero downtime al actualizar recursos en Terraform?

Poné create_before_destroy = true en el bloque lifecycle del recurso. Para recursos con nombres únicos como S3 o IAM, combinalo con name_prefix en lugar de name, así el recurso nuevo y el viejo pueden coexistir durante la transición.

Conclusión

Las Terraform Lifecycle Rules son la diferencia entre un apply que confiás y uno que te da miedo correr un viernes a la tarde. La receta mínima para producción es corta: prevent_destroy en todo lo que tenga estado (bases, buckets, storage), create_before_destroy donde no toleres caídas, e ignore_changes para los atributos que cambian por fuera de tu control. Revisá tus módulos de producción hoy y fijate cuáles bases de datos todavía no tienen prevent_destroy. Ese es el cambio que más rápido te va a salvar de un mal día.

Fuentes

Te puede interesar...