Guía - TF-PRO HashiCorp Terraform Authoring and Operations Professional
Última revisión: mayo de 2026
Una referencia escaneable de patrones arquitectónicos que evalúa el examen TF-PRO. Lee de arriba a abajo o salta a una sección.
HCL y Configuración
Un recurso necesita un número variable de bloques anidados idénticos (por ejemplo, reglas de entrada) impulsados por una lista/mapa.
Use un bloque `dynamic` cuyo `for_each` itere la colección; haga referencia a cada elemento a través del iterador de bloque (nombre predeterminado = etiqueta del bloque) dentro de `content {}`.
Por qué: Los bloques dinámicos generan bloques anidados repetidos sin copiar y pegar; el iterador mantiene cada bloque generado vinculado a su elemento de origen.
Cree un recurso por cada entrada en un mapa de objetos, con claves estables para que la reordenación nunca fuerce el reemplazo.
Establezca `for_each = var.objects` (un mapa). Use `each.key` para la clave estable y `each.value.<attr>` para los campos. Evite `count` aquí, los cambios de índice causan inestabilidad.
Por qué: Las claves de mapa son identidades estables en el estado; los índices de lista son posicionales y cambian cuando se añaden o eliminan elementos.
Decida entre `count` y `for_each` para múltiples instancias.
Use `for_each` cuando las instancias tienen identidades distintas (un conjunto/mapa); use `count` solo para N copias idénticas e insensibles al orden. Prefiera `for_each` para cualquier cosa que pueda crecer/disminuir.
Por qué: `for_each` se dirige por clave (resource["key"]); `count` se dirige por índice (resource[0]), lo que se reorganiza en inserciones/eliminaciones.
Una variable de entrada es un objeto donde algunos atributos son opcionales y necesitan valores predeterminados.
Escríbalo como `object({ name = string, size = optional(number, 10) })`. `optional(type, default)` proporciona el valor predeterminado cuando el llamador omite el atributo.
Por qué: `optional()` con un valor predeterminado mantiene a los llamadores concisos mientras garantiza un valor concreto en el flujo de trabajo, sin manejar nulos en todas partes.
Valide una suposición sobre un recurso antes de aplicar, o garantice un resultado después.
Use `lifecycle { precondition { ... } }` para afirmar las entradas antes de crear/actualizar, y `postcondition` para afirmar las salidas después. Ambos toman `condition` + `error_message`.
Por qué: Las condiciones personalizadas fallan rápidamente con un mensaje claro en lugar de producir una aplicación rota o un error posterior confuso.
Un recurso debe recrearse cada vez que otro recurso o atributo cambia.
Añada `lifecycle { replace_triggered_by = [aws_x.y.id] }`. Cuando el valor referenciado cambia, Terraform fuerza el reemplazo de este recurso.
Por qué: Expresa una dependencia de reemplazo de forma declarativa, evitando el `-replace` manual en cada cambio relacionado.
Reemplazar un recurso causa tiempo de inactividad porque el antiguo es destruido antes de que exista el nuevo.
Establezca `lifecycle { create_before_destroy = true }` para que Terraform aprovisione primero el reemplazo, y luego destruya el antiguo. Asegure nombres únicos/sin conflictos difíciles.
Por qué: Reemplazo sin tiempo de inactividad; pero tenga cuidado con las colisiones de nombres y los límites de cuota mientras ambos existen brevemente.
Rechace valores de entrada inválidos tempranamente (por ejemplo, un entorno que no sea dev/stage/prod).
Añada un bloque `validation { condition = contains(["dev","stage","prod"], var.env), error_message = "..." }` a la variable.
Por qué: Detecta entradas incorrectas en tiempo de planificación con un mensaje legible en lugar de fallar profundamente en una llamada al proveedor.
Haga referencia a un valor que podría no existir sin que falle el plan.
Use `try(local.maybe.value, "default")` para recurrir en caso de errores, o `can(expr)` para obtener un booleano que indique si una expresión tiene éxito.
Por qué: Manejo elegante de datos opcionales/con forma variable; evita "Error: Unsupported attribute" en claves ausentes.
Transforme una lista en un mapa, o filtre/moldee una colección para un argumento de recurso.
Use una expresión `for`: `{ for u in var.users : u.name => u.role if u.active }` (mapa) o `[for x in list : upper(x)]` (lista).
Por qué: Las expresiones `for` son la forma idiomática de remodelar datos; la cláusula `if` filtra, la forma `k => v` construye mapas.
Una variable o salida contiene un secreto que no debe imprimirse en la salida de plan/aplicación.
Marque la variable `sensitive = true` (y también las salidas). Terraform la oculta en la salida de CLI, aunque todavía se almacena en el estado.
Por qué: Evita la divulgación accidental en los registros/salida de CI; el estado en sí mismo aún debe protegerse (backend cifrado, control de acceso).
Administre recursos en dos regiones/cuentas dentro de una misma configuración.
Declare proveedores con alias (`provider "aws" { alias = "west" region = "us-west-2" }`) y establezca `provider = aws.west` en los recursos o pásselo a los módulos.
Por qué: Los alias permiten que una configuración apunte a múltiples instancias de proveedor; los módulos los reciben explícitamente a través del argumento `providers`.
Una dependencia oculta (no expresada a través de referencias) causa problemas de ordenación.
Añada `depends_on = [aws_iam_role_policy.x]` para forzar la ordenación. Úselo con moderación - prefiera dependencias implícitas a través de referencias de atributos.
Por qué: `depends_on` explícito maneja las dependencias que el gráfico no puede inferir, pero el uso excesivo crea planes conservadores y más lentos.
Renderice un archivo de configuración/datos de usuario desde una plantilla con variables estructuradas.
Use `templatefile("${path.module}/tpl.tftpl", { items = local.items })`; la plantilla usa interpolación `%{ for }` / `${}`.
Por qué: `templatefile` mantiene la renderización pura/en tiempo de planificación (a diferencia del proveedor de plantillas obsoleto) y admite bucles/condicionales.
Construya una lista plana de cada combinación (subred, regla) para alimentar un solo `for_each`.
Use `setproduct(var.subnets, var.rules)` para el producto cartesiano, o `flatten([for ...])` para colapsar listas anidadas en una sola.
Por qué: Estas funciones transforman datos anidados en la colección plana y con claves únicas que `for_each` requiere.
Módulos
Un módulo de registro cambia y altera inesperadamente la infraestructura en la siguiente inicialización.
Fije con `version = "~> 4.2"` (solo módulos de registro). Para fuentes Git, fije una etiqueta `?ref=v4.2.0`. Ejecute `terraform init -upgrade` deliberadamente para mover los fijadores.
Por qué: Los módulos no fijados flotan a la última versión; la fijación hace que las actualizaciones sean intencionales y revisables.
Un módulo raíz necesita un valor producido en lo profundo de un módulo hijo.
Expóngalo como una `output` en el hijo, luego haga referencia a `module.child.output_name`. Los valores no expuestos como salida no son accesibles para los llamadores.
Por qué: Los módulos están encapsulados; las salidas son la única forma en que los datos cruzan el límite del módulo hacia arriba.
Instancie el mismo módulo una vez por equipo/entorno desde un mapa.
Establezca `for_each` en el bloque del módulo: `module "env" { for_each = var.envs; source = "./env"; name = each.key }`. Haga referencia a `module.env["prod"]`.
Por qué: `for_each` en módulos escala un patrón sin copiar y pegar bloques; las claves dan direcciones estables.
Un módulo hijo debe crear recursos en un proveedor no predeterminado (con alias).
Pase los proveedores explícitamente: `module "x" { providers = { aws = aws.west } }`. El hijo declara el proveedor en `required_providers` con `configuration_aliases`.
Por qué: Los módulos no heredan proveedores con alias implícitamente; el mapa de proveedores conecta el alias padre con el hijo.
Renombrar un recurso o moverlo a un módulo normalmente lo destruiría y lo recrearía.
Añada un bloque `moved { from = aws_instance.old; to = module.compute.aws_instance.new }`. Terraform actualiza las direcciones de estado sin destruir.
Por qué: Los bloques `moved` hacen que las refactorizaciones sean seguras y revisables en el código, reemplazando el `terraform state mv` manual.
Un módulo monolítico se ha vuelto inmanejable y mezcla preocupaciones de redes, computación y datos.
Descomponga en módulos hijos enfocados y compóngalos en un módulo raíz, pasando las salidas de uno como entradas al siguiente. Mantenga los módulos con un único propósito.
Por qué: La composición mejora la reutilización y la capacidad de prueba; los módulos con ámbito restringido se versionan y evolucionan de forma independiente.
Los consumidores pasan combinaciones inválidas de entradas a un módulo compartido.
Añada bloques `validation` y `precondition` dentro del módulo para hacer cumplir los contratos, y documente las entradas con `description`.
Por qué: Un módulo es propietario de su contrato; la validación interna protege a cada llamador, no solo a una configuración raíz.
Los módulos profundamente anidados dificultan el seguimiento del flujo de datos y el paso de proveedores.
Mantenga el anidamiento superficial (1-2 niveles). Pase los proveedores y las entradas clave explícitamente en cada nivel; evite depender de una herencia implícita profunda.
Por qué: Los árboles superficiales son más fáciles de entender; el anidamiento profundo amplifica la complejidad del paso de proveedores y la canalización de salidas.
Publique un módulo reutilizable en el registro privado y desarróllelo sin romper a los llamadores.
Etiquete las versiones con semver (`v1.2.0`); los cambios de entrada/salida que rompen la compatibilidad aumentan la versión principal. Los llamadores fijan con restricciones `~>`.
Por qué: El versionado semántico permite a los consumidores adoptar correcciones/características de forma segura y optar por cambios importantes deliberadamente.
Elija de dónde obtener un módulo para un nivel de madurez dado.
Rutas locales (`./modules/x`) para módulos en el repositorio, Git (`git::...?ref=tag`) para módulos compartidos pero no publicados, registro (`namespace/name/provider`) para módulos versionados/publicados.
Por qué: El tipo de fuente coincide con el ámbito de compartición; solo las fuentes de registro admiten el argumento `version` y la resolución de restricciones.
La salida de un módulo contiene un secreto consumido por el módulo raíz.
Marque la salida del módulo `sensitive = true`. Consumirla en un contexto no sensible generará un error hasta que también la trate como sensible.
Por qué: La sensibilidad se propaga a través del límite del módulo, evitando fugas accidentales en la salida raíz.
Un conjunto de recursos existente basado en `count` necesita convertirse a `for_each` sin destruir instancias.
Añada bloques `moved` que mapeen cada índice `resource[0]` a la nueva dirección `resource["key"]`, luego cambie a `for_each`.
Por qué: Los bloques `moved` reindexan el estado de direccionamiento posicional a direccionamiento por identidad, evitando la destrucción/recreación.
CLI y Gestión de Estado
Ha renombrado un recurso en la configuración; el plan ahora quiere destruir el antiguo y crear uno nuevo.
Prefiera un bloque `moved` en la configuración. Para correcciones ad-hoc/CLI, use `terraform state mv aws_x.old aws_x.new` para reasignar el objeto existente.
Por qué: Ambos actualizan la dirección del estado para que Terraform vea el objeto existente como el recurso renombrado, sin destruir.
Ponga un recurso existente, creado manualmente, bajo la gestión de Terraform.
Añada un bloque `import { to = aws_x.y; id = "i-123" }` y ejecute `terraform plan -generate-config-out=gen.tf` para generar la configuración, luego refine y aplique.
Por qué: La importación impulsada por la configuración es revisable y genera una configuración inicial, a diferencia del `terraform import` imperativo más antiguo.
Deje de gestionar un recurso con Terraform pero déjelo en ejecución en la nube.
Ejecute `terraform state rm aws_x.y`. Terraform olvida el objeto; no se destruye. También elimine su configuración para evitar un plan de recreación.
Por qué: `state rm` desvincula sin eliminar - útil al pasar un recurso a otra herramienta/equipo.
Mover el estado de un backend local a S3 (o a HCP Terraform).
Añada/reemplace el bloque `backend`/`cloud`, ejecute `terraform init` - Terraform detecta el cambio y le pide que migre el estado existente al nuevo backend.
Por qué: `init` orquesta la copia; responder sí migra el estado de forma segura en lugar de comenzar con un estado vacío.
Dos ingenieros ejecutan `apply` contra el mismo estado remoto simultáneamente.
Use un backend que admita el bloqueo (S3+DynamoDB, HCP Terraform, etc.). Terraform adquiere un bloqueo por operación; la segunda ejecución espera o genera un error.
Por qué: El bloqueo evita escrituras concurrentes que podrían corromper el estado. Nunca lo desactive casualmente.
Una aplicación fallida dejó un bloqueo obsoleto y ahora cada ejecución está bloqueada.
Confirme que ninguna operación se está ejecutando realmente, luego `terraform force-unlock <LOCK_ID>`. Use el ID del mensaje de error.
Por qué: `force-unlock` libera un bloqueo huérfano; hacerlo mientras una operación real se está ejecutando conlleva el riesgo de corrupción del estado.
Detecte la desviación entre la configuración/estado y la infraestructura real sin proponer cambios.
Ejecute `terraform plan -refresh-only` (o `apply -refresh-only` para actualizar el estado). Informa las diferencias sin planificar cambios de recursos.
Por qué: Separa la detección de desviación de la planificación de cambios - usted ve lo que cambió en la nube antes de decidir conciliar.
Un recurso no se comporta correctamente y desea recrearlo sin editar la configuración.
Ejecute `terraform apply -replace="aws_instance.web"`. Este es el reemplazo moderno para el obsoleto `terraform taint`.
Por qué: `-replace` fuerza la destrucción y recreación de un recurso en la siguiente aplicación, de forma declarativa en la CLI.
Está tentado a usar `-target` rutinariamente para acelerar las aplicaciones.
Use `-target` solo para la recuperación de errores o correcciones quirúrgicas. Evítelo como un flujo de trabajo normal - produce aplicaciones parciales y puede omitir dependencias.
Por qué: El uso rutinario de `target` oculta problemas de dependencia y produce un estado incompleto; HashiCorp lo documenta como una herramienta excepcional.
Un proveedor cambió de espacio de nombres (por ejemplo, hashicorp/aws a un fork) y el estado hace referencia a la antigua dirección.
Ejecute `terraform state replace-provider registry.terraform.io/hashicorp/aws registry.example.com/org/aws`.
Por qué: Reescribe las referencias del proveedor en el estado para que `init/plan` resuelva la nueva fuente sin recrear recursos.
Una configuración necesita salidas producidas por otra configuración/espacio de trabajo.
Utilice la fuente de datos `terraform_remote_state` (o las salidas de ejecución de HCP Terraform) para leer las salidas de otro estado en modo de solo lectura.
Por qué: Comparte valores entre límites de estado sin duplicar recursos; solo las salidas exportadas son legibles.
La configuración del backend (bucket, clave) difiere por entorno y no debe codificarse de forma rígida.
Déjelos fuera del bloque `backend` y pásselos en `init`: `terraform init -backend-config=prod.hcl` (o `-backend-config="key=..."`).
Por qué: La configuración parcial mantiene una configuración reutilizable en todos los entornos, mientras que proporciona valores de backend específicos del entorno en la inicialización.
Necesita un estado separado para dev/stage/prod a partir de una única configuración.
Use espacios de trabajo de CLI (`terraform workspace new prod`) para un aislamiento ligero, o configuraciones raíz/espacios de trabajo de HCP separados para una separación más fuerte.
Por qué: Cada espacio de trabajo tiene su propio estado; haga referencia a `terraform.workspace` para variar la nomenclatura/tamaño. Para un aislamiento fuerte, prefiera backends/espacios de trabajo distintos.
Operaciones de HCP Terraform
Evite almacenar claves de nube de larga duración en las variables del espacio de trabajo de HCP Terraform.
Configure credenciales de proveedor dinámicas: HCP Terraform usa OIDC/identidad de carga de trabajo para obtener credenciales de corta duración de AWS/Azure/GCP/Vault por cada ejecución.
Por qué: Elimina los secretos estáticos; las credenciales se acuñan justo a tiempo y caducan, reduciendo el radio de impacto.
Las mismas variables (configuración del proveedor, etiquetas) son necesarias en múltiples espacios de trabajo.
Defina un conjunto de variables y aplíquelo a un proyecto o a espacios de trabajo seleccionados. Las variables a nivel de espacio de trabajo anulan los valores del conjunto de variables.
Por qué: Los conjuntos de variables evitan duplicar la configuración compartida; la precedencia (espacio de trabajo > conjunto) permite que un espacio de trabajo anule cuando sea necesario.
Aplique barreras de seguridad (no S3 público, etiquetas obligatorias) en cada ejecución.
Adjunte un conjunto de políticas Sentinel u OPA. Establezca el nivel de aplicación: consultivo (advertencia), obligatorio suave (anular con permiso) u obligatorio estricto (bloqueo).
Por qué: La política como código controla las ejecuciones centralmente; los niveles de aplicación equilibran la rigidez con la flexibilidad operativa.
Integre una verificación externa (estimación de costos, escaneo de seguridad) en el pipeline de ejecución.
Configure una tarea de ejecución en una etapa (pre-plan, post-plan, pre-apply). HCP Terraform llama al servicio externo y controla la ejecución según su resultado.
Por qué: Las tareas de ejecución extienden el pipeline con verificaciones de terceros sin necesidad de una configuración de CI personalizada.
Elija cómo se activan las ejecuciones para un espacio de trabajo.
Impulsado por VCS (el commit/PR activa el plan), impulsado por CLI (`terraform plan/apply` contra el remoto) o impulsado por API (configuración cargada). Elija según el flujo de trabajo del equipo.
Por qué: Impulsado por VCS se adapta a GitOps; impulsado por CLI se adapta a la iteración local; impulsado por API se adapta a pipelines personalizados. Son mutuamente excluyentes por espacio de trabajo.
Otorgue a un equipo acceso de escritura a los espacios de trabajo de staging pero solo de lectura a producción.
Defina el alcance de los permisos a nivel de organización/proyecto/espacio de trabajo: asigne acceso al equipo (lectura/plan/escritura/administrador) por proyecto o espacio de trabajo; use la agrupación de proyectos para gestionar a escala.
Por qué: Los permisos granulares y con alcance definido aplican el principio de mínimo privilegio; las concesiones a nivel de proyecto reducen la gestión por espacio de trabajo.
Una aplicación de un espacio de trabajo de red debe poner en cola automáticamente una ejecución en espacios de trabajo de aplicaciones dependientes.
Configure un disparador de ejecución: el espacio de trabajo descendente se suscribe al ascendente; una aplicación exitosa pone en cola la ejecución descendente.
Por qué: Los disparadores de ejecución encadenan espacios de trabajo dependientes para que los cambios en la infraestructura compartida se propaguen en orden.
Permita que usuarios no expertos en Terraform aprovisionen infraestructura estandarizada a través de un formulario.
Publique un módulo sin código en el registro privado; los usuarios lo instancian a través de la interfaz de usuario, proporcionando solo entradas - sin autoría HCL.
Por qué: Los módulos sin código democratizan el aprovisionamiento de autoservicio manteniendo la configuración subyacente gobernada y versionada.
Comparta módulos y proveedores verificados en toda la organización.
Publique en el registro privado de HCP Terraform; los consumidores hacen referencia a `app.terraform.io/org/name/provider` con restricciones de versión.
Por qué: Un registro privado centraliza el descubrimiento, el versionado y la gobernanza de los módulos internos.
Organice decenas de espacios de trabajo por equipo/aplicación para permisos y conjuntos de variables.
Agrupe espacios de trabajo en proyectos; aplique permisos de equipo y conjuntos de variables a nivel de proyecto.
Por qué: Los proyectos escalan la gobernanza - usted gestiona el acceso y la configuración compartida por proyecto en lugar de por espacio de trabajo.
Detecte continuamente cuándo la producción se desvía del estado configurado.
Habilite las evaluaciones de salud (detección de desviación / validación continua) en el espacio de trabajo; HCP Terraform actualiza periódicamente e informa sobre la desviación y las aserciones fallidas.
Por qué: Las evaluaciones automatizadas revelan la desviación y las postcondiciones rotas entre las aplicaciones, antes de que causen incidentes.
HCP Terraform debe alcanzar la infraestructura dentro de una red privada sin entrada pública.
Implemente agentes de HCP Terraform en la red privada y asigne el espacio de trabajo a un grupo de agentes; las ejecuciones se realizan a través del agente.
Por qué: Los agentes permiten que HCP Terraform opere contra entornos privados/aislados sin exponerlos públicamente.
Una aplicación errónea corrompió el estado y necesita recuperarse.
HCP Terraform mantiene el estado versionado; revierta a una versión de estado anterior desde la UI/API del espacio de trabajo y vuelva a planificar.
Por qué: El versionado de estado incorporado proporciona puntos de recuperación sin necesidad de gestionar las instantáneas del backend usted mismo.