Introducción

En la lección anterior aprendiste qué es el estado y por qué Terraform lo necesita. En esta lección profundizaremos en los archivos que componen el sistema de estado de Terraform: su estructura interna, cómo manipularlos de forma segura y las operaciones avanzadas que puedes realizar sobre ellos. Entender esto te permitirá gestionar tu infraestructura con mayor confianza y resolver problemas complejos.


Estructura Detallada del Archivo terraform.tfstate

El archivo terraform.tfstate es un documento JSON con una estructura bien definida. Analicemos cada parte con un ejemplo completo y realista:

{
  "version": 4,
  "terraform_version": "1.5.7",
  "serial": 12,
  "lineage": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "outputs": {
    "id_instancia": {
      "value": "i-0a1b2c3d4e5f67890",
      "type": "string",
      "sensitive": false
    },
    "clave_secreta": {
      "value": "s3cr3t0",
      "type": "string",
      "sensitive": true
    }
  },
  "resources": [
    {
      "mode": "managed",
      "type": "aws_vpc",
      "name": "principal",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "arn": "arn:aws:ec2:us-east-1:123456789012:vpc/vpc-0abc12345",
            "cidr_block": "10.0.0.0/16",
            "enable_dns_hostnames": true,
            "enable_dns_support": true,
            "id": "vpc-0abc12345",
            "instance_tenancy": "default",
            "tags": {
              "Name": "VPC-Principal",
              "Entorno": "produccion"
            }
          },
          "sensitive_attributes": [],
          "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ=="
        }
      ]
    },
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "servidor_web",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "ami": "ami-0c55b159cbfafe1f0",
            "availability_zone": "us-east-1a",
            "id": "i-0a1b2c3d4e5f67890",
            "instance_type": "t2.micro",
            "private_ip": "10.0.1.25",
            "public_ip": "54.123.45.67",
            "subnet_id": "subnet-0abc12345",
            "vpc_security_group_ids": ["sg-0abc12345"],
            "tags": {
              "Name": "ServidorWeb"
            }
          },
          "sensitive_attributes": [],
          "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==",
          "dependencies": [
            "aws_vpc.principal"
          ]
        }
      ]
    },
    {
      "mode": "data",
      "type": "aws_ami",
      "name": "ubuntu",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "id": "ami-0c55b159cbfafe1f0",
            "name": "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"
          }
        }
      ]
    }
  ]
}

Análisis campo por campo

Campos raíz:

Campo Tipo Descripción
version número Versión del esquema del archivo de estado. Actualmente es 4. Si Terraform actualiza su formato interno, este número aumenta
terraform_version string Versión exacta de Terraform que generó el estado. Útil para diagnóstico
serial número Contador que se incrementa con cada escritura al estado. Ayuda a detectar conflictos en sistemas distribuidos
lineage UUID Identificador único generado la primera vez que se crea el estado. Permanece constante para toda la vida del estado. Impide mezclar estados de proyectos diferentes
outputs objeto Mapa de los valores de salida definidos con el bloque output
resources array Lista de todos los recursos (gestionados y de datos) que Terraform conoce

Dentro de cada recurso:

Campo Descripción
mode managed para recursos creados con resource {}, data para fuentes de datos
type Tipo de recurso, como aws_instance o aws_vpc
name Nombre lógico del recurso en tu código HCL
provider Identificador completo del proveedor, incluyendo el namespace del Registry
instances Array con una entrada por instancia (una si no usas count/for_each)
attributes Todos los atributos del recurso tras ser creado/actualizado
sensitive_attributes Lista de atributos marcados como sensibles (sus valores se ocultan en la salida pero SÍ se guardan en el estado)
private Datos internos en Base64 que el proveedor usa para su funcionamiento interno
dependencies Lista de otros recursos de los que depende este recurso

Importante sobre sensitive_attributes: Los valores sensibles (como contraseñas o tokens) sí se guardan en texto plano en el archivo de estado. Por eso es fundamental proteger el acceso al archivo de estado, especialmente cuando se usa un backend remoto.


El Archivo terraform.tfstate.backup

Cada vez que Terraform modifica el estado, guarda automáticamente una copia del estado anterior con el nombre terraform.tfstate.backup.

mi-proyecto/
├── main.tf
├── terraform.tfstate         ← Estado actual
└── terraform.tfstate.backup  ← Estado anterior (copia de seguridad automática)

¿Cuándo es útil el backup?

Si una operación de terraform apply falla a mitad de camino y el estado queda en un estado inconsistente, puedes restaurar el backup manualmente:

# PRECAUCIÓN: Esto sobreescribe el estado actual
cp terraform.tfstate.backup terraform.tfstate

Sin embargo, esto solo funciona con estado local. Con backends remotos, los sistemas de versionado del backend (como S3 con versioning activado) proporcionan una funcionalidad equivalente y más robusta.

Limitaciones del backup:

  • Solo guarda el estado inmediatamente anterior. Si haces dos apply consecutivos, el backup del primer apply se sobreescribe.
  • No sustituye a una estrategia de respaldo real (veremos esto más adelante en esta lección).

El Archivo .terraform.lock.hcl

Cuando ejecutas terraform init, Terraform crea (o actualiza) un archivo llamado .terraform.lock.hcl. Este archivo es diferente del estado: no registra recursos, sino las versiones exactas de los proveedores que se usaron.

# .terraform.lock.hcl
# Este archivo fue generado automáticamente por "terraform init".
# Está destinado a ser incluido en el control de versiones.

provider "registry.terraform.io/hashicorp/aws" {
  version     = "5.23.0"
  constraints = "~> 5.0"
  hashes = [
    "h1:AbCdEfGhIjKlMnOpQrStUvWxYz0123456789abcdef=",
    "zh:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  ]
}

provider "registry.terraform.io/hashicorp/local" {
  version     = "2.4.0"
  constraints = "~> 2.0"
  hashes = [
    "h1:XyZ123abcDEF456ghiJKL789mnoPQR012stuVWX345=",
  ]
}

¿Para qué sirve el lock file?

  • Reproducibilidad: Garantiza que todos los miembros del equipo usen exactamente la misma versión del proveedor, evitando el problema de "funciona en mi máquina".
  • Seguridad: Los hashes criptográficos verifican que los binarios descargados no han sido alterados.
  • Actualizaciones controladas: Para actualizar un proveedor, debes ejecutar terraform init -upgrade explícitamente.

Gestión del lock file

# Actualizar todas las versiones de proveedores (respetando las constraints)
terraform init -upgrade

# Añadir hashes para múltiples plataformas (útil en CI/CD)
terraform providers lock -platform=linux_amd64 -platform=darwin_amd64

El archivo .terraform.lock.hcl DEBE incluirse en el control de versiones (git). Es una buena práctica incluirlo para garantizar consistencia entre entornos.


Manipulación del Estado: terraform state mv

El comando terraform state mv mueve recursos dentro del estado, o entre estados. Es uno de los comandos más potentes y también más peligrosos si se usa incorrectamente.

Sintaxis

terraform state mv [opciones] ORIGEN DESTINO

Casos de uso comunes

Caso 1: Renombrar un recurso en el código

Imagina que tienes este recurso y quieres renombrarlo:

# Antes (en tu código HCL)
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
}

Si simplemente cambias el nombre en el código a servidor_web y ejecutas terraform plan, Terraform pensará que debes destruir web y crear servidor_web (una instancia completamente nueva). Para evitar esto:

# Primero, mueve el recurso en el estado
terraform state mv aws_instance.web aws_instance.servidor_web

# Luego, actualiza tu código HCL
# resource "aws_instance" "web" { ... }  ← elimina esto
# resource "aws_instance" "servidor_web" { ... }  ← añade esto

# Ahora terraform plan no mostrará cambios destructivos
terraform plan

Caso 2: Mover un recurso a un módulo

Cuando refactorizas código y mueves recursos a módulos:

# Mover un recurso al módulo "red"
terraform state mv aws_vpc.principal module.red.aws_vpc.principal

Caso 3: Mover un recurso entre workspaces o proyectos

# Extraer el estado de un recurso a un archivo temporal
terraform state mv -state-out=../otro-proyecto/terraform.tfstate \
  aws_instance.servidor_web \
  aws_instance.servidor_web

Precauciones con terraform state mv

  • Siempre haz un backup antes de usar state mv:
    terraform state pull > backup_estado_$(date +%Y%m%d_%H%M%S).tfstate
    
  • Verifica el resultado con terraform state list y terraform plan después de la operación.
  • En entornos con estado remoto, state mv opera directamente sobre el backend; los cambios son inmediatos.

Manipulación del Estado: terraform state rm

El comando terraform state rm elimina un recurso del estado de Terraform sin destruir el recurso real en la nube. Esto le dice a Terraform: "deja de gestionar este recurso".

Sintaxis

terraform state rm [opciones] DIRECCIÓN

Casos de uso

Caso 1: Dejar de gestionar un recurso

Tienes una base de datos que quieres excluir de la gestión de Terraform (quizás porque la vas a gestionar manualmente o con otra herramienta):

# Elimina del estado sin destruir el recurso en AWS
terraform state rm aws_db_instance.base_datos_legado

Después de esto:

  • El recurso sigue existiendo en AWS.
  • Si ejecutas terraform plan, Terraform ya no lo mencionará.
  • Si tienes el bloque resource en el código, Terraform intentará crear un nuevo recurso (ten cuidado: también elimina el bloque resource del código si no quieres que se cree uno nuevo).

Caso 2: Eliminar múltiples recursos a la vez

# Eliminar todos los recursos de un módulo del estado
terraform state rm 'module.red'

# Eliminar un recurso con for_each (la dirección incluye la clave)
terraform state rm 'aws_instance.servidores["web-01"]'

Caso 3: Corregir un estado corrupto

Si un recurso en el estado apunta a un recurso que ya no existe en la nube y los intentos de refresh fallan:

terraform state rm aws_instance.instancia_eliminada

Importar Infraestructura Existente con terraform import

Frecuentemente te encontrarás con infraestructura que fue creada manualmente (antes de usar Terraform) y necesitas incorporarla a la gestión de Terraform. Para esto existe terraform import.

¿Qué hace terraform import?

terraform import hace lo opuesto a terraform destroy: en lugar de eliminar un recurso real y borrarlo del estado, toma un recurso real existente y lo añade al estado sin modificarlo.

Flujo de trabajo para importar infraestructura

Paso 1: Escribe el bloque resource en tu código HCL

Terraform necesita saber en qué bloque "encajar" el recurso importado:

# main.tf
resource "aws_instance" "servidor_existente" {
  # Terraform rellenará los atributos desde la nube
  # pero necesita que el bloque exista
  ami           = "ami-0c55b159cbfafe1f0"  # Deberás descubrirlo
  instance_type = "t2.micro"               # Deberás descubrirlo
}

Paso 2: Ejecuta terraform import

# Sintaxis: terraform import DIRECCIÓN_RECURSO ID_REAL
terraform import aws_instance.servidor_existente i-0a1b2c3d4e5f67890

Salida esperada:

aws_instance.servidor_existente: Importing from ID "i-0a1b2c3d4e5f67890"...
aws_instance.servidor_existente: Import prepared!
  Prepared aws_instance for import
aws_instance.servidor_existente: Refreshing state... [id=i-0a1b2c3d4e5f67890]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

Paso 3: Ejecuta terraform plan para verificar

terraform plan

Lo ideal es que el plan no muestre ningún cambio. Si muestra cambios, significa que tu bloque resource no refleja exactamente el estado actual del recurso. Deberás ajustar el código HCL hasta que plan diga "No changes".

Paso 4: Verificar el recurso importado en el estado

terraform state show aws_instance.servidor_existente

Importación en bloque con for_each (Terraform >= 1.5)

A partir de Terraform 1.5, existe un bloque nativo import que permite importar múltiples recursos de forma declarativa:

# imports.tf
import {
  to = aws_instance.servidor_existente
  id = "i-0a1b2c3d4e5f67890"
}

import {
  to = aws_security_group.sg_web
  id = "sg-0abc12345"
}
# Terraform generará automáticamente el código HCL
terraform plan -generate-config-out=recursos_importados.tf

Esto genera un archivo recursos_importados.tf con el código HCL completo basado en los atributos reales de los recursos. Es mucho más eficiente que escribir el código manualmente.


Estrategias de Respaldo del Estado

El estado de Terraform es crítico. Perderlo significa perder el control sobre tu infraestructura. Aquí hay estrategias para protegerlo:

Estrategia 1: Versionado en S3

Cuando usas S3 como backend, activa el versionado del bucket:

# El bucket S3 con versionado activado
resource "aws_s3_bucket" "terraform_state" {
  bucket = "mi-empresa-terraform-state"
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  versioning_configuration {
    status = "Enabled"
  }
}

# También activa el cifrado en reposo
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
  }
}

Estrategia 2: Respaldo manual antes de operaciones críticas

# Antes de cualquier operación arriesgada, exporta el estado actual
terraform state pull > backups/estado_$(date +%Y%m%d_%H%M%S).tfstate

# Para restaurar si algo sale mal
terraform state push backups/estado_20240115_103000.tfstate

Estrategia 3: Terraform Cloud

Terraform Cloud mantiene un historial completo de todos los estados y permite hacer rollback a cualquier versión anterior desde la interfaz web.


Qué NO Hacer con el Estado

Esta sección es tan importante como todas las anteriores. Cometer estos errores puede resultar en la pérdida del control sobre tu infraestructura:

❌ NO edites el archivo terraform.tfstate manualmente

# NUNCA hagas esto
nano terraform.tfstate
vim terraform.tfstate

El archivo JSON tiene una estructura muy específica y cualquier error de formato o valor incorrecto puede corromperlo completamente. Usa siempre los comandos oficiales de Terraform.

❌ NO elimines el archivo terraform.tfstate

# NUNCA hagas esto sin entender las consecuencias
rm terraform.tfstate

Si eliminas el estado, Terraform "olvidará" todos los recursos que gestionaba. La próxima vez que ejecutes apply, intentará crearlos de nuevo, lo que puede resultar en duplicados o errores si los recursos ya existen.

❌ NO guardes el estado en el repositorio de git

# .gitignore
# Añade siempre estas líneas
*.tfstate
*.tfstate.backup
.terraform/

El estado puede contener valores sensibles (contraseñas, tokens, claves privadas) en texto plano. Nunca lo incluyas en git. Usa siempre un backend remoto con cifrado para entornos de equipo.

❌ NO compartas el mismo estado entre proyectos no relacionados

Cada proyecto de Terraform debe tener su propio archivo de estado. Mezclar recursos de proyectos diferentes en el mismo estado crea dependencias innecesarias y hace que las operaciones de plan y apply sean más lentas y peligrosas.


Ejercicio Práctico: Reorganizar Recursos en el Estado

Objetivo

Practicar el uso de terraform state mv para simular una refactorización donde movemos un recurso a un módulo.

Configuración inicial

mkdir -p ejercicio-state-mv/modulos/servidor
cd ejercicio-state-mv
# main.tf (configuración inicial sin módulos)
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
  }
}

resource "local_file" "config_app" {
  content  = "app_name=MiAplicacion\nversion=1.0"
  filename = "${path.module}/config_app.txt"
}

resource "local_file" "config_bd" {
  content  = "host=localhost\nport=5432\ndb=miapp"
  filename = "${path.module}/config_bd.txt"
}

Paso 1: Aplica la configuración inicial

terraform init
terraform apply -auto-approve
terraform state list

Salida esperada:

local_file.config_app
local_file.config_bd

Paso 2: Crear la estructura de módulo

Crea la carpeta y archivo del módulo:

# modulos/servidor/main.tf
variable "nombre_app" {
  type = string
}

resource "local_file" "config_app" {
  content  = "app_name=${var.nombre_app}\nversion=1.0"
  filename = "${path.module}/../../config_app.txt"
}

Paso 3: Actualiza main.tf para usar el módulo

# main.tf actualizado
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
  }
}

module "servidor" {
  source     = "./modulos/servidor"
  nombre_app = "MiAplicacion"
}

resource "local_file" "config_bd" {
  content  = "host=localhost\nport=5432\ndb=miapp"
  filename = "${path.module}/config_bd.txt"
}

Paso 4: Sin mover el estado, observa el plan destructivo

terraform init  # necesario para inicializar el módulo
terraform plan

Terraform querrá DESTRUIR local_file.config_app y CREAR module.servidor.local_file.config_app. Esto es lo que queremos evitar.

Paso 5: Mueve el recurso en el estado

# Haz un backup primero
terraform state pull > backup_antes_mv.tfstate

# Mueve el recurso
terraform state mv local_file.config_app module.servidor.local_file.config_app

Salida esperada:

Move "local_file.config_app" to "module.servidor.local_file.config_app"
Successfully moved 1 object(s).

Paso 6: Verifica que el plan ya no destruye nada

terraform plan

Salida esperada:

No changes. Your infrastructure matches the configuration.

Paso 7: Verifica el nuevo estado

terraform state list

Salida esperada:

local_file.config_bd
module.servidor.local_file.config_app

La refactorización se completó sin destruir ningún recurso real.


Errores Comunes al Manipular el Estado

Error 1: Dirección de recurso incorrecta en state mv

# Error: la dirección no existe en el estado
terraform state mv aws_instance.web_servers aws_instance.servidores_web
Error: Invalid target address

Cannot move to aws_instance.servidores_web: there is no resource with that
type and name at the given path.

Solución: Usa terraform state list para obtener las direcciones exactas antes de ejecutar state mv.

Error 2: Intentar importar sin el bloque resource

terraform import aws_instance.nuevo i-0a1b2c3d4e5f67890
Error: resource address "aws_instance.nuevo" does not exist in the configuration.

Before importing this resource, please create its configuration in the root module.

Solución: Siempre escribe el bloque resource en tu código HCL antes de ejecutar terraform import.

Error 3: El lock file genera conflictos entre plataformas

Error: Failed to install provider

The local package for registry.terraform.io/hashicorp/aws 5.23.0 doesn't match
any of the checksums previously recorded in the dependency lock file.

Solución: Ejecuta terraform providers lock con las plataformas necesarias:

terraform providers lock \
  -platform=linux_amd64 \
  -platform=darwin_amd64 \
  -platform=windows_amd64

Error 4: state rm accidental

Si eliminaste un recurso del estado por error y el recurso aún existe en la nube:

# Recupera el recurso importándolo de nuevo
terraform import aws_instance.servidor_eliminado_por_error i-0a1b2c3d4e5f67890

Resumen

En esta lección has profundizado en los archivos que componen el sistema de estado de Terraform:

  • El archivo terraform.tfstate tiene una estructura JSON con campos críticos: version, serial, lineage, outputs y resources. Cada recurso registra su mode, type, name, attributes y dependencies.
  • El archivo terraform.tfstate.backup guarda el estado inmediatamente anterior y puede usarse para recuperaciones de emergencia.
  • El archivo .terraform.lock.hcl registra las versiones exactas de los proveedores y debe incluirse en git para garantizar reproducibilidad.
  • terraform state mv permite mover y renombrar recursos en el estado, fundamental para refactorizaciones sin tiempo de inactividad.
  • terraform state rm elimina recursos del estado sin destruirlos en la nube.
  • terraform import incorpora infraestructura existente a la gestión de Terraform.
  • Nunca debes editar el estado manualmente ni incluirlo en git.

En la siguiente lección (03-03-remote-state.md) aprenderás a configurar backends remotos (S3, Azure Blob, Terraform Cloud) para que tu estado sea accesible por todo el equipo, esté protegido y soporte bloqueos para evitar conflictos de escritura concurrente.

© Copyright 2026. Todos los derechos reservados