Sintaxis del bloque module

El bloque module es la forma de invocar un módulo secundario desde el módulo raíz (o desde otro módulo secundario). Su sintaxis básica es:

module "<nombre_local>" {
  source = "<fuente_del_modulo>"

  # Variables de entrada del módulo
  variable_1 = valor_1
  variable_2 = valor_2
}

Cada parte tiene un significado preciso:

  • <nombre_local>: identificador único para esta instancia del módulo. Puedes usar el mismo módulo varias veces con distintos nombres locales. Se usa para referenciar los outputs: module.<nombre_local>.<output>.
  • source: indica dónde está el módulo. Es el único argumento obligatorio.
  • Variables: el resto de argumentos corresponden a las variables declaradas en el variables.tf del módulo. Si una variable no tiene default, es obligatorio proporcionarla aquí.

Argumentos especiales del bloque module

Además de las variables del módulo, existen argumentos especiales que controla Terraform, no el módulo:

module "mi_instancia" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "~> 5.0"

  # Variables del módulo
  name          = "mi-servidor"
  instance_type = "t3.micro"

  # --- Argumentos especiales de Terraform ---

  # count: crea múltiples instancias del módulo (como con recursos)
  count = 3

  # for_each: crea una instancia por cada elemento del mapa o set
  # (no se puede usar count y for_each simultáneamente)
  # for_each = { dev = "t3.micro", prod = "t3.large" }

  # depends_on: dependencias explícitas con otros recursos o módulos
  depends_on = [module.red]

  # providers: especifica qué configuración de provider usar (avanzado)
  # providers = {
  #   aws = aws.eu_west_1
  # }
}

Fuentes de módulos: local, Terraform Registry, GitHub y S3

El argumento source acepta distintos formatos según dónde esté el módulo. Terraform identifica el tipo de fuente automáticamente por el formato del string.

Fuente local

La más sencilla: el módulo vive en el mismo sistema de archivos. La ruta es relativa al archivo que hace la llamada:

# Módulo en un subdirectorio
module "red" {
  source = "./modules/networking"
}

# Módulo en el directorio padre
module "compartido" {
  source = "../modules/comun"
}

# Módulo varios niveles arriba
module "corporativo" {
  source = "../../infraestructura-base/modules/seguridad"
}

Importante: las rutas locales siempre empiezan con ./ o ../. Sin esa barra inicial, Terraform interpreta la ruta como un módulo del registry.

Terraform Registry

El formato para el registry público es <namespace>/<nombre>/<provider>:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "mi-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
}

GitHub

Puedes referenciar directamente repositorios GitHub. El doble slash // separa el repositorio del subdirectorio:

# Todo el repositorio como módulo
module "ejemplo" {
  source = "github.com/hashicorp/example"
}

# Subdirectorio específico dentro del repositorio
module "s3_bucket" {
  source = "github.com/terraform-aws-modules/terraform-aws-s3-bucket//modules/object"
}

# Con referencia a una rama, tag o commit específico
module "s3_bucket_v3" {
  source = "github.com/terraform-aws-modules/terraform-aws-s3-bucket//modules/object?ref=v3.15.1"
}

Repositorios Git genéricos

Para repositorios en otros servidores Git (GitLab, Bitbucket, servidores privados):

# HTTPS (repositorio público o con autenticación via .netrc)
module "interno" {
  source = "git::https://gitlab.empresa.com/infra/terraform-modules.git//vpc?ref=v2.0.0"
}

# SSH (requiere clave SSH configurada)
module "privado" {
  source = "git::ssh://[email protected]/infra/terraform-modules.git//networking?ref=main"
}

Amazon S3

Para módulos almacenados en buckets S3:

module "desde_s3" {
  source = "s3::https://s3-eu-west-1.amazonaws.com/mi-bucket-terraform/modulos/servidor-v1.2.0.zip"
}

Google Cloud Storage

module "desde_gcs" {
  source = "gcs::https://www.googleapis.com/storage/v1/mi-bucket/modulos/networking.zip"
}

El argumento source y sus formatos

Tabla resumen de todos los formatos de source:

Tipo de fuente Formato Requiere version Autenticación
Local ./ruta/al/modulo No No
Terraform Registry (público) namespace/nombre/provider Recomendado No
Terraform Registry (privado) app.terraform.io/org/nombre/provider Recomendado Token TF Cloud
GitHub HTTPS github.com/org/repo//subdir?ref=tag Via ?ref= Token (repo privado)
GitHub SSH git::ssh://[email protected]/org/repo.git//subdir?ref=tag Via ?ref= Clave SSH
GitLab HTTPS git::https://gitlab.com/org/repo.git//subdir?ref=tag Via ?ref= Token
Git genérico git::https://servidor.com/repo.git?ref=tag Via ?ref= Variable
S3 s3::https://s3-region.amazonaws.com/bucket/archivo.zip Via nombre IAM
GCS gcs::https://www.googleapis.com/storage/v1/bucket/archivo.zip Via nombre SA
HTTP/HTTPS https://example.com/modulo.zip Via URL Variable

Pasar variables a un módulo

Al invocar un módulo, los argumentos del bloque module (excepto los especiales como source, version, count, for_each, depends_on, providers) se mapean directamente a las variables declaradas en variables.tf del módulo:

# Si el módulo tiene en variables.tf:
# variable "nombre" { type = string }
# variable "instancias" { type = number, default = 1 }
# variable "etiquetas" { type = map(string), default = {} }

module "mi_servidor" {
  source = "./modules/servidor"

  # Pasamos valores para las variables del módulo
  nombre     = "servidor-web"           # string
  instancias = 3                        # number
  etiquetas  = {                        # map(string)
    Entorno  = "produccion"
    Proyecto = "tienda-online"
    Equipo   = "backend"
  }
  # 'instancias' y 'etiquetas' tienen default, así que son opcionales
}

Pasar expressions como valores de variables

Los valores de las variables no tienen que ser literales: pueden ser cualquier expresión de Terraform, incluyendo referencias a otros recursos, outputs de otros módulos o variables del módulo raíz:

variable "entorno" {
  type    = string
  default = "dev"
}

module "base_datos" {
  source = "./modules/database"

  # Usando una variable del módulo raíz
  entorno = var.entorno

  # Usando el output de otro módulo
  subnet_ids = module.red.private_subnet_ids

  # Usando una función de Terraform
  nombre_bd = "${var.entorno}-db-${formatdate("YYYYMMDD", timestamp())}"

  # Expresión condicional
  multi_az = var.entorno == "production" ? true : false
}

Acceder a outputs de un módulo

Para usar los valores que un módulo expone (sus outputs), se usa la sintaxis:

module.<nombre_local>.<nombre_output>
module "red" {
  source     = "./modules/networking"
  cidr_block = "10.0.0.0/16"
}

module "compute" {
  source = "./modules/compute"

  # Usamos el output 'subnet_id' del módulo 'red'
  subnet_id = module.red.subnet_id
  vpc_id    = module.red.vpc_id
}

# Podemos usar los outputs en el módulo raíz también
output "ip_servidor" {
  description = "IP pública del servidor creado"
  value       = module.compute.ip_publica
}

output "url_aplicacion" {
  description = "URL de acceso a la aplicación"
  value       = "https://${module.compute.ip_publica}"
}

Acceder a outputs de módulos con count o for_each

Cuando usas count o for_each en un módulo, accedes a los outputs de cada instancia mediante índice o clave:

# Con count: se crea una lista de módulos
module "servidor" {
  source = "./modules/servidor"
  count  = 3

  nombre = "servidor-${count.index}"
}

# Acceder al output de una instancia específica
output "ip_primer_servidor" {
  value = module.servidor[0].ip_publica
}

# Acceder a todos los outputs como lista
output "todas_las_ips" {
  value = module.servidor[*].ip_publica
}
# Con for_each: se crea un mapa de módulos
module "servidor" {
  source   = "./modules/servidor"
  for_each = toset(["web", "api", "worker"])

  nombre = each.key
}

# Acceder al output de una instancia específica
output "ip_servidor_web" {
  value = module.servidor["web"].ip_publica
}

# Acceder a todos los outputs como mapa
output "todas_las_ips" {
  value = { for k, v in module.servidor : k => v.ip_publica }
}

El argumento version en módulos del registry

Cuando usas módulos del Terraform Registry, siempre debes especificar version. Sin él, Terraform usará la versión más reciente disponible, lo que puede causar cambios inesperados en tu infraestructura:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"          # Versión exacta: muy restrictivo
}

module "ec2" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "~> 5.0"         # >= 5.0, < 6.0: acepta parches y menores
}

module "rds" {
  source  = "terraform-aws-modules/rds/aws"
  version = ">= 6.0, < 7.0"  # Rango explícito
}

module "s3" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "!= 3.5.0"        # Cualquier versión excepto la 3.5.0 (tiene un bug)
}

Operadores de versión

Operador Significado Ejemplo Acepta
= o sin operador Versión exacta "= 5.1.2" Solo 5.1.2
!= Excluir versión "!= 5.1.2" Todo excepto 5.1.2
> Mayor que "> 5.0" 5.0.1, 5.1.0, 6.0.0...
>= Mayor o igual ">= 5.0" 5.0.0, 5.1.0, 6.0.0...
< Menor que "< 6.0" 5.x.x pero no 6.0.0
<= Menor o igual "<= 5.9.9" Hasta 5.9.9
~> Pessimistic constraint "~> 5.1" >= 5.1, < 6.0
~> (parche) Pessimistic constraint "~> 5.1.0" >= 5.1.0, < 5.2.0

El operador ~> (tilde-mayor) es el más usado: permite actualizaciones de parches y menores pero bloquea cambios mayores.


Módulos anidados (nested modules)

Un módulo puede invocar a otros módulos, creando una jerarquía. Esto permite componer módulos complejos a partir de módulos simples:

root/
├── main.tf                   ← módulo raíz
└── modules/
    ├── aplicacion-completa/  ← módulo de nivel 1
    │   ├── main.tf           ← llama a los módulos de nivel 2
    │   ├── variables.tf
    │   ├── outputs.tf
    │   └── (no tiene submódulos propios aquí)
    ├── red/                  ← módulo de nivel 2
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── servidor/             ← módulo de nivel 2
        ├── main.tf
        ├── variables.tf
        └── outputs.tf
# modules/aplicacion-completa/main.tf
# Este módulo "orquesta" otros módulos

module "red_interna" {
  source     = "../red"       # ruta relativa desde este módulo
  cidr_block = var.cidr_red
  nombre     = "${var.nombre_app}-red"
}

module "servidor_web" {
  source    = "../servidor"
  nombre    = "${var.nombre_app}-web"
  subnet_id = module.red_interna.subnet_id  # output del módulo de red
}

Advertencia: evita anidar módulos más de 2-3 niveles. La profundidad excesiva hace que los errores sean difíciles de rastrear y que las referencias de outputs se vuelvan complicadas.


terraform get y terraform init con módulos

Cuando añades o modificas módulos, Terraform necesita descargados o localizarlos antes de poder crear el plan:

terraform init

Es el comando principal para inicializar un directorio de trabajo. Hace múltiples cosas:

# Inicialización completa: descarga providers Y módulos
terraform init

# Salida típica cuando hay módulos:
# Initializing modules...
# - red in modules/networking
# - compute in modules/compute
# Downloading terraform-aws-modules/vpc/aws 5.1.2 for vpc...
# - vpc in .terraform/modules/vpc
#
# Initializing provider plugins...
# ...

terraform get

Solo descarga módulos (no toca los providers). Útil cuando solo has añadido o cambiado módulos:

# Descarga los módulos referenciados
terraform get

# Actualiza los módulos ya descargados a sus versiones más recientes
# (respetando las restricciones de version)
terraform get -update

¿Cuándo usar cada uno?

Situación Comando recomendado
Primera vez en el proyecto terraform init
Añadiste un nuevo módulo terraform init o terraform get
Cambiaste el source de un módulo terraform init
Quieres actualizar módulos del registry terraform get -update
Añadiste un nuevo provider terraform init
Cambiaste restricciones de versión de módulo terraform get -update

Actualizar módulos: terraform get -update

Cuando quieres actualizar los módulos del registry a versiones más recientes (dentro de los rangos permitidos):

# Ver versiones actuales descargadas
cat .terraform/modules/modules.json

# Actualizar módulos a la versión más reciente permitida
terraform get -update

# Tras actualizar, verificar el plan para ver qué cambia
terraform plan

El archivo .terraform/modules/modules.json registra qué versión de cada módulo está descargada:

{
  "Modules": [
    {
      "Key": "vpc",
      "Source": "registry.terraform.io/terraform-aws-modules/vpc/aws",
      "Version": "5.1.2",
      "Dir": ".terraform/modules/vpc"
    }
  ]
}

Ejemplos completos de uso de módulos locales y del registry

Ejemplo 1: Usando módulos locales encadenados

# main.tf — módulo raíz que orquesta la infraestructura completa

terraform {
  required_version = ">= 1.0"
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
  }
}

# Módulo de "base de datos" (conceptual con archivos locales)
module "base_datos_dev" {
  source = "./modules/configuracion-app"

  nombre_aplicacion = "postgresql"
  directorio_destino = "/tmp/configs/dev/db"

  configuracion_servidor = {
    host    = "localhost"
    puerto  = 5432
    workers = 4
  }
  entorno = "development"
}

# Módulo de "API" que depende de la base de datos
module "api_dev" {
  source = "./modules/configuracion-app"

  nombre_aplicacion = "mi-api"
  directorio_destino = "/tmp/configs/dev/api"

  configuracion_servidor = {
    host    = "localhost"
    puerto  = 3000
    workers = 2
  }
  entorno = "development"

  # Dependencia explícita: la API se configura después de la BD
  depends_on = [module.base_datos_dev]
}

# Outputs del módulo raíz
output "archivos_creados" {
  value = {
    base_datos = module.base_datos_dev.resumen
    api        = module.api_dev.resumen
  }
}

Ejemplo 2: Usando módulos del Terraform Registry (AWS)

# Nota: Este ejemplo requiere credenciales AWS configuradas.
# Lo incluimos como referencia de sintaxis real.

terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-west-1"
}

# -----------------------------------------------
# Módulo de VPC del registry oficial
# Documentación: registry.terraform.io/modules/terraform-aws-modules/vpc/aws
# -----------------------------------------------
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "mi-proyecto-vpc"
  cidr = "10.0.0.0/16"

  # Zonas de disponibilidad donde crear subredes
  azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]

  # Subredes privadas (sin acceso directo a internet)
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]

  # Subredes públicas (con acceso a internet via IGW)
  public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  # Crear NAT Gateway para que las subredes privadas puedan acceder a internet
  enable_nat_gateway = true
  # Un solo NAT gateway para ahorrar costes (en producción usa uno por AZ)
  single_nat_gateway = true

  # Etiquetas aplicadas a todos los recursos del módulo
  tags = {
    Proyecto   = "mi-proyecto"
    Entorno    = "produccion"
    GestionPor = "terraform"
  }
}

# -----------------------------------------------
# Módulo de instancia EC2 del registry oficial
# -----------------------------------------------
module "servidor_web" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "~> 5.0"

  name = "servidor-web"

  # AMI de Amazon Linux 2023 (ejemplo, el ID varía por región)
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  # Usamos el output del módulo VPC para colocar el servidor en la subred correcta
  subnet_id              = module.vpc.public_subnets[0]
  vpc_security_group_ids = [aws_security_group.web.id]

  # Asignación de IP pública (necesario en subred pública)
  associate_public_ip_address = true

  tags = {
    Proyecto = "mi-proyecto"
    Rol      = "web-server"
  }
}

# Grupo de seguridad (definido directamente, no como módulo)
resource "aws_security_group" "web" {
  name   = "sg-web"
  vpc_id = module.vpc.vpc_id  # Output del módulo VPC

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Outputs del módulo raíz
output "vpc_id" {
  value = module.vpc.vpc_id
}

output "ip_servidor" {
  value = module.servidor_web.public_ip
}

Ejercicio: Usar el módulo creado en el tema anterior y el módulo oficial de AWS VPC

Contexto: Tienes el módulo gestion-proyecto creado en el tema 04-02. Ahora debes:

  1. Crear un módulo raíz que use gestion-proyecto para tres proyectos diferentes.
  2. Usar for_each para crear los tres proyectos con una sola llamada al módulo.
  3. Exponer un output que liste todos los directorios creados como un mapa.

Datos de los proyectos:

  • portal-clientes: "Portal web para gestión de clientes", versión "3.1.0", autor "Equipo Frontend"
  • api-pagos: "API de procesamiento de pagos", versión "2.0.5", autor "Equipo Backend"
  • worker-emails: "Worker asíncrono para envío de emails", versión "1.2.0", autor "Equipo Plataforma"

Solución detallada

# main.tf

terraform {
  required_version = ">= 1.0"
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
  }
}

# Definimos los proyectos como un mapa de objetos.
# Esto nos permite usar for_each para crear todos los proyectos con una sola
# llamada al módulo.
locals {
  proyectos = {
    "portal-clientes" = {
      descripcion = "Portal web para gestión de clientes"
      version     = "3.1.0"
      autor       = "Equipo Frontend"
      caracteristicas = ["autenticacion-sso", "dashboard-analiticas", "exportacion-csv"]
    }
    "api-pagos" = {
      descripcion = "API de procesamiento de pagos"
      version     = "2.0.5"
      autor       = "Equipo Backend"
      caracteristicas = ["stripe-integration", "webhooks", "idempotencia"]
    }
    "worker-emails" = {
      descripcion = "Worker asíncrono para envío de emails"
      version     = "1.2.0"
      autor       = "Equipo Plataforma"
      caracteristicas = ["plantillas-html", "cola-reintentos", "tracking"]
    }
  }
}

# for_each en el bloque module: crea una instancia del módulo por cada proyecto.
# each.key es la clave del mapa (nombre del proyecto).
# each.value es el objeto con los datos del proyecto.
module "proyectos" {
  source   = "./modules/gestion-proyecto"
  for_each = local.proyectos

  # each.key contiene el nombre del proyecto (clave del mapa)
  nombre_proyecto = each.key

  # each.value contiene el objeto con los demás datos
  descripcion     = each.value.descripcion
  version         = each.value.version
  autor           = each.value.autor
  caracteristicas = each.value.caracteristicas
}

# Output: mapa de nombre_proyecto => directorio_base
# La expresión for transforma el mapa de módulos en un nuevo mapa.
output "directorios_proyectos" {
  description = "Mapa de proyectos a sus directorios base creados."
  value = {
    # k es el nombre del proyecto (each.key), v es la instancia del módulo
    for k, v in module.proyectos : k => v.directorio_base
  }
}

# Output: lista de todos los archivos README creados
output "todos_los_readme" {
  description = "Lista de rutas de todos los README.md creados."
  value = [
    for k, v in module.proyectos : v.ruta_readme
  ]
}

# Output: mapa completo con todos los detalles
output "resumen_completo" {
  description = "Información completa de todos los proyectos creados."
  value = {
    for k, v in module.proyectos : k => {
      directorio    = v.directorio_base
      readme        = v.ruta_readme
      configuracion = v.ruta_configuracion
    }
  }
}

Para ejecutar y verificar:

# 1. Inicializar el proyecto
terraform init

# 2. Ver el plan: qué recursos se crearán
terraform plan

# Salida esperada del plan:
# Plan: 9 to add, 0 to change, 0 to destroy.
# (3 proyectos × 3 archivos cada uno = 9 archivos)

# 3. Aplicar los cambios
terraform apply -auto-approve

# 4. Ver los outputs
terraform output directorios_proyectos
# {
#   "api-pagos" = "/tmp/proyectos/api-pagos"
#   "portal-clientes" = "/tmp/proyectos/portal-clientes"
#   "worker-emails" = "/tmp/proyectos/worker-emails"
# }

terraform output todos_los_readme
# tolist([
#   "/tmp/proyectos/api-pagos/README.md",
#   "/tmp/proyectos/portal-clientes/README.md",
#   "/tmp/proyectos/worker-emails/README.md",
# ])

# 5. Verificar los archivos creados
ls /tmp/proyectos/
# api-pagos  portal-clientes  worker-emails

cat /tmp/proyectos/portal-clientes/README.md

# 6. Limpiar recursos cuando termines
terraform destroy -auto-approve

Cómo referenciar una instancia específica con for_each

# Si necesitas el directorio de un proyecto específico:
output "directorio_api_pagos" {
  value = module.proyectos["api-pagos"].directorio_base
}

# Si necesitas verificar que una clave existe antes de usarla:
output "directorio_condicional" {
  value = contains(keys(module.proyectos), "api-pagos") ? module.proyectos["api-pagos"].directorio_base : "no existe"
}

Errores comunes al usar módulos

Error 1: Olvidar terraform init tras añadir un módulo

Error: Module not installed

  on main.tf line 5:
   5: module "vpc" {

This module is not yet installed. Run "terraform init" to install all modules
required by this configuration.

Solución: terraform init siempre antes de terraform plan o terraform apply cuando hay módulos nuevos.

Error 2: Usar source sin ./ para módulos locales

# INCORRECTO: Terraform busca "modules/networking" en el registry público
module "red" {
  source = "modules/networking"  # ← falta ./
}

# CORRECTO
module "red" {
  source = "./modules/networking"
}

Error 3: No especificar version en módulos del registry

# PELIGROSO: sin version, usará siempre la última disponible
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  # Si publican la versión 6.0 con breaking changes, tu infra se romperá
}

# CORRECTO
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"
}

Error 4: Intentar acceder a un output que no existe en el módulo

Error: Unsupported attribute

  on main.tf line 25, in output "mi_output":
  25:   value = module.servidor.ip_privada

This object does not have an attribute named "ip_privada".

Solución: Verifica que el output ip_privada está declarado en el outputs.tf del módulo servidor.

Error 5: Cambiar count o for_each de un módulo existente

Si tu módulo tenía count = 2 y lo cambias a count = 3, Terraform entiende que los recursos existentes son module.servidor[0] y module.servidor[1], y el nuevo es module.servidor[2]. Esto es correcto.

Pero si cambias de count a for_each, o viceversa, Terraform lo trata como destrucción y recreación de todos los recursos del módulo:

# Cambiar de esto:
module "servidor" {
  count = 2
  ...
}

# A esto:
module "servidor" {
  for_each = toset(["web", "api"])
  ...
}
# RESULTADO: Terraform destruye module.servidor[0], module.servidor[1]
# y crea module.servidor["web"], module.servidor["api"]
# ¡Puede causar downtime!

Solución: Planifica con antelación si usarás count o for_each. Usa terraform state mv si necesitas migrar sin recrear.

Error 6: Módulos anidados con rutas source incorrectas

Cuando un módulo anidado llama a otro módulo, la ruta source es relativa al módulo que hace la llamada, no al módulo raíz:

root/
├── main.tf
└── modules/
    ├── orquestador/
    │   └── main.tf        ← llama a ../networking
    └── networking/
        └── main.tf
# modules/orquestador/main.tf
module "red" {
  source = "../networking"   # Correcto: relativo a modules/orquestador/
  # NO: source = "./modules/networking"  (sería relativo al root)
}

Resumen

En esta lección has aprendido todo lo necesario para usar módulos de Terraform de forma efectiva:

  • La sintaxis del bloque module y sus argumentos especiales (count, for_each, depends_on).
  • Los distintos formatos de source: local, Terraform Registry, GitHub, Git genérico, S3.
  • Cómo pasar variables a un módulo y cómo usar expresiones como valores.
  • Cómo acceder a outputs de un módulo con module.<nombre>.<output>, y cómo hacerlo con módulos instanciados con count o for_each.
  • La importancia de especificar version en módulos del registry y los operadores disponibles.
  • Cuándo usar terraform init vs terraform get para gestionar módulos.
  • Los errores más comunes y cómo evitarlos.

En el siguiente tema explorarás el Terraform Registry: cómo buscar y evaluar módulos públicos, cómo publicar tus propios módulos y cómo trabajar con módulos privados en Terraform Cloud.

© Copyright 2026. Todos los derechos reservados