--- title: "Terraform import y gestión del estado en Ubuntu 24.04" description: "Importa recursos existentes en Terraform con bloques import, renombra con moved, deja de gestionarlos con removed, migra el backend y detecta el drift." category: "Automatización Avanzada" language: es source: https://cubepath.com/es/docs/automatización-avanzada/terraform-import-y-gestion-de-estado updated: 2026-09-25 --- # Terraform import y gestión del estado en Ubuntu 24.04 Terraform guarda en su archivo de estado la relación entre cada recurso de tu código y el objeto real que gestiona. Cuando esa relación falla (un recurso creado a mano, un renombrado en el código, un cambio hecho fuera de Terraform), tienes que intervenir en el estado. En este tutorial practicarás las operaciones más habituales en Ubuntu 24.04: importar un recurso existente, renombrarlo sin destruirlo, dejar de gestionarlo, migrar el estado a otro backend y detectar el drift. Para que todo sea reproducible en un solo servidor, el ejemplo usa el proveedor de Docker y una red Docker creada a mano. Las mismas órdenes y bloques funcionan con AWS, Azure, Google Cloud o cualquier otro proveedor que soporte importación. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios `sudo`. - Terraform 1.7 o posterior, instalado desde el repositorio oficial de HashiCorp (los bloques `removed` necesitan la 1.7). - Docker Engine instalado y tu usuario en el grupo `docker`, de forma que `docker ps` funcione sin `sudo`. - Conocimientos básicos de HCL y del ciclo `init`, `plan`, `apply`. Comprueba ambas herramientas antes de empezar: ```bash terraform version docker network ls ``` ## Paso 1: Crear un recurso fuera de Terraform Simula la situación típica: alguien creó un recurso a mano y ahora quieres gestionarlo con Terraform. Crea una red Docker: ```bash docker network create red-app ``` Anota su ID, que es el identificador que usará la importación: ```bash docker network inspect -f '{{.Id}}' red-app ``` ```output 3f9c2d7e5a41b8c0e6d1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7 ``` ## Paso 2: Preparar el proyecto de Terraform Crea el directorio del proyecto: ```bash mkdir -p ~/tf-estado && cd ~/tf-estado ``` Define el proveedor en `providers.tf`: ```bash nano providers.tf ``` ```hcl terraform { required_version = ">= 1.7.0" required_providers { docker = { source = "kreuzwerker/docker" version = "~> 3.0" } } } provider "docker" {} ``` Inicializa el proyecto para descargar el proveedor: ```bash terraform init ``` ```output Terraform has been successfully initialized! ``` ## Paso 3: Importar el recurso con un bloque import Desde Terraform 1.5, la forma recomendada de importar es declarar un bloque `import` en el código. A diferencia de la orden `terraform import`, se revisa en el `plan` antes de tocar el estado y puede quedar en el repositorio como registro. Crea `red.tf` con el recurso y el bloque de importación. Sustituye el ID por el que obtuviste en el paso 1: ```bash nano red.tf ``` ```hcl import { to = docker_network.app id = "3f9c2d7e5a41b8c0e6d1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7" } resource "docker_network" "app" { name = "red-app" } ``` Revisa el plan: ```bash terraform plan ``` ```output docker_network.app: Preparing import... [id=3f9c2d7e5a41...] docker_network.app: Refreshing state... [id=3f9c2d7e5a41...] Plan: 1 to import, 0 to add, 0 to change, 0 to destroy. ``` La línea clave es `1 to import, 0 to add, 0 to change, 0 to destroy`. Si el plan muestra cambios o un reemplazo (`must be replaced`), tu bloque `resource` no coincide con el recurso real: ajusta los atributos que indica el plan hasta que desaparezcan. Nunca apliques una importación que vaya a reemplazar el recurso. Aplica la importación: ```bash terraform apply ``` ```output Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed. ``` Comprueba que el recurso ya está en el estado: ```bash terraform state list terraform state show docker_network.app ``` ```output docker_network.app # docker_network.app: resource "docker_network" "app" { driver = "bridge" id = "3f9c2d7e5a41..." name = "red-app" ... } ``` Una vez aplicada, puedes borrar el bloque `import` de `red.tf`; ya no hace nada. ### Generar la configuración automáticamente Si el recurso tiene muchos atributos, deja solo el bloque `import` (sin el bloque `resource`) y pide a Terraform que escriba la configuración: ```bash terraform plan -generate-config-out=generado.tf ``` Terraform crea `generado.tf` con todos los atributos que lee del proveedor. Revísalo, elimina los valores por defecto que no aportan nada y mueve el bloque a su archivo definitivo antes de aplicar. La función es útil como punto de partida, no como código final. ### La orden terraform import La orden clásica sigue disponible y modifica el estado de inmediato, sin plan previo. Requiere que el bloque `resource` ya exista: ```bash terraform import docker_network.app 3f9c2d7e5a41b8c0e6d1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7 ``` Úsala solo para casos puntuales; el bloque `import` es más seguro en equipo. ## Paso 4: Hacer una copia del estado antes de modificarlo Antes de cualquier cambio manual en el estado, guarda una copia. `terraform state pull` funciona igual con estado local o remoto: ```bash terraform state pull > estado-$(date +%Y%m%d-%H%M).json ls estado-*.json ``` El archivo de estado puede contener datos sensibles (contraseñas generadas, claves privadas, IPs internas). No lo subas nunca a un repositorio y añade `*.tfstate*` y estas copias a `.gitignore`. ## Paso 5: Renombrar un recurso con un bloque moved Si cambias el nombre local de un recurso en el código, Terraform lo interpreta como "destruir el antiguo y crear uno nuevo". El bloque `moved` le indica que es el mismo objeto con otra dirección. Edita `red.tf`, cambia el nombre local de `app` a `principal` y añade el bloque `moved`: ```bash nano red.tf ``` ```hcl resource "docker_network" "principal" { name = "red-app" } moved { from = docker_network.app to = docker_network.principal } ``` Revisa el plan: ```bash terraform plan ``` ```output # docker_network.app has moved to docker_network.principal resource "docker_network" "principal" { name = "red-app" ... } Plan: 0 to add, 0 to change, 0 to destroy. ``` Aplica con `terraform apply` y comprueba el nuevo nombre con `terraform state list`. El mismo mecanismo sirve para mover un recurso dentro de un módulo (`to = module.red.docker_network.principal`). La alternativa imperativa es `terraform state mv docker_network.app docker_network.principal`, que cambia el estado al instante. Prefiere `moved`: queda en el código y el resto del equipo recibe el mismo cambio al hacer `plan`. ## Paso 6: Dejar de gestionar un recurso sin destruirlo A veces un recurso debe salir de Terraform pero seguir existiendo, por ejemplo porque pasa a gestionarlo otro equipo. Desde Terraform 1.7, el bloque `removed` lo expresa en el código. Sustituye el contenido de `red.tf` por: ```hcl removed { from = docker_network.principal lifecycle { destroy = false } } ``` Revisa el plan: ```bash terraform plan ``` ```output # docker_network.principal will no longer be managed by Terraform, but will not be destroyed # (destroy = false is set in the configuration) Plan: 0 to add, 0 to change, 0 to destroy. ``` Aplica y verifica que la red sigue existiendo aunque ya no esté en el estado: ```bash terraform apply terraform state list docker network ls --filter name=red-app ``` ```output NETWORK ID NAME DRIVER SCOPE 3f9c2d7e5a41 red-app bridge local ``` El equivalente imperativo es `terraform state rm docker_network.principal`. Para seguir con el tutorial, vuelve a importar la red repitiendo el paso 3 con el nombre `principal` y borra el bloque `removed`. ## Paso 7: Detectar y corregir el drift El drift aparece cuando alguien cambia la infraestructura sin pasar por Terraform. Provócalo borrando la red a mano: ```bash docker network rm red-app ``` Un plan en modo `-refresh-only` muestra qué ha cambiado fuera de Terraform sin proponer cambios en la infraestructura: ```bash terraform plan -refresh-only ``` ```output Note: Objects have changed outside of Terraform # docker_network.principal has been deleted - resource "docker_network" "principal" { ... } ``` Ahora decides qué versión es la correcta: - Si el cambio manual era un error, ejecuta `terraform apply` y Terraform recreará la red según el código. - Si el cambio manual es el nuevo estado deseado, ejecuta `terraform apply -refresh-only` para aceptar la realidad en el estado y después adapta el código. En este caso, recrea la red: ```bash terraform apply ``` ```output Apply complete! Resources: 1 added, 0 changed, 0 destroyed. ``` > Nota: `terraform refresh` está obsoleto. Usa `terraform plan -refresh-only` y `terraform apply -refresh-only`, que muestran los cambios antes de escribirlos en el estado. Para detectar drift de forma periódica, ejecuta `terraform plan -detailed-exitcode` en tu CI: devuelve `0` si no hay cambios, `2` si hay diferencias y `1` si hay un error. ## Paso 8: Migrar el estado a un backend remoto Un estado local en un único servidor no sirve para trabajar en equipo: no tiene bloqueo compartido ni copias automáticas. Para moverlo a un backend remoto, añade el bloque `backend` y ejecuta `terraform init -migrate-state`. Este ejemplo usa un bucket de Amazon S3 con bloqueo nativo mediante archivo (`use_lockfile`, disponible desde Terraform 1.10). El bucket debe existir y tener el versionado activado, y necesitas credenciales de AWS en el entorno: ```bash nano backend.tf ``` ```hcl terraform { backend "s3" { bucket = "mi-empresa-terraform-state" key = "tf-estado/terraform.tfstate" region = "eu-west-1" encrypt = true use_lockfile = true } } ``` Migra el estado y responde `yes` cuando Terraform pregunte si quieres copiarlo: ```bash terraform init -migrate-state ``` ```output Do you want to copy existing state to the new backend? Enter a value: yes Successfully configured the backend "s3"! ``` Verifica que el estado remoto contiene los mismos recursos y que el plan no propone cambios: ```bash terraform state list terraform plan ``` Cuando lo confirmes, guarda aparte y elimina `terraform.tfstate` y `terraform.tfstate.backup` del directorio para evitar confusiones. Si en lugar de S3 usas HCP Terraform, el proceso es el mismo con un bloque `cloud`. ## Solución de problemas **`Error acquiring the state lock`.** Otra ejecución tiene el estado bloqueado. Espera a que termine. Si el proceso murió (un CI cancelado, una sesión SSH cortada) y estás seguro de que no hay nada en marcha, libera el bloqueo con el ID que aparece en el mensaje: ```bash terraform force-unlock LOCK_ID ``` **`Resource already managed by Terraform` al importar.** La dirección ya existe en el estado. Comprueba con `terraform state show` si es el mismo objeto; si no lo es, elimínala con un bloque `removed` o `terraform state rm` antes de importar. **El plan de importación propone reemplazar el recurso.** Algún atributo que fuerza la recreación no coincide (en redes Docker, por ejemplo, `driver` o `ipam_config`). Copia el valor real que muestra `terraform plan` en tu bloque `resource`. **Estado dañado tras una edición manual.** Restaura la copia del paso 4. En un backend remoto, súbela con `terraform state push estado-AAAAMMDD-HHMM.json`. Terraform rechaza el push si el número de serie es anterior al actual; en ese caso comprueba bien qué copia es la correcta antes de usar `-force`. ## Conclusión Has importado un recurso existente con un bloque `import`, lo has renombrado con `moved`, lo has sacado de Terraform con `removed` sin destruirlo, has detectado y corregido el drift y has migrado el estado a un backend remoto. Como siguientes pasos, activa el versionado del bucket de estado, ejecuta `terraform plan -detailed-exitcode` de forma programada para vigilar el drift y reorganiza tu código en módulos usando bloques `moved` para no recrear nada.