OpenTofu
Herramienta de infraestructura como código de código abierto y fork comunitario de Terraform.
Instalación
Sección titulada «Instalación»Instalación por Plataforma
Sección titulada «Instalación por Plataforma»| Comando | Descripción |
|---|---|
brew install opentofu | Instalar en macOS con Homebrew |
snap install --classic opentofu | Instalar en Linux con Snap |
choco install opentofu | Instalar en Windows con Chocolatey |
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh | Instalar mediante script de shell |
docker run -it ghcr.io/opentofu/opentofu | Ejecutar vía Docker |
tofu --version | Mostrar versión instalada |
tofu -help | Mostrar comandos disponibles |
Instalación con Gestor de Versiones
Sección titulada «Instalación con Gestor de Versiones»# Usando tofuenv (como tfenv para OpenTofu)
git clone https://github.com/tofuutils/tofuenv.git ~/.tofuenv
echo 'export PATH="$HOME/.tofuenv/bin:$PATH"' >> ~/.bashrc
# Listar versiones disponibles
tofuenv list-remote
# Instalar versión específica
tofuenv install 1.8.0
tofuenv use 1.8.0
# Fijar versión en proyecto
echo "1.8.0" > .opentofu-version
Comandos Principales
Sección titulada «Comandos Principales»Comandos de Flujo de Trabajo
Sección titulada «Comandos de Flujo de Trabajo»| Comando | Descripción |
|---|---|
tofu init | Inicializar directorio de trabajo y descargar proveedores |
tofu init -upgrade | Re-inicializar y actualizar versiones de proveedores/módulos |
tofu init -backend-config=prod.hcl | Inicializar con archivo de configuración de backend |
tofu plan | Previsualizar cambios de infraestructura |
tofu plan -out=plan.tfplan | Guardar plan en archivo para aplicar después |
tofu plan -target=aws_instance.web | Planificar cambios solo para recurso específico |
tofu plan -var-file=prod.tfvars | Planificar con archivo de variables específico |
tofu apply | Aplicar cambios de infraestructura (solicita confirmación) |
tofu apply -auto-approve | Aplicar cambios sin confirmación |
tofu apply plan.tfplan | Aplicar un archivo de plan guardado |
tofu destroy | Destruir toda la infraestructura gestionada |
tofu destroy -auto-approve | Destruir sin confirmación |
tofu destroy -target=aws_instance.web | Destruir solo recurso específico |
Comandos de Inspección
Sección titulada «Comandos de Inspección»| Comando | Descripción |
|---|---|
tofu validate | Validar sintaxis de configuración |
tofu fmt | Formatear archivos de configuración |
tofu fmt -check | Verificar si los archivos están formateados (compatible con CI) |
tofu fmt -recursive | Formatear todos los archivos en subdirectorios |
tofu output | Mostrar todos los valores de salida |
tofu output -json | Mostrar salidas en formato JSON |
tofu output db_password | Mostrar valor de salida específico |
tofu show | Mostrar estado actual en formato legible |
tofu show -json | Mostrar estado en formato JSON |
tofu show plan.tfplan | Mostrar detalles del plan guardado |
tofu graph | Generar gráfico de dependencias de recursos (formato DOT) |
tofu graph | dot -Tpng > graph.png | Renderizar gráfico de dependencias como PNG |
tofu refresh | Reconciliar estado con infraestructura real |
tofu providers | Listar proveedores usados en la configuración |
Configuración (HCL)
Sección titulada «Configuración (HCL)»Configuración de Proveedores
Sección titulada «Configuración de Proveedores»# Bloque de proveedores requeridos
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.5"
}
}
# Cifrado de estado (característica exclusiva de OpenTofu)
encryption {
method "aes_gcm" "default" {
keys = key_provider.pbkdf2.mykey
}
state {
method = method.aes_gcm.default
enforced = true
}
plan {
method = method.aes_gcm.default
enforced = true
}
key_provider "pbkdf2" "mykey" {
passphrase = var.state_passphrase
}
}
}
# Proveedor con configuración predeterminada
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
ManagedBy = "opentofu"
Environment = var.environment
Project = var.project_name
}
}
}
# Alias de proveedor para multi-región
provider "aws" {
alias = "west"
region = "us-west-2"
}
# Usar proveedor con alias en un recurso
resource "aws_instance" "west_server" {
provider = aws.west
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
}
Definiciones de Recursos
Sección titulada «Definiciones de Recursos»# Recurso básico
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = "${var.project_name}-web"
}
}
# Fuente de datos (leer recursos existentes)
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# Valores locales
locals {
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "opentofu"
}
az_count = length(data.aws_availability_zones.available.names)
}
# Count y for_each
resource "aws_subnet" "public" {
count = local.az_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = merge(local.common_tags, {
Name = "${var.project_name}-public-${count.index}"
Type = "public"
})
}
resource "aws_security_group_rule" "ingress" {
for_each = {
http = { port = 80, cidrs = ["0.0.0.0/0"] }
https = { port = 443, cidrs = ["0.0.0.0/0"] }
ssh = { port = 22, cidrs = [var.admin_cidr] }
}
type = "ingress"
from_port = each.value.port
to_port = each.value.port
protocol = "tcp"
cidr_blocks = each.value.cidrs
security_group_id = aws_security_group.main.id
}
Variables y Salidas
Sección titulada «Variables y Salidas»Definiciones de Variables
Sección titulada «Definiciones de Variables»| Comando | Descripción |
|---|---|
variable "name" { type = string } | Definir una variable de tipo string |
variable "port" { type = number default = 8080 } | Variable con valor predeterminado |
variable "tags" { type = map(string) } | Variable de tipo map |
variable "cidrs" { type = list(string) } | Variable de tipo lista |
variable "config" { type = object({ ... }) } | Variable de tipo objeto con estructura |
variable "db_pass" { sensitive = true } | Marcar variable como sensible (oculta en la salida) |
variable "env" { validation { ... } } | Variable con validación personalizada |
Pasar Variables
Sección titulada «Pasar Variables»| Comando | Descripción |
|---|---|
tofu plan -var="name=value" | Pasar variable vía CLI |
tofu plan -var-file="prod.tfvars" | Pasar variables desde archivo |
TF_VAR_name=value tofu plan | Pasar vía variable de entorno |
Crear archivo terraform.tfvars | Valores de variables auto-cargados |
Crear archivo *.auto.tfvars | Archivos de variables auto-cargados |
Ejemplos de Variables y Salidas
Sección titulada «Ejemplos de Variables y Salidas»# variables.tf
variable "environment" {
type = string
description = "Deployment environment"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_config" {
type = object({
instance_type = string
volume_size = number
encrypted = bool
})
default = {
instance_type = "t3.micro"
volume_size = 20
encrypted = true
}
}
variable "database_password" {
type = string
sensitive = true
}
# outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "web_url" {
description = "URL of the web server"
value = "https://${aws_lb.web.dns_name}"
}
output "db_connection_string" {
description = "Database connection string"
value = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/${var.db_name}"
sensitive = true
}
Archivos de Variables
Sección titulada «Archivos de Variables»# prod.tfvars
environment = "prod"
instance_config = {
instance_type = "t3.large"
volume_size = 100
encrypted = true
}
# dev.tfvars
environment = "dev"
instance_config = {
instance_type = "t3.micro"
volume_size = 20
encrypted = false
}
Gestión de Estado
Sección titulada «Gestión de Estado»Comandos de Estado
Sección titulada «Comandos de Estado»| Comando | Descripción |
|---|---|
tofu state list | Listar todos los recursos en el estado |
tofu state show aws_instance.web | Mostrar detalles de recurso específico |
tofu state mv aws_instance.old aws_instance.new | Renombrar recurso en el estado |
tofu state rm aws_instance.web | Eliminar recurso del estado (mantiene la infraestructura real) |
tofu state pull | Obtener estado remoto a stdout |
tofu state push | Enviar estado local al backend remoto |
tofu import aws_instance.web i-1234567 | Importar recurso existente al estado |
tofu state replace-provider hashicorp/aws registry.opentofu.org/hashicorp/aws | Reemplazar proveedor en el estado |
tofu force-unlock LOCK_ID | Liberar un bloqueo de estado atascado |
Configuración de Backend
Sección titulada «Configuración de Backend»# Backend S3 (más común)
terraform {
backend "s3" {
bucket = "my-tofu-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tofu-locks"
encrypt = true
}
}
# Backend PostgreSQL
terraform {
backend "pg" {
conn_str = "postgres://user:pass@db.example.com/tofu_state?sslmode=require"
}
}
# Backend HTTP (funciona con GitLab, alternativas a Terraform Cloud)
terraform {
backend "http" {
address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod"
lock_address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
unlock_address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
username = "gitlab-ci-token"
password = var.ci_token
}
}
# Backend local (predeterminado, para desarrollo)
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
Cifrado de Estado (Exclusivo de OpenTofu)
Sección titulada «Cifrado de Estado (Exclusivo de OpenTofu)»# Cifrado de estado con AWS KMS
terraform {
encryption {
key_provider "aws_kms" "main" {
kms_key_id = "arn:aws:kms:us-east-1:123456789:key/abcd-1234"
region = "us-east-1"
key_spec = "AES_256"
}
method "aes_gcm" "default" {
keys = key_provider.aws_kms.main
}
state {
method = method.aes_gcm.default
enforced = true
}
}
}
Módulos
Sección titulada «Módulos»Uso de Módulos
Sección titulada «Uso de Módulos»| Comando | Descripción |
|---|---|
module "vpc" { source = "./modules/vpc" } | Usar un módulo local |
module "vpc" { source = "github.com/org/repo//modules/vpc" } | Usar un módulo de GitHub |
module "vpc" { source = "registry.opentofu.org/..." } | Usar módulo del registro de OpenTofu |
tofu get | Descargar y actualizar módulos |
tofu get -update | Forzar actualización de todos los módulos |
Estructura de Módulo
Sección titulada «Estructura de Módulo»modules/
└── vpc/
├── main.tf # Recursos
├── variables.tf # Variables de entrada
├── outputs.tf # Valores de salida
├── versions.tf # Requisitos de proveedores
└── README.md # Documentación de uso
Ejemplo de Módulo
Sección titulada «Ejemplo de Módulo»# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = merge(var.tags, { Name = var.name })
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnets[count.index]
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name}-public-${count.index}"
})
}
# modules/vpc/variables.tf
variable "name" { type = string }
variable "cidr" { type = string }
variable "azs" { type = list(string) }
variable "public_subnets" { type = list(string) }
variable "tags" { type = map(string) default = {} }
# modules/vpc/outputs.tf
output "vpc_id" { value = aws_vpc.this.id }
output "public_subnet_ids" { value = aws_subnet.public[*].id }
# Usando el módulo
module "vpc" {
source = "./modules/vpc"
name = "production"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
tags = local.common_tags
}
Migración desde Terraform
Sección titulada «Migración desde Terraform»Compatibilidad Directa
Sección titulada «Compatibilidad Directa»| Comando | Descripción |
|---|---|
Reemplazar terraform con tofu en comandos | Los comandos CLI son compatibles directamente |
tofu init -upgrade | Re-inicializar con proveedores de OpenTofu |
Mantener archivos .tf existentes sin cambios | La sintaxis HCL es totalmente compatible |
Mantener archivos .tfstate existentes | El formato de estado es compatible |
Mantener .terraform.lock.hcl existente | El archivo de bloqueo es compatible |
Actualizar CI/CD para usar la acción opentofu/setup-opentofu | Cambiar GitHub Actions |
Lista de Verificación de Migración
Sección titulada «Lista de Verificación de Migración»Migración de Terraform a OpenTofu:
1. Instalar OpenTofu junto a Terraform
2. En el directorio de tu proyecto, ejecutar: tofu init
3. Ejecutar: tofu plan
- Comparar salida con: terraform plan
- Debería ser idéntico (sin cambios)
4. Reemplazar 'terraform' con 'tofu' en pipelines de CI/CD
5. Actualizar GitHub Actions:
- Anterior: hashicorp/setup-terraform
- Nuevo: opentofu/setup-opentofu
6. Actualizar fuentes de proveedores en el archivo de bloqueo (si es necesario):
- tofu providers lock
7. Habilitar cifrado de estado (exclusivo de OpenTofu):
- Agregar bloque encryption al bloque terraform {}
8. Cambiar referencias del registro de módulos:
- registry.terraform.io → registry.opentofu.org
- (Ambos siguen funcionando, pero el registro de OpenTofu es preferido)
GitHub Actions CI/CD
Sección titulada «GitHub Actions CI/CD»name: OpenTofu Plan & Apply
on:
push:
branches: [main]
pull_request:
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: opentofu/setup-opentofu@v1
with:
tofu_version: 1.8.0
- name: Init
run: tofu init
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Plan
run: tofu plan -out=plan.tfplan -no-color
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Apply (main only)
if: github.ref == 'refs/heads/main'
run: tofu apply -auto-approve plan.tfplan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Características Exclusivas de OpenTofu
Sección titulada «Características Exclusivas de OpenTofu»Cifrado de Estado
Sección titulada «Cifrado de Estado»Cifrar archivos de estado en reposo — no disponible en Terraform. Soporta proveedores de claves PBKDF2, AWS KMS, GCP KMS y Azure Key Vault.
Cifrado de Estado del Lado del Cliente
Sección titulada «Cifrado de Estado del Lado del Cliente»A diferencia del cifrado del lado del servidor de Terraform Cloud, OpenTofu cifra el estado antes de que salga de tu máquina, asegurando que ningún tercero pueda leer el estado de tu infraestructura.
Independencia de Registro
Sección titulada «Independencia de Registro»OpenTofu mantiene su propio registro de proveedores y módulos en registry.opentofu.org, asegurando disponibilidad a largo plazo independiente de las decisiones de licenciamiento de HashiCorp.
Mejores Prácticas
Sección titulada «Mejores Prácticas»-
Usa estado remoto con bloqueo — configura un backend S3 + DynamoDB (o equivalente) para prevenir modificaciones concurrentes y habilitar la colaboración en equipo.
-
Habilita el cifrado de estado — usa la característica exclusiva de cifrado de OpenTofu con KMS o PBKDF2 para proteger datos sensibles en archivos de estado.
-
Fija versiones de proveedores y módulos — usa restricciones
~>(ej.,~> 5.0) para permitir actualizaciones de parches mientras previenes cambios incompatibles. -
Usa workspaces o separación de directorios para entornos — mantén el estado de dev, staging y producción separado para prevenir cambios accidentales entre entornos.
-
Escribe módulos para patrones reutilizables — extrae patrones comunes (VPCs, bases de datos, clústeres de Kubernetes) en módulos con entradas y salidas claras.
-
Valida variables con restricciones — agrega bloques
validationa las variables para capturar valores inválidos antes de que lleguen al proveedor de nube. -
Guarda planes antes de aplicar — usa
tofu plan -out=plan.tfplanluegotofu apply plan.tfplanpara asegurar que aplicas exactamente lo que revisaste. -
Usa
default_tagsen proveedores — establece etiquetas a nivel de organización (ManagedBy, Environment, Project) en el nivel del proveedor para asegurar etiquetado consistente. -
Importa recursos existentes — usa
tofu importpara traer infraestructura no gestionada bajo control de OpenTofu en lugar de recrearla. -
Ejecuta
tofu fmt -checken CI — aplica formateo consistente entre todos los miembros del equipo haciendo fallar las builds de CI en archivos sin formatear. -
Usa fuentes de datos para referencias externas — referencia infraestructura existente (AMIs, VPCs, zonas DNS) con bloques
dataen lugar de codificar IDs directamente. -
Revisa los planes cuidadosamente — siempre lee la salida del plan antes de aplicar. Presta atención a acciones de
destroyyreplaceque podrían causar tiempo de inactividad.