StackPractices
intermediate Por Mathias Paulenko

Plantilla de Política de Versionado de Módulos Terraform

Política lista para adaptar sobre versionado y publicación de módulos Terraform: reglas de semver, gestión de breaking changes y deprecación, formato de changelog, publicación en registro y pipelines CI/CD de release.

Temas: devops

Visión General

Una política que tu equipo puede copiar y adaptar para versionar módulos Terraform: reglas de versionado semántico, flujos de breaking changes y deprecación, requisitos de changelog, publicación en registro y pipelines CI/CD de release. Úsala tal cual o recorta las secciones que no necesites — sirve para el Terraform Registry público, registros privados en Terraform Cloud/Enterprise y fuentes git simples.


1. Reglas de Versionado Semántico

1.1 Formato de Versión

Formato: MAJOR.MINOR.PATCH (ej.: 2.3.1)

MAJOR (X.0.0) — Cambios que rompen compatibilidad
  - Variable renombrada o eliminada
  - Tipo de recurso cambiado o eliminado
  - Output renombrado o eliminado
  - Versión mínima de Terraform incrementada
  - Constraint de versión de provider cambiada a incompatible

MINOR (0.X.0) — Nuevas funcionalidades, compatible hacia atrás
  - Nueva variable añadida (con default)
  - Nuevo recurso añadido
  - Nuevo output añadido
  - Nuevo feature flag añadido

PATCH (0.0.X) — Correcciones, compatible hacia atrás
  - Bug fix en configuración de recursos
  - Actualización de documentación
  - Bump de versión de dependencia (compatible)
  - Actualización de ejemplos

1.2 Versiones Pre-release

1.0.0-alpha.1   — Preview temprano, espera breaking changes
1.0.0-beta.1    — Funcionalidad completa, en pruebas
1.0.0-rc.1      — Release candidate, pruebas finales
1.0.0           — Release estable

1.3 Cómo Elegir el Incremento

Cuando no tengas claro qué número mover, recorre la API pública del módulo: inputs, outputs y constraints de versión.

Árbol de decisión para elegir el incremento semver — los cambios que rompen la API pública llevan major, las adiciones compatibles minor, y las correcciones internas patch.

2. Estructura del Módulo

2.1 Archivos Requeridos

modules/
└── my-module/
    ├── main.tf          # Definiciones de recursos
    ├── variables.tf     # Variables de entrada
    ├── outputs.tf       # Valores de salida
    ├── versions.tf      # Constraints de Terraform y providers
    ├── README.md        # Documentación del módulo
    ├── CHANGELOG.md     # Historial de versiones
    ├── examples/        # Ejemplos de uso
    │   └── basic/
    │       ├── main.tf
    │       └── README.md
    └── tests/
        └── basic_test.go  # Terratest o kitchen-terraform

2.2 versions.tf — Restricciones de Versión

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0, < 6.0"
    }
  }
}

2.3 Plantilla de README.md

# my-module

Descripción corta de lo que hace este módulo.

## Uso

    module "my_module" {
      source  = "myorg/my-module/aws"
      version = "2.3.1"

      name = "my-app"
      environment = "production"
    }

## Inputs

| Nombre | Descripción | Tipo | Default | Requerido |
|--------|-------------|------|---------|-----------|
| name | Nombre del recurso | string | n/a | sí |
| environment | Nombre del entorno | string | "production" | no |

## Outputs

| Nombre | Descripción |
|--------|-------------|
| id | ID del recurso |
| arn | ARN del recurso |

3. Gestión de Breaking Changes

Los cambios que rompen compatibilidad son inevitables en módulos compartidos — lo importante es que los consumidores los vean venir. Esta sección define qué tiene que salir junto a cada release incompatible.

3.1 Checklist de Breaking Changes

- [ ] Sube la versión MAJOR
- [ ] Documenta el breaking change en CHANGELOG.md
- [ ] Incluye una guía de migración en CHANGELOG.md
- [ ] Añade un aviso de deprecación en la versión MINOR anterior
- [ ] Actualiza todos los ejemplos para usar la nueva API
- [ ] Ejecuta los tests contra la nueva versión
- [ ] Notifica a los equipos consumidores por Slack/email
- [ ] Fija una fecha de deprecación para la versión antigua (mínimo 90 días)
- [ ] Crea un issue tracker para el progreso de migración
- [ ] Actualiza la documentación del módulo

3.2 Patrón de Deprecación

variable "instance_type" {
  description = "(Deprecated) Usa `instance_types` en su lugar. Se eliminará en v3.0.0."
  type        = string
  default     = null
  deprecated  = true
}

variable "instance_types" {
  description = "Lista de tipos de instancia para el auto-scaling group"
  type        = list(string)
  default     = ["t3.micro"]
}

3.3 Plantilla de Guía de Migración

## Guía de Migración: v1.x a v2.x

### Breaking Changes

1. Variable `instance_type` eliminada → usa `instance_types` (list)
2. El output `tags` cambió de `map(string)` a `map(any)`
3. La versión mínima de Terraform subió de 1.0 a 1.5

### Pasos de Migración

1. Actualiza la versión del módulo en tu configuración:

       module "my_module" {
         source  = "myorg/my-module/aws"
         version = "2.0.0"
       }

2. Reemplaza `instance_type` por `instance_types`:

       # Antes (v1.x)
       module "my_module" {
         instance_type = "t3.micro"
       }

       # Después (v2.x)
       module "my_module" {
         instance_types = ["t3.micro"]
       }

3. Ejecuta `terraform init -upgrade` para descargar la nueva versión
4. Ejecuta `terraform plan` y revisa los cambios
5. Ejecuta `terraform apply` cuando estés listo

4. Requisitos del CHANGELOG

4.1 Formato del CHANGELOG

# Changelog

## [2.3.1] - 2026-07-04

### Fixed
- Corregida la política IAM cuando `enable_logging` es false
- Corregido panic cuando `tags` es un mapa vacío

## [2.3.0] - 2026-06-15

### Added
- Nueva variable `enable_monitoring` (default: false)
- Nuevo output `monitoring_dashboard_url`

### Changed
- Constraint del provider AWS actualizada a >= 5.0, < 6.0

## [2.2.0] - 2026-05-01

### Added
- Soporte de `kms_key_id` en el módulo de bucket S3

### Deprecated
- Variable `instance_type` deprecada, usa `instance_types`

## [2.0.0] - 2026-03-01

### Breaking
- `instance_type` eliminada, reemplazada por `instance_types` (list)
- El output `tags` cambió de `map(string)` a `map(any)`
- Versión mínima de Terraform elevada a 1.5

5. Publicación en el Registro de Módulos

El versionado solo se vuelve real cuando los consumidores pueden descargar lo que taggeaste. Esta sección cubre los tres canales de publicación — elige uno por módulo y sé consistente.

5.1 Terraform Registry (Público)

# Taggea el release en git
git tag v2.3.1
git push origin v2.3.1

# Terraform Registry detecta los tags desde GitHub automáticamente
# URL del módulo: terraform-aws-modules/s3-bucket/aws
# Versión: 2.3.1

5.2 Registro Privado (Terraform Cloud/Enterprise)

# Configura Terraform CLI para el registro privado
terraform login app.terraform.io

# Usa un módulo desde el registro privado
module "my_module" {
  source  = "app.terraform.io/myorg/my-module/aws"
  version = "2.3.1"
}

5.3 Módulos por Fuente Git

# Versionado por tag
module "my_module" {
  source = "git::https://github.com/myorg/terraform-modules.git//my-module?ref=v2.3.1"
}

# Por rama (no recomendado para producción)
module "my_module" {
  source = "git::https://github.com/myorg/terraform-modules.git//my-module?ref=main"
}

6. Pipeline CI/CD

6.1 GitHub Actions — Pipeline de Release del Módulo

name: Terraform Module Release
on:
  push:
    tags: ['v*']

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.0

      - name: Terraform format check
        run: terraform fmt -check -recursive

      - name: Terraform validate
        run: |
          terraform init -backend=false
          terraform validate

      - name: Run Terratest
        run: |
          cd tests/
          go test -v -timeout 30m

      - name: Generate docs
        uses: terraform-docs/terraform-docs@v1.0.0
        with:
          output-file: README.md
          output-method: inject

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          generate_release_notes: true
          body_path: CHANGELOG.md

      - name: Notify teams
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} -d "{
            \"text\": \"Terraform module ${{ github.ref_name }} released\"
          }"

6.2 Validación Pre-Release

name: Terraform Module Validation
on: [pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        terraform_version: ['1.5.0', '1.6.0', '1.7.0']
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: ${{ matrix.terraform_version }}

      - name: Format check
        run: terraform fmt -check -recursive

      - name: Init
        run: terraform init -backend=false

      - name: Validate
        run: terraform validate

      - name: Security scan (tfsec)
        uses: aquasecurity/tfsec-action@v1.0.0

      - name: Cost estimation (infracost)
        uses: infracost/infracost-action@v3
        with:
          path: examples/basic

7. Pin de Versión en Proyectos Consumidores

Las reglas de publicación cubren la mitad del contrato — la otra mitad es cómo los consumidores fijan tu módulo. Estos son los defaults recomendados para que las actualizaciones sean aburridas.

7.1 Estrategias de Pin

Estrategia            | Ejemplo              | Riesgo
──────────────────────┼──────────────────────┼──────────────────────────
Pin exacto            | version = "2.3.1"   | Sin auto-updates, manual
Constraint pesimista  | version = "~> 2.3"  | Auto patch + minor updates
Major pesimista       | version = "~> 2.0"  | Solo auto minor updates
Rango                 | version = ">= 2.0"  | Arriesgado, puede romper
Latest                | (sin version)       | Muy arriesgado, evitar

7.2 Pin Recomendado

# Producción — pin exacto
module "production_db" {
  source  = "terraform-aws-modules/rds/aws"
  version = "6.10.0"
}

# Staging — constraint pesimista (auto patch updates)
module "staging_db" {
  source  = "terraform-aws-modules/rds/aws"
  version = "~> 6.10"
}

# Desarrollo — major pesimista (auto minor + patch)
module "dev_db" {
  source  = "terraform-aws-modules/rds/aws"
  version = "~> 6.0"
}

Ver También

Referencias externas:

Versiones ejecutables de esta política en el repositorio companion.

Preguntas frecuentes

¿Cómo sé cuándo subir la versión major?

Sube major siempre que los consumidores tengan que tocar su propia configuración para adoptar tu cambio. Los disparadores típicos:

  • Una variable u output se elimina o renombra
  • Una variable cambia de tipo (de string a list, por ejemplo)
  • Un default cambia de forma que produce recursos distintos
  • Sube la versión mínima de Terraform o del provider

Ante la duda, sube major — sobre-comunicar una ruptura sale mucho más barato que romper en silencio el terraform apply de alguien. Y suaviza el aterrizaje: marca la interfaz antigua como deprecated en la versión minor actual antes de eliminarla en la siguiente major.

¿Debería usar el Terraform Registry o fuentes git para módulos internos?

Usa el registro privado si tienes Terraform Cloud/Enterprise — te da gestión de versiones, UI navegable e integración con workspaces gratis.

Si no, las fuentes git con refs a tag son el fallback: git::https://github.com/myorg/modules.git//my-module?ref=v2.3.1. Dos reglas para ese camino:

  • Fija siempre un tag (?ref=v2.3.1) — los refs a rama como ?ref=main pueden cambiar bajo tus pies y no son reproducibles.
  • Cuando pasas de ~5 módulos compartidos o ~10 equipos consumidores, el registro se paga solo; la distribución por git no escala a nivel organización.
¿Cómo depreco una funcionalidad de un módulo sin romper a los consumidores actuales?

Abre una ventana de deprecación en lugar de un corte seco:

  1. Marca la variable u output como deprecated y señala el reemplazo en la descripción.
  2. Anúncialo en el README, el CHANGELOG y un mensaje a los equipos consumidores.
  3. Mantén el comportamiento antiguo funcionando junto al nuevo al menos una versión minor.
  4. Fija una fecha de deprecación — 90 días como mínimo — y trackea las migraciones en tu issue tracker.
  5. Publica la eliminación en el siguiente release major, con una guía de migración que muestre el antes y el después.
¿Qué hago si un release de módulo tiene un bug?

Depende de la severidad. Si el bug rompe infraestructura o pierde datos, trátalo como un incidente:

  • No puedes borrar una versión publicada del registro, pero puedes señalarla en el README y notificar a los equipos consumidores de inmediato.
  • Saca un release patch (ej.: 2.3.2) con el fix y actualiza el CHANGELOG.
  • ¿Bug no crítico? Con el patch release y la entrada en el changelog basta.

Una regla vale en ambos casos: nunca hagas force-push de un tag. Los consumidores que ya lo descargaron se quedan con el contenido antiguo mientras las descargas nuevas reciben el nuevo — el mismo número de versión pasa a significar dos cosas distintas, y eso es peor que el bug.

¿Cómo testeo un módulo antes de publicarlo?

Automatízalo en CI — el pipeline de la sección 6 es el setup de referencia:

  • Escribe tests con Terratest o kitchen-terraform que desplieguen el módulo en una cuenta cloud desechable, verifiquen los recursos y lo destruyan todo.
  • Corre los tests en cada PR contra una matriz de versiones de Terraform (y de providers si soportas un rango).
  • Añade terraform fmt -check, terraform validate, tfsec/checkov para seguridad e infracost para visibilidad de coste.
  • Publica solo cuando el PR esté en verde, revisado y mergeado.