Cualquier recurso cloud que hayas creado haciendo clic en una consola web (una VM, un load balancer, un registro DNS) también se puede describir en un archivo de texto y crear con un solo comando. El archivo vive en git, pasa por una pull request y reproduce el mismo setup en una segunda cuenta sin que nadie tenga que recordar qué nueve ajustes cambió a mano. Eso es lo que hace Terraform.

Qué hace Terraform en realidad

Terraform es una herramienta open-source de HashiCorp que convierte la infraestructura en archivos de configuración, escritos en HCL (HashiCorp Configuration Language), y los aplica contra la API de un proveedor cloud. Escribes un bloque resource que describe el bucket S3, la instancia EC2 o el registro DNS de Cloudflare que quieres, ejecutas terraform apply, y Terraform calcula las llamadas a la API necesarias para que ese recurso exista — y luego le hace seguimiento, así un segundo apply solo cambia lo que realmente cambió.

El modelo es declarativo, no procedural: describes el estado final, no los pasos para llegar a él. No escribes “crea un bucket, luego configura su policy, luego activa el versioning”. Escribes cómo debe verse el bucket, y Terraform calcula el orden de las operaciones, incluyendo qué tiene que existir primero cuando un recurso depende de otro.

Terraform por sí solo no sabe nada de AWS, Azure o Cloudflare. Ese conocimiento vive en los providers: plugins que traducen los bloques HCL en llamadas contra una API específica. El Terraform Registry incluye miles de providers, oficiales y mantenidos por la comunidad, que cubren desde los tres grandes clouds hasta la configuración de repositorios de GitHub y los monitores de Datadog. El mismo mecanismo crea un cluster de Kubernetes gestionado (google_container_cluster, aws_eks_cluster) o un único registro DNS delante de un CDN. Si un servicio tiene una API, es probable que alguien ya haya escrito un provider para él.

Cómo funcionan terraform plan y apply

Un directorio de trabajo mínimo tiene al menos dos archivos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# terraform.tf — qué providers necesita esta configuración
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.5"
}

provider "aws" {
  region = "eu-west-1"
}
1
2
3
4
5
6
7
8
9
# main.tf — el recurso en sí
resource "aws_s3_bucket" "reports" {
  bucket = "acme-monthly-reports"

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Cuatro comandos cubren el ciclo de trabajo diario:

1
2
3
4
terraform init     # descarga el plugin del provider aws, configura el backend
terraform plan     # muestra qué cambiaría, sin tocar nada
terraform apply    # pide confirmación, luego hace las llamadas a la API
terraform destroy  # elimina todo lo que gestiona esta configuración

terraform init lee required_providers y descarga los binarios de los plugins correspondientes dentro de .terraform/. Lo ejecutas una vez por directorio, y otra vez cada vez que añades un provider o un módulo.

terraform plan es el comando que más usarás. Compara tus archivos .tf, el state registrado y la infraestructura real tal como la reporta la API del provider, y luego imprime un diff: recursos por crear (+), cambiar (~) o destruir (-). Todavía no pasa nada. Eso es lo que hace seguro apuntar Terraform a producción: lees el plan antes de que se toque algo.

terraform apply vuelve a ejecutar el plan, lo muestra de nuevo y espera a que escribas yes antes de llamar a las APIs del provider en el orden de dependencia correcto. En CI pasas -auto-approve o, mejor, aplicas un archivo de plan que ya revisaste: terraform apply tfplan. El mismo pipeline que ejecuta Construir y Publicar una Imagen Docker con GitHub Actions en cada merge puede ejecutar terraform plan en una pull request y terraform apply cuando esta llega a main.

Para qué sirve el state file

Después del primer apply, Terraform escribe terraform.tfstate — un archivo JSON que mapea cada recurso de tu configuración al objeto real que creó, ID incluido. No es una contabilidad opcional. Así es como plan sabe qué existe ya sin volver a escanear toda tu cuenta de AWS en cada ejecución, y así es como Terraform conecta aws_s3_bucket.reports en tu código con el bucket acme-monthly-reports-a8f3 en la realidad.

De ahí se derivan dos consecuencias directas.

El state puede contener secretos. Una contraseña de base de datos configurada como argumento de un recurso termina en texto plano en el archivo de state, porque Terraform necesita los atributos completos del recurso para detectar drift. Nunca hagas commit de terraform.tfstate a git.

El state necesita compartirse y bloquearse. El valor por defecto es un archivo local, que funciona solo y se rompe en cuanto una segunda persona ejecuta apply desde su propio portátil: ahora hay dos fuentes de verdad. La solución es un backend remoto: el state se guarda en S3, Azure Blob, GCS o Terraform Cloud, con un bloqueo que impide que dos apply corran a la vez.

1
2
3
4
5
6
7
8
9
terraform {
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "reports/terraform.tfstate"
    region       = "eu-west-1"
    use_lockfile = true
    encrypt      = true
  }
}

use_lockfile es el locking nativo del backend S3, disponible de forma estable desde Terraform 1.11: escribe un archivo de bloqueo directamente en el bucket, así que en las versiones actuales ya no hace falta una tabla DynamoDB dedicada al locking.

Una vez que el equipo apunta al mismo backend, el plan de cada uno refleja el último apply de los demás.

Cómo reutilizar una configuración con variables y módulos

Hardcodear eu-west-1 y un nombre de bucket funciona para un ejemplo de cinco líneas, no para un entorno real. Terraform separa la forma de la infraestructura de los valores que cambian según el entorno.

1
2
3
4
5
6
7
8
9
# variables.tf
variable "environment" {
  type    = string
  default = "staging"
}

variable "bucket_name" {
  type = string
}
1
2
3
4
5
6
7
8
# main.tf, referenciando la variable
resource "aws_s3_bucket" "reports" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
  }
}
1
terraform apply -var="bucket_name=acme-prod-reports" -var="environment=production"

O pon los valores en un archivo terraform.tfvars para no reescribir los flags en cada ejecución. outputs.tf hace el camino inverso. Expone un valor que Terraform calculó, como la IP pública de una instancia o un ARN generado, para que otra herramienta u otra configuración de Terraform pueda consumirlo:

1
2
3
output "bucket_arn" {
  value = aws_s3_bucket.reports.arn
}

Cuando un conjunto de recursos se repite entre proyectos (una VPC estándar, un stack de web-app estándar), lo encapsulas en un módulo: un directorio de archivos .tf referenciado con un argumento source, que toma inputs y devuelve outputs como una función. La mayoría de los equipos terminan con un puñado de módulos internos y muchas configuraciones ligeras que los llaman con variables distintas.

Cuándo Terraform no es la herramienta adecuada

Terraform crea recursos; no configura lo que corre en ellos. Creará una instancia EC2, pero instalar paquetes, gestionar usuarios y mantener la configuración sincronizada en esa instancia es un trabajo distinto — tradicionalmente de Ansible, de un script cloud-init, o de una imagen de contenedor que la instancia descarga y ejecuta. Pasar la configuración de la aplicación a través de Terraform es la dirección equivocada. Crear la VPC a través de Ansible es la otra dirección equivocada. La mayoría de los setups reales usan ambos, y Terraform pasa la IP de la instancia directamente al inventory de Ansible mediante un output.

Para un bucket que vas a borrar en una hora, o una VM de prueba desechable, el archivo de state y el ciclo escribir-plan-apply cuestan más de lo que ahorran. La CLI de AWS o la consola son más rápidas para algo que no piensas mantener ni reproducir.

Diferencias entre Terraform, OpenTofu y Pulumi

Terraform ya no es la única herramienta que lee HCL. HashiCorp lo movió de la licencia open-source MPL a la Business Source License en 2023, y ahora la Linux Foundation mantiene OpenTofu, un fork que conservó la licencia MPL y se mantuvo compatible con la sintaxis HCL y el formato de state de Terraform. Cambiar más adelante es un cambio de configuración, no una reescritura.

Pulumi va en la dirección contraria: en vez de HCL escribes la infraestructura en TypeScript, Python o Go. Tiene sentido si tu equipo quiere control de flujo real y tests unitarios sobre la lógica de infraestructura, en lugar de las expresiones más limitadas de HCL.

Cómo probar Terraform sin una cuenta cloud

La red de seguridad de Terraform es plan, y puedes ejercitar todo el ciclo gratis antes de apuntarlo a algo facturable. Los providers random y local no necesitan ninguna credencial cloud.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

resource "random_pet" "name" {
  length = 2
}

output "generated_name" {
  value = random_pet.name.id
}

Ejecutas terraform init y terraform apply en un directorio vacío y obtienes un state file real y un recurso gestionado real, sin nada que pagar. Hazlo una vez, lee la salida del plan línea por línea hasta que los símbolos +, ~ y - signifiquen algo para ti, y luego apunta los mismos cuatro comandos a un provider que facture por hora.

Artículos relacionados