Terraform 43: CI/CD — plan en el PR, apply con gate
easy⏱ 5 mincourseterraform
El state remoto es la base
CI no tiene un terraform.tfstate local. Un backend remoto le da a cada corrida el mismo state más un lock para que dos pipelines no apliquen a la vez. En Azure es un Storage Account + container; el key aísla cada stack/entorno.
terraform {
backend "azurerm" {
resource_group_name = "myorg-tfstate-dev-rg"
storage_account_name = "myorgtfstatedev"
container_name = "tfstate"
key = "orders/dev.tfstate"
}
}
Los pull requests reciben un plan, no un apply
En cada PR el pipeline corre fmt -check, validate y plan, y publica el plan para revisión. Los reviewers aprueban el diff, no solo el código. Nada cambia en la nube desde un PR.
# pr-validation pipeline (runs on pull_request)
steps:
- terraform fmt -check -recursive
- terraform init -input=false
- terraform validate
- terraform plan -input=false -out=tfplan # posted to the PR
El apply tiene gate y es solo-merge
La etapa de apply se dispara solo al mergear a main, aplica el plan guardado, y queda detrás de una aprobación manual de entorno para uat/prod. Se aplica el mismo artefacto que revisaste — sin drift entre plan y apply.
# apply pipeline (runs on push to main)
environment: prod # requires manual approval
steps:
- terraform init -input=false
- terraform apply -input=false -auto-approve tfplan
Nada de applies locales. Nunca.
Un terraform apply desde una laptop saltea la revisión, usa las credenciales personales de alguien y compite con el pipeline por el lock del state. CI tiene las únicas credenciales que pueden mutar la infra; las personas abren PRs y aprueban — no aplican.
Decidí la etapa del pipeline
Escribí pipelineStage(event) donde event = { type: "pull_request" | "push"; branch: string; env: string }. Devolvé "plan" para un pull_request; para un push a main devolvé "gated-apply" cuando env es uat/prod, si no "apply"; cualquier otro push devuelve "skip". Logueá los tres casos dados. Un PR debe imprimir plan; un push a main en prod debe imprimir gated-apply.