Introducción
El control de versiones es tan fundamental para la infraestructura como lo es para el código de aplicaciones. Cuando gestionas infraestructura con Terraform, cada cambio en los archivos .tf puede resultar en la creación, modificación o destrucción de recursos reales en la nube. Esto hace que registrar cada cambio, quién lo hizo y por qué, sea absolutamente crítico.
Git se ha convertido en el estándar de la industria para controlar versiones en proyectos de infraestructura como código (IaC). En este módulo aprenderás las mejores prácticas para usar Git con Terraform de manera profesional y segura.
Por qué Usar Control de Versiones con Terraform
El control de versiones en proyectos de infraestructura aporta beneficios que van más allá de simplemente guardar el historial:
- Trazabilidad: Saber exactamente qué cambió, cuándo y quién lo hizo antes de que falle una base de datos en producción
- Reversibilidad: Poder volver a una versión anterior de la infraestructura si un cambio causa problemas
- Colaboración: Permitir que múltiples ingenieros trabajen en la infraestructura simultáneamente sin pisarse
- Revisión de cambios: Un pull request permite que otro ingeniero revise los cambios antes de aplicarlos en producción
- Auditoría: Cumplimiento con normativas de seguridad que requieren registro de cambios en sistemas críticos
- Documentación implícita: El historial de commits explica la evolución de la infraestructura
- Integración con CI/CD: Automatizar validación y aplicación de cambios al hacer merge a la rama principal
Qué Archivos Incluir en Git y Qué Excluir
Esta es una de las decisiones más importantes al configurar el repositorio:
Tabla: Archivos de Terraform en el Repositorio Git
| Archivo / Directorio | Incluir en Git | Razón |
|---|---|---|
*.tf |
SI | Código fuente de la infraestructura |
*.tfvars (sin secretos) |
SI | Valores de variables no sensibles |
*.tfvars (con secretos) |
NO | Credenciales o datos sensibles nunca en Git |
.terraform.lock.hcl |
SI | Garantiza reproducibilidad del entorno |
.terraform/ |
NO | Directorio de plugins descargados (puede ser grande) |
terraform.tfstate |
NO | Estado puede contener secretos en texto plano |
terraform.tfstate.backup |
NO | Igual que el estado principal |
*.tfplan |
NO | Los planes contienen valores actuales de recursos |
crash.log |
NO | Logs de errores de Terraform |
override.tf |
Depende | Solo si es compartido entre el equipo |
.terraformrc o terraform.rc |
NO | Configuración personal del usuario |
El Archivo .gitignore para Proyectos Terraform
Un .gitignore bien configurado es la primera línea de defensa contra la filtración accidental de secretos o la inclusión de archivos innecesarios. Este es el contenido completo recomendado:
# .gitignore para proyectos Terraform
# Basado en las recomendaciones oficiales de HashiCorp
# ============================================================
# Directorios de Terraform
# ============================================================
# Directorio de trabajo de Terraform (plugins descargados, módulos en caché)
# Este directorio puede pesar varios GB y es regenerado con "terraform init"
.terraform/
# ============================================================
# Archivos de estado de Terraform
# ============================================================
# El archivo de estado contiene TODA la información de la infraestructura,
# incluyendo secretos, contraseñas y claves en texto plano.
# NUNCA debe estar en Git. Usar backend remoto (S3, Terraform Cloud, etc.)
terraform.tfstate
terraform.tfstate.*
# Backups automáticos del estado
*.tfstate.backup
# ============================================================
# Archivos de plan de Terraform
# ============================================================
# Los archivos de plan contienen los valores actuales de los recursos
# y pueden incluir información sensible
*.tfplan
*.plan
# ============================================================
# Archivos de variables con datos sensibles
# ============================================================
# Archivos de variables que pueden contener secretos.
# Si tu proyecto tiene un .tfvars con credenciales, exclúyelo aquí.
# Ejemplo: secret.tfvars, credentials.tfvars, prod.tfvars
*.auto.tfvars
# Si usas este patrón para archivos con secretos, descomenta:
# *secrets*.tfvars
# *credentials*.tfvars
# ============================================================
# Logs y archivos de crash
# ============================================================
# Terraform genera logs de crash cuando encuentra errores internos
crash.log
crash.*.log
# ============================================================
# Archivos de override (sobrescriben configuración)
# ============================================================
# Los archivos de override son específicos del desarrollador local.
# Si se comparten, deben ser incluidos explícitamente.
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# ============================================================
# Configuración de CLI de Terraform
# ============================================================
# Configuración personal de Terraform CLI
.terraformrc
terraform.rc
# ============================================================
# Sistemas operativos y editores
# ============================================================
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Linux
*~
# JetBrains IDEs (IntelliJ, PyCharm, etc.)
.idea/
# Visual Studio Code
.vscode/
*.code-workspace
# Vim
*.swp
*.swo
# Emacs
\#*\#
.\#*
# ============================================================
# NOTA IMPORTANTE sobre .terraform.lock.hcl
# ============================================================
# El archivo .terraform.lock.hcl NO está en este .gitignore porque
# SE RECOMIENDA incluirlo en el repositorio.
# Este archivo garantiza que todos los miembros del equipo usen
# exactamente las mismas versiones de los providers.
# Ver sección "El archivo .terraform.lock.hcl" más adelante.El Archivo .terraform.lock.hcl: ¿Incluir o Excluir?
Esta es una pregunta frecuente que genera confusión. La respuesta recomendada por HashiCorp es incluirlo en Git.
¿Qué contiene este archivo?
# .terraform.lock.hcl
# Generado automáticamente por "terraform init"
# NUNCA modificar manualmente
provider "registry.terraform.io/hashicorp/aws" {
version = "5.31.0" # Versión exacta instalada
constraints = "~> 5.0" # Restricción especificada en versions.tf
# Hashes criptográficos para múltiples plataformas
# Estos hashes garantizan la integridad del provider descargado
hashes = [
"h1:abc123...", # Hash para linux_amd64
"zh:def456...", # Hash para darwin_amd64
"zh:ghi789...", # Hash para windows_amd64
]
}Por qué incluirlo en Git
- Reproducibilidad: Todos los miembros del equipo y el CI/CD usan exactamente la misma versión del provider
- Seguridad: Los hashes criptográficos verifican que el provider descargado no ha sido manipulado
- Estabilidad: Evita que una nueva versión del provider rompa inesperadamente la infraestructura
Cómo actualizar el lockfile
# Actualizar a las últimas versiones dentro de las restricciones
terraform init -upgrade
# Añadir hashes para plataformas adicionales (útil para equipos con distintos OS)
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_amd64 \
-platform=windows_amd64Estrategias de Branching para IaC
GitFlow para Infraestructura
GitFlow es una estrategia de ramas bien definida que funciona bien para equipos medianos o grandes:
main (protegida) ──────────────────────────────────────────────▶
↑ ↑
merge PR merge PR
│ │
release/v1.2 ───────┘ release/v1.3 ─────
│
develop ────────────────────────────────────────────▶
↑ ↑
merge PR merge PR
│ │
feature/add-rds ─┘ feature/update-vpc ─┘Trunk-Based Development (recomendado para IaC)
Para proyectos de infraestructura, trunk-based development suele ser más apropiado porque los cambios de infraestructura deben ser pequeños y frecuentes:
main (protegida) ──────────────────────────────────────────────▶
↑ ↑ ↑ ↑
merge PR merge PR merge PR merge PR
│ │ │ │
feature/A feature/B fix/state-C feature/D
(vida corta: horas o días, nunca semanas)Ventajas del trunk-based para IaC:
- Los cambios de infraestructura grandes son peligrosos; mejor hacerlos incrementales
- Evita divergencias largas entre ramas que son difíciles de integrar
- Facilita el despliegue continuo (CI/CD)
Flujo de Trabajo: Feature Branch → PR → Review → Merge → Apply
Este es el flujo estándar para gestionar cambios de infraestructura:
# PASO 1: Crear una rama para el nuevo cambio
# El nombre debe ser descriptivo del cambio
git checkout -b feature/add-rds-database
# PASO 2: Realizar los cambios en los archivos .tf
# (editar database.tf, variables.tf, outputs.tf, etc.)
# PASO 3: Validar el código antes de commitear
terraform fmt -check # Verificar formato
terraform validate # Verificar sintaxis
terraform plan # Ver qué cambios se aplicarán
# PASO 4: Commitear los cambios con un mensaje descriptivo
git add versions.tf variables.tf database.tf outputs.tf
git commit -m "feat(database): add RDS PostgreSQL instance for user data
- Add aws_db_instance resource with multi-AZ support
- Add security group for database access
- Expose endpoint and port as outputs
- Variables: db_name, db_username, db_instance_class
Resolves: INFRA-123"
# PASO 5: Subir la rama al repositorio remoto
git push origin feature/add-rds-database
# PASO 6: Crear un Pull Request en GitHub/GitLab
# El PR debe incluir:
# - Descripción del cambio
# - Output de "terraform plan"
# - Checklist de revisión
# (Ver plantilla de PR más adelante)
# PASO 7: Revisión por un compañero (code review)
# El revisor verifica que el plan es correcto y seguro
# PASO 8: Merge a main (o develop)
# Solo después de aprobación
# PASO 9: CI/CD ejecuta "terraform apply" automáticamente
# o el operador lo ejecuta manualmente en el entorno correspondienteConvenciones de Commits para Cambios de Infraestructura
Los mensajes de commit son parte de la documentación. Para IaC, se recomienda seguir el estándar Conventional Commits con prefijos específicos:
# Formato: # <tipo>(<ámbito>): <descripción corta> # # [cuerpo opcional con más detalles] # # [referencias a issues] # TIPOS para infraestructura: # feat - Nueva funcionalidad de infraestructura # fix - Corrección de un problema existente # refactor - Cambio que no añade funcionalidad ni corrige un bug # security - Cambio relacionado con seguridad # cost - Optimización de costos # docs - Documentación # ci - Cambios en CI/CD # EJEMPLOS: # Añadir nuevo recurso feat(networking): add VPC peering between prod and analytics accounts # Corregir un error de configuración fix(security-group): remove overly permissive 0.0.0.0/0 ingress rule - Change port 22 ingress to use specific CIDR range - Resolves security audit finding SEC-456 # Refactorización sin cambio de infraestructura refactor(modules): extract common tags to locals.tf # Cambio de seguridad security(iam): restrict S3 bucket policy to specific roles only # Optimización de costos cost(ec2): downsize dev instances from t3.medium to t3.small # No estimated impact on production environment
Protección de Secretos en Repositorios
La regla número uno: nunca committear credenciales, claves de API, contraseñas o tokens en un repositorio de Git.
Métodos para gestionar secretos
# MÉTODO 1: Variables de entorno (el más simple)
# Las credenciales se configuran como variables de entorno del sistema
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="wJalr..."
export TF_VAR_db_password="mi-contraseña-segura" # Prefijo TF_VAR_ para variables de Terraform
# MÉTODO 2: Archivo .tfvars excluido de Git
# Crear secrets.tfvars con las credenciales y añadirlo al .gitignore
echo "secrets.tfvars" >> .gitignore
# Uso:
terraform apply -var-file="secrets.tfvars"# MÉTODO 3: Obtener secretos de AWS Secrets Manager o HashiCorp Vault
# Este es el método más seguro para producción
# data.tf - Obtener secreto de AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_credentials" {
secret_id = "prod/database/credentials" # Nombre del secreto en AWS
}
locals {
# jsondecode() convierte el JSON del secreto en un mapa de Terraform
db_credentials = jsondecode(
data.aws_secretsmanager_secret_version.db_credentials.secret_string
)
}
resource "aws_db_instance" "main" {
# Usar el secreto obtenido de AWS Secrets Manager (nunca hardcodeado)
username = local.db_credentials["username"]
password = local.db_credentials["password"]
}# MÉTODO 4: Marcar variables como sensibles
# Esto impide que el valor aparezca en los logs de terraform plan/apply
variable "db_password" {
description = "Contraseña de la base de datos"
type = string
sensitive = true # El valor NO aparecerá en los outputs ni en los planes
}Herramientas para Detección de Secretos
Añadir estas herramientas como hooks de Git o pasos de CI previene la filtración accidental de secretos:
git-secrets (de AWS Labs)
# Instalación
brew install git-secrets # macOS
# o clonar desde https://github.com/awslabs/git-secrets y ejecutar make install
# Configurar el repositorio para escanear credenciales de AWS
git secrets --install # Instala los hooks en el repositorio actual
git secrets --register-aws # Añade patrones para detectar credenciales de AWS
# Escanear el historial existente en busca de secretos filtrados
git secrets --scan-history
# A partir de ahora, git commit fallará si detecta credenciales de AWSdetect-secrets (de Yelp)
# Instalación
pip install detect-secrets
# Escanear el proyecto y crear un archivo de baseline
detect-secrets scan > .secrets.baseline
# Incluir el baseline en Git (documenta los falsos positivos conocidos)
git add .secrets.baseline
git commit -m "ci: add detect-secrets baseline"
# Ejecutar como pre-commit hook (ver sección de hooks)
detect-secrets-hook --baseline .secrets.baseline
# Actualizar el baseline cuando añades nuevos "falsos positivos" legítimos
detect-secrets scan --update .secrets.baselinepre-commit hooks
# .pre-commit-config.yaml
# Framework para gestionar múltiples hooks de pre-commit
repos:
# Hook oficial de Terraform (fmt, validate, etc.)
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.83.5
hooks:
- id: terraform_fmt # Formatea el código
- id: terraform_validate # Valida la sintaxis
- id: terraform_tflint # Linting avanzado
# Detección de secretos
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']# Instalar pre-commit
pip install pre-commit
# Instalar los hooks en el repositorio
pre-commit install
# Ahora cada "git commit" ejecutará todos los hooks automáticamenteEtiquetado de Versiones para Configuraciones de Infraestructura
El etiquetado (tagging) permite marcar versiones específicas de la infraestructura, especialmente útil para módulos compartidos:
# Crear un tag de versión semántica después de un cambio importante
git tag -a v1.2.0 -m "Release 1.2.0: Add RDS module and S3 lifecycle policies"
# Subir el tag al repositorio remoto
git push origin v1.2.0
# Ver todos los tags
git tag -l
# En módulos de Terraform, referenciar una versión específica por tag:
# modules/networking/main.tf
module "networking" {
source = "git::https://github.com/empresa/terraform-modules.git//networking?ref=v1.2.0"
# ?ref=v1.2.0 ancla el módulo a esa versión exacta del tag de Git
}Ejemplo de Flujo de Trabajo Completo con Git y Terraform
# =========================================================
# ESCENARIO: Añadir un bucket S3 para almacenar logs
# =========================================================
# 1. Asegurarse de estar en la rama main y actualizada
git checkout main
git pull origin main
# 2. Crear rama para el nuevo cambio
git checkout -b feature/add-s3-logs-bucket
# 3. Añadir el nuevo código
# (crear o editar storage.tf)
# 4. Inicializar Terraform si es necesario (primera vez o nuevo provider)
terraform init
# 5. Formatear el código
terraform fmt
# 6. Validar la sintaxis
terraform validate
# Output esperado: Success! The configuration is valid.
# 7. Ver el plan de cambios
terraform plan -out=s3-logs.tfplan
# Guardar el plan garantiza que "apply" aplique exactamente lo planificado
# 8. Revisar el output del plan cuidadosamente:
# Plan: 2 to add, 0 to change, 0 to destroy.
# 9. Commitear los cambios
git add storage.tf outputs.tf variables.tf
git commit -m "feat(storage): add S3 bucket for application logs
- Add aws_s3_bucket with versioning and lifecycle policies
- Add bucket policy to restrict access to application role only
- Enable server-side encryption with AES256
- Add 90-day transition to Glacier and 365-day expiration
Ref: INFRA-789"
# 10. Push al repositorio remoto
git push origin feature/add-s3-logs-bucket
# 11. Crear Pull Request en GitHub
# Incluir en la descripción del PR:
# - Cambios propuestos
# - Output de "terraform plan"
# - Impacto esperado (recursos nuevos, costos adicionales)
# 12. Esperar aprobación del revisor
# 13. Merge a main
# 14. CI/CD aplica automáticamente (o manual en prod):
terraform apply s3-logs.tfplan
# 15. Limpiar el archivo del plan (no va en Git)
rm s3-logs.tfplan
# 16. Limpiar la rama local (ya fue mergeada)
git branch -d feature/add-s3-logs-bucketEjercicio: Configurar un Repositorio Git para un Proyecto Terraform
Descripción
Tienes un proyecto Terraform nuevo para desplegar una aplicación web en AWS. El proyecto no tiene configuración de Git. Tu tarea es:
- Inicializar el repositorio Git
- Crear el
.gitignoreapropiado - Configurar el repositorio con la estructura de ramas correcta
- Proteger secretos de AWS
- Hacer el primer commit con la estructura inicial
Solución
# Paso 1: Inicializar el repositorio
git init mi-proyecto-terraform
cd mi-proyecto-terraform
# Paso 2: Crear la estructura de directorios
mkdir -p modules/networking modules/compute environments/dev environments/prod
# Paso 3: Crear el .gitignore completo
cat > .gitignore << 'EOF'
# Terraform
.terraform/
terraform.tfstate
terraform.tfstate.*
*.tfstate.backup
*.tfplan
*.plan
*.auto.tfvars
crash.log
crash.*.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc
# Secretos - NUNCA en Git
secrets.tfvars
*secrets*.tfvars
*credentials*.tfvars
*.pem
*.key
*_rsa
# OS / Editores
.DS_Store
.idea/
.vscode/
*.swp
EOF
# Paso 4: Crear estructura de ramas
# Primer commit con la estructura inicial
touch README.md
git add .gitignore README.md
git commit -m "chore: initial project structure with .gitignore"
# Paso 5: Configurar rama main como rama por defecto
git branch -M main
# Paso 6: Conectar con repositorio remoto
git remote add origin https://github.com/empresa/mi-proyecto-terraform.git
git push -u origin main
# Paso 7: Crear rama develop para trabajo diario
git checkout -b develop
git push -u origin develop
# Paso 8: En GitHub, proteger la rama main:
# Settings > Branches > Add rule:
# - Require pull request reviews (mínimo 1 aprobación)
# - Require status checks to pass (terraform fmt, validate)
# - Restrict pushes (solo CI/CD puede hacer push directo)
# Paso 9: Configurar credenciales de AWS como variables de entorno
# (no como archivos en el proyecto)
export AWS_ACCESS_KEY_ID="tu-access-key"
export AWS_SECRET_ACCESS_KEY="tu-secret-key"
export AWS_DEFAULT_REGION="us-east-1"
echo "Repositorio configurado correctamente."
echo "Nunca uses 'git add .' sin revisar qué archivos estás añadiendo."Errores Comunes con Control de Versiones
-
Committear el directorio
.terraform/: Este directorio puede pesar cientos de MB. Siempre en.gitignore. -
Committear
terraform.tfstate: El estado puede contener contraseñas de bases de datos en texto plano. Usar siempre backend remoto con cifrado. -
No usar ramas: Hacer cambios directamente en
mainsin revisión es peligroso en infraestructura de producción. -
Commits demasiado grandes: Un commit que cambia 20 recursos distintos es imposible de revisar. Commits pequeños y frecuentes.
-
No incluir el output de
terraform planen el PR: Sin el plan, el revisor no puede saber qué se va a crear o destruir. -
Ignorar el
.terraform.lock.hcl: No incluirlo en Git puede llevar a que el CI use una versión diferente del provider que la del desarrollador. -
Usar
git add .sin pensar: Revisar siempre qué archivos se están añadiendo congit statusantes de commitear.
Resumen
En este módulo has aprendido:
- Por qué el control de versiones es indispensable para proyectos de Terraform
- Qué archivos incluir y excluir del repositorio (con énfasis en nunca commitear el estado)
- Cómo crear un
.gitignorecompleto y seguro para proyectos Terraform - El rol del
.terraform.lock.hcly por qué debe estar en Git - Estrategias de branching: GitFlow vs trunk-based development
- El flujo de trabajo estándar de feature branch con revisión de PR
- Convenciones de commits usando Conventional Commits
- Técnicas y herramientas para proteger secretos
- Cómo etiquetar versiones de infraestructura
En el siguiente módulo aprenderás a probar el código de Terraform usando herramientas como terraform validate, tflint, checkov y el framework nativo de testing terraform test, lo que te permitirá detectar errores antes de aplicar cambios en producción.
Curso de Terraform
Módulo 1: Introducción a Terraform
- ¿Qué es Terraform?
- Instalando Terraform
- Conceptos Básicos de Terraform
- Primera Configuración de Terraform
Módulo 2: Lenguaje de Configuración de Terraform
Módulo 3: Gestión del Estado
Módulo 4: Módulos de Terraform
Módulo 5: Aprovisionamiento de Recursos
- Conceptos Básicos de Aprovisionamiento
- Aprovisionamiento de Recursos AWS
- Aprovisionamiento de Recursos Azure
- Aprovisionamiento de Recursos GCP
Módulo 6: Funcionalidades Avanzadas de Terraform
Módulo 7: Mejores Prácticas de Terraform
- Organización del Código
- Control de Versiones
- Pruebas del Código de Terraform
- Mejores Prácticas de Seguridad
Módulo 8: Terraform en CI/CD
- Integración de Terraform con CI/CD
- Automatización de Terraform con Jenkins
- Uso de Terraform con GitHub Actions
- Terraform Cloud y Enterprise
