Terraform y OpenTofu: Infraestructura como Código en 2026
La división de licencia BSL y el fork de OpenTofu, HCL y providers, módulos reutilizables, estado remoto con locking, el ciclo plan/apply, workspaces, import, el framework nativo terraform test, CI/CD, y cómo se compara IaC con Ansible y Pulumi — con HCL real y actual.
Por Jose Nobile | Actualizado 2026-07-01 | 14 min de lectura
Índice
- Infraestructura como Código en 2026
- Terraform vs OpenTofu: El Fork
- HCL: Providers, Recursos, Variables, Outputs
- Módulos: Infraestructura Reutilizable
- Gestión de Estado y Backends Remotos
- Plan/Apply, Workspaces, Import, Drift
- Testing con terraform test
- Integración CI/CD
- Terraform vs Ansible vs Pulumi
Infraestructura como Código en 2026
Infraestructura como Código (IaC) significa describir tu nube — redes, VMs, bases de datos, DNS, IAM — en texto versionado en lugar de dar clics en consolas. Terraform (de HashiCorp, ahora parte de IBM) y su fork open-source OpenTofu (gobernado por la Linux Foundation) son las dos herramientas IaC declarativas y agnósticas de proveedor dominantes. Escribes el estado final deseado en HashiCorp Configuration Language (HCL); la herramienta calcula el diff contra la realidad y hace el conjunto mínimo de llamadas a la API para converger.
Ambas herramientas son compatibles drop-in hoy: el mismo HCL, los mismos providers, el mismo formato de estado. Las prácticas centrales que mantienen IaC seguro en producción:
- Todo en Git — Cada recurso vive en archivos
.tfbajo control de versiones. El repositorio es la única fuente de verdad; los cambios fluyen por pull requests y revisión de código, nunca una consola. - Estado remoto y con lock — Nunca dejes
terraform.tfstateen un portátil. Guárdalo en un backend remoto (S3, GCS, Terraform Cloud/HCP) con state locking para que dos applies no puedan corromperlo. - Plan antes de apply —
terraform planes un ensayo. Revisa el diff — especialmente destrucciones y reemplazos — antes de queapplytoque nada. En CI, publica el plan en el pull request. - Módulos pequeños y reutilizables — Factoriza patrones comunes (una VPC, un bucket, un cluster GKE) en módulos versionados. Compone infraestructura en lugar de copiar y pegar bloques de recursos.
- Política y escaneo en CI — Controla los applies con política como código (OPA/Conftest, Sentinel) y escaneo estático (tfsec, Checkov, Trivy) para atrapar security groups abiertos y buckets públicos antes de que salgan.
Una configuración mínima pero completa — fija la versión de la herramienta, declara el provider, crea un recurso:
# main.tf -- works identically on Terraform and OpenTofu
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "state_demo" {
bucket = "josenobile-iac-demo-2026"
tags = { Environment = "dev", ManagedBy = "terraform" }
}Terraform vs OpenTofu: El Fork
En agosto de 2023 HashiCorp cambió la licencia de Terraform del open-source MPL-2.0 a la Business Source License (BSL/BUSL 1.1) — una licencia source-available que prohíbe usar Terraform para construir un producto competidor, convirtiéndose en open source solo cuatro años después de cada release. La comunidad respondió con un fork: OpenTofu, aceptado en la Linux Foundation en septiembre de 2023 y mantenido bajo MPL-2.0. La adquisición de HashiCorp por IBM por $6.4B se cerró en febrero de 2025 y no cambió la licencia. A mediados de 2026 Terraform va en la línea 1.14.x mientras OpenTofu va en 1.12.x — los códigos base empezaron a divergir, pero el HCL del día a día sigue funcionando en ambos.
Terraform (HashiCorp / IBM)
Source-available bajo la Business Source License. Roadmap de un solo proveedor, integración estrecha con HCP Terraform (antes Terraform Cloud), el Terraform Registry y la política Sentinel. Adiciones recientes de 1.14: un bloque actions y List Resources (*.tfquery.hcl + terraform query). Gratis de usar salvo que construyas un producto IaC competidor.
OpenTofu (Linux Foundation)
Open source real, gobernanza neutral de proveedor, sin restricción de uso competitivo. Trae features que Terraform no tiene: encriptación de estado del lado del cliente, evaluación temprana de variables/providers, y (1.12) un prevent_destroy dinámico más removed { ... } que quita un recurso del estado sin destruirlo. CLI drop-in: ejecuta tofu en vez de terraform.
Cómo Elegir en 2026
Elige OpenTofu por una licencia aprobada por la OSI, encriptación de estado o independencia de un solo proveedor. Elige Terraform si necesitas HCP Terraform/Sentinel o un contrato de soporte empresarial. Migrar suele ser solo cambiar el binario y ejecutar tofu init — pruébalo primero en un workspace fuera de producción.
HCL: Providers, Recursos, Variables, Outputs
HashiCorp Configuration Language (HCL) es declarativo: describes bloques de estado deseado y el motor calcula el orden de las operaciones desde un grafo de dependencias implícito construido por referencia. Rara vez escribes ciclos o condicionales de forma imperativa — for_each, count y las expresiones cubren casi todo.
Los bloques constructivos de toda configuración:
- Providers — Plugins que hablan con una API (aws, google, azurerm, kubernetes, cloudflare). Se declaran en
required_providersy se fijan con una restricción de versión; los descargaterraform init. - Recursos y data sources — Los bloques
resourcecrean/gestionan objetos; los bloquesdataleen los existentes. Referencias comoaws_vpc.main.idcrean dependencias automáticamente. - Variables — Entradas tipadas (
variable) con defaults, descripciones y bloquesvalidation. Se definen vía.tfvars,-varo variables de entornoTF_VAR_*. - Outputs y locals —
outputexpone valores (y alimenta la composición de módulos);localsnombra expresiones intermedias para no repetirte.
Variables, un recurso manejado por for_each y un output:
# variables.tf
variable "environment" {
type = string
description = "Deployment environment"
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be dev, staging, or prod."
}
}
variable "bucket_names" {
type = set(string)
default = ["logs", "assets", "backups"]
}
# main.tf
resource "aws_s3_bucket" "app" {
for_each = var.bucket_names
bucket = "josenobile-${var.environment}-${each.key}"
}
# outputs.tf
output "bucket_arns" {
value = { for k, b in aws_s3_bucket.app : k => b.arn }
}Módulos: Infraestructura Reutilizable
Un módulo es solo un directorio de archivos .tf con entradas (variables) y salidas (outputs). Toda configuración ya es el "módulo raíz"; lo compones a partir de módulos hijos para no copiar y pegar. Los módulos son la forma de convertir un patrón probado — una VPC, una base de datos, un cluster de Kubernetes — en un bloque constructivo versionado, revisado y reutilizable, compartido entre equipos y ambientes.
Estructura e Interfaz
Mantén un contrato limpio: variables.tf (entradas), main.tf (recursos), outputs.tf (retornos) y un README. Quienes lo llaman dependen solo de la interfaz, así que puedes refactorizar el interior libremente. Mantén los módulos pequeños y de un solo propósito.
Fuentes y Registry
Referencia módulos desde una ruta local, una URL de Git (fíjala con ?ref=v1.4.0) o un registry. El Terraform Registry público y el OpenTofu Registry publican módulos probados en batalla (p. ej. terraform-aws-modules/vpc/aws) que puedes consumir en lugar de escribir el tuyo.
Versionado y Buenas Prácticas
Usa semver en cada módulo; fija la version en quien lo llama para que un cambio upstream nunca sorprenda a un apply de producción. Expone solo lo que necesitan, provee defaults sensatos y valida las entradas. Prefiere componer módulos pequeños antes que un módulo monolítico que "hace de todo".
Consumir un módulo del registry y conectar su output a otro recurso:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 6.0"
name = "prod-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
}
resource "aws_security_group" "app" {
name = "app-sg"
vpc_id = module.vpc.vpc_id # output from the module
}Gestión de Estado y Backends Remotos
El estado es cómo Terraform y OpenTofu mapean tu HCL a objetos del mundo real. El archivo de estado registra IDs, atributos y metadatos de recursos para que la herramienta sepa qué gestiona y pueda calcular un diff preciso. Si pierdes o corrompes el estado, la herramienta deja de saber qué le pertenece — por eso tratar el estado como dato crítico, compartido y protegido es la disciplina operativa más importante de IaC.
Backends remotos y locking. Un backend define dónde vive el estado. El estado local sirve para un experimento en solitario; un equipo necesita un backend remoto para que todos lean y escriban el mismo estado. La garantía esencial es el state locking: mientras un apply tiene el lock, los demás se bloquean, así dos personas no pueden mutar la infraestructura al tiempo y corromper el estado. Desde Terraform 1.11 / OpenTofu 1.11+, el backend S3 hace lock de forma nativa vía un objeto use_lockfile en el bucket — ya no se requiere la tabla de lock separada en DynamoDB (aunque sigue soportada). GCS hace lock sobre el objeto automáticamente; HCP Terraform y Terraform Cloud hacen lock del lado del servidor.
Encriptación y drift. El estado suele contener secretos (contraseñas de BD, llaves), así que encríptalo en reposo — habilita SSE del bucket, y en OpenTofu usa la encriptación de estado integrada del lado del cliente para que el archivo sea ilegible incluso para quien tenga el bucket. Detecta el drift (cambios hechos por fuera, en la consola) ejecutando terraform plan (o plan -refresh-only) de forma programada en CI; un plan no vacío significa que la realidad se desvió del código. Mantén el versionado en el bucket de estado para poder revertir una escritura mala.
Backend S3
El backend más común. Guarda el estado en un bucket S3 versionado y encriptado; habilita el use_lockfile = true nativo para el locking. Usa una clave de estado por ambiente/componente. Otorga IAM de mínimo privilegio al rol de CI que lo lee/escribe.
Backend GCS
Backend de Google Cloud Storage con locking automático a nivel de objeto — sin recurso de lock extra. Activa el versionado del bucket y la encriptación CMEK. Usa un prefix para separar los objetos de estado por ambiente dentro de un mismo bucket.
HCP Terraform / TACOS
HCP Terraform (antes Terraform Cloud) guarda el estado, lo bloquea, corre plan/apply de forma remota y agrega RBAC, política y una UI de runs. Alternativas abiertas — Spacelift, Scalr, env0 o Atlantis autohospedado — ofrecen el mismo flujo "TACOS" alrededor de cualquiera de los dos binarios.
# backend.tf -- S3 with native locking (no DynamoDB table needed)
terraform {
backend "s3" {
bucket = "josenobile-tfstate"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true # native S3 state locking
}
}Regla práctica: un estado pequeño y bloqueado de forma independiente por ambiente y componente (red, datos, app) en lugar de un estado gigante para todo. Estados más pequeños significan planes más rápidos, un radio de impacto menor y locks que no bloquean a toda la organización en cada cambio.
Plan/Apply, Workspaces, Import, Drift
El ciclo central es init → plan → apply. init descarga los providers y configura el backend; plan muestra exactamente qué se creará, cambiará o destruirá; apply lo ejecuta. destroy lo desmonta. Guarda un plan en un archivo y aplica ese plan exacto para que lo que revisaste sea lo que corre:
terraform init # or: tofu init terraform fmt -recursive # canonical formatting terraform validate # static config check terraform plan -out=tfplan # review the diff terraform apply tfplan # apply the reviewed plan terraform destroy # tear everything down
El ciclo plan/apply
Lee siempre el plan antes de aplicar — fija tu atención en -/+ (reemplazo) y - (destrucción) sobre recursos con estado. Usa -target con moderación para arreglos quirúrgicos, y lifecycle { prevent_destroy = true } para blindar recursos críticos como bases de datos.
Workspaces
Los workspaces de la CLI (terraform workspace new staging) mantienen varios estados desde una configuración — útiles para copias efímeras. Para una separación real dev/staging/prod, la mayoría prefiere directorios separados o .tfvars + backends distintos antes que los workspaces de la CLI, para evitar que una configuración se ramifique en secreto según terraform.workspace.
Import
Trae bajo gestión infraestructura existente creada a clics. Prefiere el bloque declarativo import { } (planificable, revisable, genera configuración con -generate-config-out) antes que el imperativo y antiguo terraform import. Las List Resources / terraform query de Terraform 1.14 pueden descubrir recursos importables en masa.
Detección de drift
El drift es un cambio hecho por fuera. Ejecuta terraform plan -refresh-only de forma programada; un resultado no vacío significa que la realidad se desvió del código. Reaplica para reconciliar, o codifica el cambio. Refactoriza con seguridad usando bloques moved { } para que un renombramiento no destruya y recree.
Testing con terraform test
Terraform trae un framework de testing nativo (disponible de forma general desde 1.6, con mocking de providers/recursos agregado en 1.7; OpenTofu tiene un tofu test equivalente). Los tests viven en archivos *.tftest.hcl y te dejan hacer aserciones sobre resultados de plan o apply sin un lenguaje aparte. Así es como validas módulos antes de publicarlos.
- Archivos .tftest.hcl — Los archivos de test van junto a tu módulo (o en
tests/). Ejecútalos conterraform test; cada archivo contiene uno o más bloquesrunejecutados en orden. - Bloques run y aserciones — Cada
runhace unplanoapplyy verificaassert { condition = ... }.command = planes rápido y no toca la nube;command = applyaprovisiona recursos reales (o mockeados) y luego los desmonta. - Mocks y overrides —
mock_providersimula un provider completo para que los tests unitarios corran offline y gratis;override_resource/override_datafijan valores específicos. No se requieren credenciales de nube ni gasto para los tests de lógica. - Gating en CI — Ejecuta
fmt -check,validate,testy un escaneo de seguridad (tfsec/Checkov) en cada pull request. Rompe el build ante cualquier test o violación de política antes de que el plan siquiera se revise.
Un test que mockea el provider (sin cuenta de AWS) y hace aserciones sobre el plan:
# tests/bucket.tftest.hcl
mock_provider "aws" {}
run "bucket_name_is_prefixed" {
command = plan
variables {
environment = "prod"
bucket_names = ["logs"]
}
assert {
condition = aws_s3_bucket.app["logs"].bucket == "josenobile-prod-logs"
error_message = "Bucket name was not prefixed with the environment."
}
}Integración CI/CD
El patrón seguro: ejecuta plan automáticamente en cada pull request y publica el diff como comentario; ejecuta apply solo al hacer merge a la rama principal, usando credenciales de nube de corta duración vía OIDC (nunca llaves de larga duración en los secrets). Agrega política como código encima para que los cambios riesgosos se bloqueen automáticamente.
Plan en el PR, apply al merge
En el pull request: fmt -check, validate, test, luego plan con la salida a la vista para revisar. Al hacer merge a main: apply del plan guardado. Esto le da a quien revisa el diff exacto y mantiene a las personas fuera de la ruta de credenciales.
OIDC, no llaves estáticas
GitHub Actions y GitLab CI pueden asumir un rol IAM de nube vía OIDC, así no hay llaves AWS/GCP de larga duración en los secrets de CI. Acota el rol exactamente a los recursos que ese pipeline gestiona y usa un rol distinto por ambiente.
Política como código
Controla los applies con OPA/Conftest o Sentinel contra el JSON del plan (terraform show -json tfplan): bloquea buckets públicos, recursos sin tags o instancias sobredimensionadas. Agrega tfsec/Checkov para escaneo de seguridad estático antes del plan.
Runners remotos
Atlantis (autohospedado) o una plataforma TACOS (HCP Terraform, Spacelift, Scalr, env0) centraliza los runs, impone el locking y agrega una UI de aprobación. Funciona con cualquiera de los binarios terraform o tofu.
# .github/workflows/terraform.yml (plan on PR, apply on merge)
permissions:
id-token: write # OIDC -> assume the cloud role, no static keys
contents: read
pull-requests: write
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3 # or opentofu/setup-opentofu@v1
- run: terraform init
- run: terraform fmt -check -recursive
- run: terraform validate
- run: terraform plan -out=tfplan
- if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplanTerraform vs Ansible vs Pulumi
Estas herramientas se solapan pero resuelven problemas distintos. Terraform/OpenTofu son aprovisionadores declarativos de infraestructura en la nube. Ansible es una herramienta procedural de gestión de configuración y orquestación. Pulumi es un aprovisionador declarativo como Terraform, pero lo escribes en un lenguaje de programación de propósito general. Muchos equipos usan dos juntas — Terraform para construir los servidores y la red, Ansible para configurar lo que corre sobre ellos.
Terraform / OpenTofu
HCL declarativo, archivo de estado explícito, enorme ecosistema de providers (AWS, GCP, Azure, Kubernetes y miles más). Lo mejor para aprovisionar y gestionar todo el ciclo de vida de recursos en la nube. El estándar de facto para IaC.
Ansible
Procedural, sin agente (SSH), sin archivo de estado. Sobresale en gestión de configuración — instalar paquetes, editar archivos, correr comandos en hosts existentes — y en orquestación de despliegue de apps. Más débil para rastrear y reconciliar el ciclo de vida de recursos en la nube que una herramienta basada en estado.
Pulumi
Declarativo como Terraform pero escrito en TypeScript, Python, Go o C# — ciclos reales, tipos y soporte de IDE en lugar de HCL. Genial cuando los equipos quieren reutilizar un lenguaje de propósito general y su tooling de tests; ecosistema y comunidad más pequeños que Terraform/OpenTofu.