--- title: "Cómo instalar Terraform en Ubuntu 24.04 y primeros pasos" description: "Instala Terraform desde el repositorio de HashiCorp en Ubuntu 24.04 y aprende el flujo init, plan, apply y destroy desplegando un contenedor Nginx con Docker." category: "Herramientas de Automatización" language: es source: https://cubepath.com/es/docs/herramientas-de-automatización/introduccion-a-terraform updated: 2026-09-25 --- # Cómo instalar Terraform en Ubuntu 24.04 y primeros pasos Terraform es una herramienta de infraestructura como código: describes recursos como servidores, redes, registros DNS o contenedores en archivos de configuración, y Terraform calcula qué debe crear, cambiar o eliminar para que la realidad coincida con ellos. En este tutorial instalarás Terraform en Ubuntu 24.04 desde el repositorio oficial de HashiCorp y aprenderás el flujo básico (`init`, `plan`, `apply`, `destroy`) gestionando un contenedor de Nginx con el proveedor de Docker. Todo se ejecuta en un único servidor, así que puedes aprender los conceptos sin cuenta en ninguna nube y sin coste. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM. - Un usuario no root con privilegios `sudo`. - Docker Engine instalado desde el repositorio oficial de Docker, con tu usuario en el grupo `docker` para poder ejecutar `docker` sin `sudo`. Cierra sesión y vuelve a entrar después de añadir el grupo. Confirma que Docker funciona con tu usuario antes de continuar: ```bash docker run --rm hello-world ``` La salida debe incluir `Hello from Docker!`. ## Paso 1: Instalar Terraform desde el repositorio de HashiCorp HashiCorp publica Terraform en su propio repositorio APT, que es la forma soportada de instalarlo en Ubuntu y lo mantiene actualizado con `apt upgrade`. Instala las herramientas necesarias para añadir el repositorio: ```bash sudo apt update sudo apt install gnupg curl lsb-release ``` Descarga la clave de firma de HashiCorp en `/etc/apt/keyrings`: ```bash sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/hashicorp-archive-keyring.gpg ``` Añade el repositorio, restringido a esa clave. El nombre en clave de la versión (`noble` en Ubuntu 24.04) se rellena automáticamente: ```bash echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list ``` Instala Terraform: ```bash sudo apt update sudo apt install terraform ``` Comprueba la instalación: ```bash terraform -version ``` ```output Terraform v1.13.3 on linux_amd64 ``` Tu versión será más nueva o más antigua según cuándo lo instales. Si quieres, activa el autocompletado para Bash y abre una shell nueva para que surta efecto: ```bash terraform -install-autocomplete ``` > Nota: Desde la versión 1.6, Terraform se publica bajo la Business Source License. Si necesitas una licencia de código abierto, OpenTofu es un fork comunitario que acepta el mismo lenguaje de configuración y los mismos comandos (`tofu` en lugar de `terraform`). ## Paso 2: Entender cómo funciona Terraform Antes de escribir configuración, conviene conocer las cuatro piezas que vas a usar: | Concepto | Qué es | |---|---| | Proveedor (provider) | Un plugin que habla con una API (AWS, Cloudflare, Docker, Kubernetes...). Lo descarga `terraform init`. | | Recurso (resource) | Un objeto gestionado por un proveedor, como un contenedor o un registro DNS. | | Estado (state) | Un archivo (`terraform.tfstate`) donde Terraform anota qué objetos reales gestiona y sus atributos. | | Plan | La lista de acciones que Terraform calcula comparando tu configuración con el estado y con los objetos reales. | La configuración se escribe en HCL (HashiCorp Configuration Language) en archivos con extensión `.tf`. Terraform lee todos los archivos `.tf` del directorio actual, así que la forma de repartirlos es cosa tuya. Una convención habitual es `main.tf` para los recursos, `variables.tf` para las entradas y `outputs.tf` para los valores que se muestran. ## Paso 3: Escribir tu primera configuración Crea un directorio de proyecto: ```bash mkdir ~/terraform-docker cd ~/terraform-docker ``` Crea `main.tf`: ```bash nano main.tf ``` ```hcl terraform { required_providers { docker = { source = "kreuzwerker/docker" version = "~> 3.0" } } } provider "docker" {} resource "docker_image" "nginx" { name = "nginx:stable" keep_locally = false } resource "docker_container" "nginx" { name = "tf-nginx" image = docker_image.nginx.image_id ports { internal = 80 external = 8000 } } ``` Esto es lo que hace cada bloque: - El bloque `terraform` declara qué proveedores necesita el proyecto. `kreuzwerker/docker` es el proveedor comunitario de Docker del Terraform Registry, y `~> 3.0` acepta cualquier versión 3.x pero nunca la 4.0, que podría traer cambios incompatibles. - `provider "docker" {}` configura el proveedor. Sin argumentos, se conecta al socket local de Docker. - `docker_image.nginx` descarga la imagen `nginx:stable`. `keep_locally = false` elimina la imagen cuando destruyes el recurso. - `docker_container.nginx` ejecuta un contenedor con esa imagen y publica el puerto 80 del contenedor en el puerto 8000 del servidor. `docker_image.nginx.image_id` es una referencia a un atributo de otro recurso. Terraform usa estas referencias para construir un grafo de dependencias, de modo que sabe que debe descargar la imagen antes de crear el contenedor sin que tengas que indicar un orden. ## Paso 4: Inicializar el proyecto `terraform init` descarga los proveedores indicados en la configuración y prepara el directorio de trabajo. Debes ejecutarlo una vez en cada proyecto nuevo y de nuevo cada vez que añadas un proveedor. ```bash terraform init ``` ```output Initializing the backend... Initializing provider plugins... - Finding kreuzwerker/docker versions matching "~> 3.0"... - Installing kreuzwerker/docker v3.6.2... - Installed kreuzwerker/docker v3.6.2 (self-signed, key ID BD080C4571C6104C) ... Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. ... Terraform has been successfully initialized! ``` `init` crea dos cosas: - `.terraform/`, con los binarios de los proveedores descargados. No lo subas al repositorio. - `.terraform.lock.hcl`, que fija las versiones exactas de los proveedores y sus checksums. Súbelo al repositorio para que todo el equipo use las mismas versiones. Da formato a la configuración y valídala: ```bash terraform fmt terraform validate ``` ```output Success! The configuration is valid. ``` `fmt` reescribe los archivos con el estilo canónico e imprime el nombre de los que ha modificado. `validate` comprueba la sintaxis y las referencias sin contactar con ninguna API. ## Paso 5: Previsualizar y aplicar cambios `terraform plan` muestra lo que haría Terraform, sin hacerlo. Lee siempre el plan antes de aplicarlo. ```bash terraform plan ``` ```output Terraform will perform the following actions: # docker_container.nginx will be created + resource "docker_container" "nginx" { + name = "tf-nginx" ... } # docker_image.nginx will be created + resource "docker_image" "nginx" { + name = "nginx:stable" ... } Plan: 2 to add, 0 to change, 0 to destroy. ``` Los símbolos describen cada acción: `+` crear, `~` modificar en el sitio, `-` destruir y `-/+` destruir y volver a crear. Aplica la configuración. Terraform vuelve a mostrar el plan y pide confirmación: ```bash terraform apply ``` Escribe `yes` cuando te lo pida: ```output docker_image.nginx: Creating... docker_image.nginx: Creation complete after 8s [id=sha256:...nginx:stable] docker_container.nginx: Creating... docker_container.nginx: Creation complete after 1s [id=4f1c...] Apply complete! Resources: 2 added, 0 changed, 0 destroyed. ``` Comprueba que el contenedor está en marcha y responde: ```bash docker ps --filter name=tf-nginx curl -I http://localhost:8000 ``` ```output CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4f1c2a9b7e10 3b25b682ea82 "/docker-entrypoint.…" 30 seconds ago Up 29 seconds 0.0.0.0:8000->80/tcp tf-nginx HTTP/1.1 200 OK Server: nginx/1.28.0 ... ``` Ejecuta `terraform plan` otra vez. Como la realidad ya coincide con la configuración, Terraform responde `No changes. Your infrastructure matches the configuration.` > Advertencia: Los puertos que publica Docker se saltan las reglas de UFW, así que el puerto 8000 es accesible desde internet en un servidor público. Destruye el contenedor al terminar este tutorial, o publícalo solo en localhost si haces pruebas en un servidor público. ## Paso 6: Añadir variables y outputs Los valores fijos en el código hacen que una configuración sea difícil de reutilizar. Las variables de entrada permiten cambiarlos sin editar los recursos, y los outputs muestran valores útiles tras cada `apply`. Crea `variables.tf`: ```bash nano variables.tf ``` ```hcl variable "container_name" { description = "Nombre del contenedor de Nginx" type = string default = "tf-nginx" } variable "external_port" { description = "Puerto del servidor que expone Nginx" type = number default = 8000 validation { condition = var.external_port > 1024 && var.external_port < 65536 error_message = "El puerto externo debe estar entre 1025 y 65535." } } ``` Crea `outputs.tf`: ```bash nano outputs.tf ``` ```hcl output "container_id" { description = "ID del contenedor de Nginx" value = docker_container.nginx.id } output "url" { description = "URL en la que responde Nginx" value = "http://localhost:${var.external_port}" } ``` Ahora actualiza el recurso del contenedor en `main.tf` para que use las variables: ```bash nano main.tf ``` ```hcl resource "docker_container" "nginx" { name = var.container_name image = docker_image.nginx.image_id ports { internal = 80 external = var.external_port } } ``` Las variables se referencian como `var.`, y `${...}` inserta una expresión dentro de una cadena. Aplica pasando un puerto distinto por línea de comandos: ```bash terraform apply -var="external_port=8080" ``` El puerto publicado no se puede cambiar en un contenedor en marcha, así que el plan muestra una sustitución: ```output # docker_container.nginx must be replaced -/+ resource "docker_container" "nginx" { ... ~ ports { # forces replacement ~ external = 8000 -> 8080 # forces replacement ... } } Plan: 1 to add, 0 to change, 1 to destroy. ``` Escribe `yes`. Cuando termina el `apply`, Terraform muestra los outputs: ```output Apply complete! Resources: 1 added, 0 changed, 1 destroyed. Outputs: container_id = "9d2e..." url = "http://localhost:8080" ``` En lugar de pasar `-var` cada vez, puedes poner los valores en un archivo `terraform.tfvars`, que Terraform carga automáticamente: ```bash nano terraform.tfvars ``` ```hcl external_port = 8080 ``` Consulta los outputs en cualquier momento con: ```bash terraform output url ``` ```output "http://localhost:8080" ``` ## Paso 7: Inspeccionar el estado Lo que Terraform sabe de tu infraestructura vive en `terraform.tfstate`, en el directorio del proyecto. No lo edites nunca a mano; usa los comandos `state`. Lista los recursos que gestiona Terraform: ```bash terraform state list ``` ```output docker_container.nginx docker_image.nginx ``` Muestra todos los atributos de un recurso: ```bash terraform state show docker_container.nginx ``` El estado puede contener valores sensibles, como contraseñas o claves que devuelven los proveedores. En proyectos de equipo, guárdalo en un backend remoto con bloqueo (por ejemplo un bucket compatible con S3 o PostgreSQL) en lugar de en el disco de una persona, y mantenlo fuera de Git. Crea ya un `.gitignore` para el proyecto, para no subir los archivos equivocados más adelante: ```bash nano .gitignore ``` ```text .terraform/ *.tfstate *.tfstate.* crash.log *.tfvars ``` Mantén `.terraform.lock.hcl` y todos los archivos `.tf` en el control de versiones. Deja fuera los `*.tfvars` si contienen secretos. ## Paso 8: Destruir la infraestructura `terraform destroy` elimina todos los recursos del estado. Muestra un plan con entradas `-` y pide confirmación: ```bash terraform destroy ``` Escribe `yes`: ```output docker_container.nginx: Destroying... [id=9d2e...] docker_container.nginx: Destruction complete after 1s docker_image.nginx: Destroying... [id=sha256:...nginx:stable] docker_image.nginx: Destruction complete after 0s Destroy complete! Resources: 2 destroyed. ``` Terraform ha eliminado el contenedor antes que la imagen, el orden inverso al de creación. Confirma que no queda nada: ```bash docker ps -a --filter name=tf-nginx terraform state list ``` Ninguno de los dos comandos devuelve recursos. ## Solución de problemas **`permission denied while trying to connect to the Docker daemon socket`**: tu usuario no está en el grupo `docker` o no has vuelto a iniciar sesión desde que lo añadiste. Compruébalo con `groups`, cierra sesión y vuelve a entrar. **`Error: Inconsistent dependency lock file` o `provider ... is not available`**: has añadido o cambiado un proveedor después del último `init`. Ejecuta `terraform init -upgrade`. **`Bind for 0.0.0.0:8000 failed: port is already allocated`**: otro contenedor o proceso usa ese puerto. Elige otro `external_port` o detén el otro proceso (`sudo ss -tlnp | grep 8000` muestra cuál es). **`Error acquiring the state lock`**: hay otro comando de Terraform en marcha en el mismo directorio, o una ejecución anterior se interrumpió. Espera a que termine la otra ejecución; solo si estás seguro de que no hay ninguna activa, libera el bloqueo con `terraform force-unlock LOCK_ID`, usando el ID del mensaje de error. Para ver más detalle de cualquier error, ejecuta el comando con `TF_LOG=DEBUG`, por ejemplo `TF_LOG=DEBUG terraform plan`. ## Conclusión Has instalado Terraform desde el repositorio de HashiCorp, has escrito una configuración con un proveedor y dos recursos dependientes y has recorrido el ciclo de vida completo: `init`, `plan`, `apply`, cambios mediante variables, inspección del estado y `destroy`. El mismo flujo sirve para cualquier proveedor, tanto si gestiona contenedores como registros DNS o servidores en la nube. Como siguientes pasos, prueba un proveedor de un servicio que ya uses (por ejemplo los registros DNS de Cloudflare), mueve el estado a un backend remoto con bloqueo y agrupa los recursos relacionados en módulos reutilizables.