INFRASTRUCTURE AS CODE

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

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 .tf bajo 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.tfstate en 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 plan es un ensayo. Revisa el diff — especialmente destrucciones y reemplazos — antes de que apply toque 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.

BSL 1.1

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.

MPL-2.0

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.

HOW TO CHOOSE

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_providers y se fijan con una restricción de versión; los descarga terraform init.
  • Recursos y data sources — Los bloques resource crean/gestionan objetos; los bloques data leen los existentes. Referencias como aws_vpc.main.id crean dependencias automáticamente.
  • Variables — Entradas tipadas (variable) con defaults, descripciones y bloques validation. Se definen vía .tfvars, -var o variables de entorno TF_VAR_*.
  • Outputs y locals — output expone valores (y alimenta la composición de módulos); locals nombra 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.

AWS

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.

GCP

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.

MANAGED

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 con terraform test; cada archivo contiene uno o más bloques run ejecutados en orden.
  • Bloques run y aserciones — Cada run hace un plan o apply y verifica assert { condition = ... }. command = plan es rápido y no toca la nube; command = apply aprovisiona recursos reales (o mockeados) y luego los desmonta.
  • Mocks y overrides — mock_provider simula un provider completo para que los tests unitarios corran offline y gratis; override_resource/override_data fijan valores específicos. No se requieren credenciales de nube ni gasto para los tests de lógica.
  • Gating en CI — Ejecuta fmt -check, validate, test y 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.

PIPELINE

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.

PIPELINE

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.

PIPELINE

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.

PIPELINE

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 tfplan

Terraform 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.

Más Guías