Hacer clic manualmente a través de los paneles de proveedores cloud para crear servidores, configurar registros DNS y establecer firewalls no escala. Cuando tu infraestructura crece más allá de unos pocos recursos, la gestión manual se vuelve propensa a errores, inconsistente e imposible de reproducir. La Infraestructura como Código (IaC) resuelve esto definiendo toda tu infraestructura en archivos de configuración controlados por versiones. Terraform, creado por HashiCorp, es la herramienta IaC más ampliamente adoptada, soportando cientos de proveedores cloud con un único flujo de trabajo.
¿Qué Es la Infraestructura como Código?
La Infraestructura como Código (IaC) es la práctica de gestionar infraestructura — servidores, redes, DNS, bases de datos, firewalls — usando archivos de configuración declarativos en lugar de procesos manuales. Describes el estado deseado de tu infraestructura y la herramienta determina cómo hacerlo realidad.
Beneficios clave:
- Reproducibilidad — Despliega entornos idénticos para desarrollo, staging y producción
- Control de versiones — Rastrea cada cambio de infraestructura en Git con historial completo
- Colaboración — Revisa cambios de infraestructura a través de pull requests igual que el código de aplicación
- Automatización — Integra con pipelines CI/CD para despliegues automatizados
- Documentación — El código mismo documenta cómo se ve tu infraestructura
Terraform vs Otras Herramientas IaC
| Característica | Terraform | Ansible | Pulumi | CloudFormation |
|---|---|---|---|---|
| Enfoque | Declarativo | Imperativo/Declarativo | Imperativo | Declarativo |
| Lenguaje | HCL | YAML | Python, TypeScript, Go | JSON/YAML |
| Multi-cloud | Sí | Sí | Sí | Solo AWS |
| Gestión de estado | Archivo de estado | Sin estado | Archivo de estado | Gestionado por AWS |
| Mejor para | Aprovisionamiento de infraestructura | Gestión de configuración | Desarrolladores que prefieren código | Entornos solo AWS |
Terraform destaca en el aprovisionamiento de infraestructura a través de múltiples proveedores cloud. Usa Ansible para la gestión de configuración (instalar software en servidores) y Terraform para crear los servidores en sí. Muchos equipos usan ambos juntos.
Instalar Terraform
macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
Windows
choco install terraform
Verifica la instalación:
terraform version
Fundamentos de la Sintaxis HCL
Terraform usa HashiCorp Configuration Language (HCL), un lenguaje declarativo diseñado para ser legible por humanos. Estos son los constructos principales:
Bloques
Todo en Terraform se define en bloques:
# Bloque de recurso
resource "type" "name" {
argument1 = "value1"
argument2 = "value2"
nested_block {
key = "value"
}
}
Tipos de Datos
# Cadena de texto
name = "web-server"
# Número
count = 3
# Booleano
enable_monitoring = true
# Lista
availability_zones = ["us-east-1a", "us-east-1b"]
# Mapa
tags = {
Environment = "production"
Team = "platform"
}
Referencias e Interpolación
# Referenciar el atributo de otro recurso
subnet_id = aws_subnet.main.id
# Interpolación de cadenas
name = "app-${var.environment}-${count.index}"
# Expresión condicional
instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
El Flujo de Trabajo Principal de Terraform
Cada proyecto de Terraform sigue cuatro comandos:
1. terraform init
Inicializa el directorio de trabajo, descarga los plugins de proveedores y configura el backend:
terraform init
Ejecuta esto cuando configures un proyecto por primera vez o añadas un nuevo proveedor.
2. terraform plan
Previsualiza los cambios que Terraform realizará sin aplicarlos realmente:
terraform plan
Esta es tu red de seguridad. Siempre revisa el plan antes de aplicar. La salida muestra recursos a crear (+), modificar (~) o destruir (-).
3. terraform apply
Ejecuta los cambios mostrados en el plan:
terraform apply
Terraform mostrará el plan nuevamente y pedirá confirmación. Usa -auto-approve solo en pipelines automatizados, nunca de forma interactiva.
4. terraform destroy
Elimina todos los recursos gestionados por esta configuración:
terraform destroy
Advertencia:
terraform destroyes irreversible. Elimina infraestructura real. Siempre verifica que estés apuntando al workspace y entorno correctos antes de ejecutar este comando.
Proveedores
Los proveedores son plugins que interactúan con plataformas cloud, herramientas SaaS y otras APIs. Debes declarar qué proveedores usa tu configuración:
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
La restricción de versión ~> 5.0 significa “cualquier versión >= 5.0.0 y < 6.0.0”, permitiendo actualizaciones de parche y menores mientras previene cambios incompatibles.
Tu Primer Recurso: Registro DNS en Cloudflare
Vamos a crear un primer recurso práctico — un registro DNS en Cloudflare:
# main.tf
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
resource "cloudflare_record" "www" {
zone_id = var.cloudflare_zone_id
name = "www"
content = "203.0.113.50"
type = "A"
ttl = 300
proxied = true
}
resource "cloudflare_record" "mail" {
zone_id = var.cloudflare_zone_id
name = "@"
content = "mail.knowledgexchange.xyz"
type = "MX"
priority = 10
ttl = 3600
}
Ejecuta el flujo de trabajo:
terraform init
terraform plan
terraform apply
Terraform crea ambos registros DNS y los rastrea en su archivo de estado.
Variables
Las variables hacen tus configuraciones reutilizables y específicas por entorno:
Definir Variables
# variables.tf
variable "cloudflare_api_token" {
description = "Token API de Cloudflare con permisos de edición DNS"
type = string
sensitive = true
}
variable "cloudflare_zone_id" {
description = "ID de zona de Cloudflare para el dominio"
type = string
}
variable "environment" {
description = "Entorno de despliegue"
type = string
default = "staging"
validation {
condition = contains(["development", "staging", "production"], var.environment)
error_message = "Environment must be development, staging, or production."
}
}
variable "server_count" {
description = "Número de servidores a crear"
type = number
default = 2
}
variable "allowed_ips" {
description = "Lista de direcciones IP con acceso permitido al servidor"
type = list(string)
default = []
}
variable "tags" {
description = "Etiquetas de recursos"
type = map(string)
default = {
ManagedBy = "terraform"
}
}
Establecer Valores de Variables
Crea un archivo terraform.tfvars (cargado automáticamente):
# terraform.tfvars
cloudflare_zone_id = "abc123def456"
environment = "production"
server_count = 3
allowed_ips = ["203.0.113.50", "198.51.100.25"]
tags = {
ManagedBy = "terraform"
Environment = "production"
Team = "platform"
}
Pasa valores sensibles a través de variables de entorno:
export TF_VAR_cloudflare_api_token="your-api-token-here"
terraform apply
Importante: Nunca subas archivos
terraform.tfvarsque contengan secretos al control de versiones. Usa variables de entorno o un gestor de secretos para valores sensibles.
Outputs
Los outputs exponen valores de tu configuración, haciéndolos disponibles para otras configuraciones o scripts:
# outputs.tf
output "dns_record_hostname" {
description = "El FQDN del registro DNS creado"
value = cloudflare_record.www.hostname
}
output "server_ip" {
description = "La dirección IP pública del servidor"
value = aws_instance.web.public_ip
}
output "database_connection_string" {
description = "Cadena de conexión a la base de datos"
value = aws_db_instance.main.endpoint
sensitive = true
}
Visualiza los outputs después de aplicar:
terraform output
terraform output dns_record_hostname
Gestión del Estado
Terraform rastrea cada recurso que gestiona en un archivo de estado (terraform.tfstate). Este archivo mapea tu configuración a recursos del mundo real.
Por Qué el Estado Importa
- Terraform usa el estado para determinar qué cambios necesitan hacerse
- El estado contiene información sensible (IDs de recursos, direcciones IP, a veces contraseñas)
- Sin estado, Terraform no puede gestionar recursos existentes
Backends Remotos
Nunca almacenes archivos de estado localmente para proyectos de equipo. Usa un backend remoto:
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
El backend S3 almacena el estado en un bucket S3 con cifrado del lado del servidor. La tabla DynamoDB proporciona bloqueo de estado, previniendo que dos personas apliquen cambios simultáneamente.
Otras opciones de backend incluyen:
- Azure Blob Storage — Para equipos centrados en Azure
- Google Cloud Storage — Para usuarios de GCP
- Terraform Cloud — El servicio gestionado de HashiCorp con un nivel gratuito
- Consul — Para despliegues on-premises
Fuentes de Datos
Las fuentes de datos te permiten leer información de infraestructura existente que Terraform no gestiona:
# Buscar una VPC existente
data "aws_vpc" "existing" {
filter {
name = "tag:Name"
values = ["production-vpc"]
}
}
# Buscar la AMI de Ubuntu más reciente
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-*-24.04-amd64-server-*"]
}
}
# Usar la fuente de datos en un recurso
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = data.aws_vpc.existing.id
tags = {
Name = "web-server"
}
}
Módulos
Los módulos son paquetes reutilizables de configuración Terraform. Encapsulan recursos relacionados en una única unidad:
# modules/web-server/main.tf
variable "instance_type" {
type = string
default = "t3.micro"
}
variable "server_name" {
type = string
}
resource "aws_instance" "server" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = var.server_name
}
}
output "public_ip" {
value = aws_instance.server.public_ip
}
Usa el módulo:
# main.tf
module "web" {
source = "./modules/web-server"
server_name = "web-production"
instance_type = "t3.large"
}
module "staging" {
source = "./modules/web-server"
server_name = "web-staging"
instance_type = "t3.micro"
}
output "web_ip" {
value = module.web.public_ip
}
El Terraform Registry aloja miles de módulos de la comunidad. Por ejemplo, el popular módulo terraform-aws-modules/vpc/aws crea una VPC completa con subredes, tablas de rutas y NAT gateways en pocas líneas.
Ejemplo Práctico: Stack Web Completo
Aquí tienes un ejemplo del mundo real que despliega registros DNS, un servidor y reglas de firewall:
# main.tf
terraform {
required_version = ">= 1.7.0"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "myapp-terraform-state"
key = "web-stack/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.aws_region
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
# --- Grupo de Seguridad (Firewall) ---
resource "aws_security_group" "web" {
name = "${var.project}-web-sg"
description = "Allow HTTP, HTTPS, and SSH"
vpc_id = var.vpc_id
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH"
from_port = 2222
to_port = 2222
protocol = "tcp"
cidr_blocks = var.ssh_allowed_ips
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(var.tags, {
Name = "${var.project}-web-sg"
})
}
# --- Instancia EC2 ---
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-*-24.04-amd64-server-*"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
key_name = var.ssh_key_name
vpc_security_group_ids = [aws_security_group.web.id]
subnet_id = var.subnet_id
root_block_device {
volume_size = 30
volume_type = "gp3"
encrypted = true
}
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl enable nginx
systemctl start nginx
EOF
tags = merge(var.tags, {
Name = "${var.project}-web-server"
})
}
# --- Registros DNS ---
resource "cloudflare_record" "root" {
zone_id = var.cloudflare_zone_id
name = "@"
content = aws_instance.web.public_ip
type = "A"
ttl = 300
proxied = true
}
resource "cloudflare_record" "www" {
zone_id = var.cloudflare_zone_id
name = "www"
content = var.domain
type = "CNAME"
ttl = 300
proxied = true
}
# variables.tf
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "cloudflare_api_token" {
type = string
sensitive = true
}
variable "cloudflare_zone_id" {
type = string
}
variable "domain" {
type = string
}
variable "project" {
type = string
default = "myapp"
}
variable "instance_type" {
type = string
default = "t3.micro"
}
variable "vpc_id" {
type = string
}
variable "subnet_id" {
type = string
}
variable "ssh_key_name" {
type = string
}
variable "ssh_allowed_ips" {
type = list(string)
default = []
}
variable "tags" {
type = map(string)
default = {
ManagedBy = "terraform"
}
}
# outputs.tf
output "server_public_ip" {
description = "IP pública del servidor web"
value = aws_instance.web.public_ip
}
output "website_url" {
description = "URL del sitio web"
value = "https://${var.domain}"
}
output "ssh_command" {
description = "Comando SSH para conectarse al servidor"
value = "ssh -p 2222 ubuntu@${aws_instance.web.public_ip}"
}
Despliega el stack completo:
terraform init
terraform plan -out=plan.tfplan
terraform apply plan.tfplan
Mejores Prácticas
Fijación de Versiones
Siempre fija las versiones de proveedores y de Terraform para evitar cambios inesperados:
terraform {
required_version = ">= 1.7.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.30"
}
}
}
.gitignore para Terraform
Añade esto a tu .gitignore:
# Terraform
*.tfstate
*.tfstate.*
*.tfvars
.terraform/
.terraform.lock.hcl
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
Nota: Algunos equipos eligen subir
.terraform.lock.hclal repositorio para asegurar versiones consistentes de proveedores en todo el equipo. Este es un enfoque válido, especialmente para configuraciones de producción.
Workspaces
Usa workspaces para gestionar múltiples entornos con la misma configuración:
# Crear workspaces
terraform workspace new staging
terraform workspace new production
# Cambiar de workspace
terraform workspace select staging
# Listar workspaces
terraform workspace list
Referencia el workspace en tu configuración:
resource "aws_instance" "web" {
instance_type = terraform.workspace == "production" ? "t3.large" : "t3.micro"
tags = {
Environment = terraform.workspace
}
}
Estructura de Archivos
Organiza tu proyecto Terraform con una estructura clara:
project/
main.tf # Recursos principales
variables.tf # Declaraciones de variables
outputs.tf # Declaraciones de outputs
providers.tf # Configuración de proveedores
backend.tf # Configuración del backend
terraform.tfvars # Valores de variables (no se sube al repositorio)
modules/
web-server/
main.tf
variables.tf
outputs.tf
Errores Comunes a Evitar
-
Almacenar el estado localmente — Siempre usa un backend remoto para proyectos de equipo. Los archivos de estado locales se desincronizan y causan conflictos.
-
Codificar valores directamente — Usa variables para cualquier cosa que pueda cambiar entre entornos. Los valores codificados hacen las configuraciones frágiles.
-
No usar
terraform plan— Siempre revisa el plan antes de aplicar. Un recurso mal configurado puede eliminar datos de producción. -
Subir secretos al repositorio — Nunca subas tokens API, contraseñas o archivos
terraform.tfvarscon valores sensibles. Usa variables de entorno o un gestor de secretos. -
Ignorar el bloqueo de estado — Sin bloqueo de estado (DynamoDB para S3, o Terraform Cloud), dos personas pueden modificar la infraestructura simultáneamente, causando corrupción.
-
Crear recursos manualmente — Una vez que empiezas a usar Terraform, crea todo a través de Terraform. Los cambios manuales crean divergencias entre tu código y la infraestructura real.
-
Configuraciones monolíticas masivas — Divide configuraciones grandes en módulos y archivos de estado separados. Un único archivo de estado para toda tu infraestructura es frágil y lento.
Resumen
Terraform transforma la gestión de infraestructura de un proceso manual y propenso a errores en un flujo de trabajo repetible y controlado por versiones. Comienza con un recurso simple como un registro DNS, luego construye gradualmente hasta gestionar stacks de aplicaciones completos. Los principios clave son: define todo como código, siempre revisa los planes antes de aplicar, usa estado remoto con bloqueo y divide configuraciones grandes en módulos reutilizables.
Para más contenido sobre cloud y DevOps, explora nuestros artículos de Cloud y guías de DevOps. Si estás desplegando en plataformas cloud específicas, consulta nuestras guías sobre proveedores de hosting cloud.