--- title: "Cómo gestionar contenedores Docker con Terraform en Ubuntu" description: "Usa Terraform y el proveedor Docker en Ubuntu 24.04 para definir imágenes, redes, volúmenes y contenedores como código, y desplegar PostgreSQL con Adminer." category: "Automatización e IaC" language: es source: https://cubepath.com/es/docs/automatización-e-iac/terraform-con-proveedor-docker updated: 2026-09-25 --- # Cómo gestionar contenedores Docker con Terraform en Ubuntu Terraform describe infraestructura como código y la mantiene sincronizada con un archivo de estado. Con el proveedor Docker (`kreuzwerker/docker`) puedes gestionar imágenes, redes, volúmenes y contenedores de un host Docker igual que gestionarías recursos en la nube: con `plan` para ver los cambios antes de aplicarlos y `destroy` para eliminarlo todo. En este tutorial instalarás Terraform en Ubuntu 24.04 y desplegarás una base de datos PostgreSQL con volumen persistente y una interfaz web Adminer, conectadas por una red Docker privada. ## Requisitos previos 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 y tu usuario en el grupo `docker`, para que Terraform pueda usar el socket `/var/run/docker.sock` sin `sudo`. Compruébalo con `docker run --rm hello-world`. ## Paso 1: Instalar Terraform HashiCorp publica Terraform en su propio repositorio APT. Instala las herramientas necesarias y descarga la clave de firma: ```bash sudo apt update sudo apt install gnupg curl lsb-release 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. El nombre en clave de la versión (`noble`) se obtiene con `lsb_release`: ```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.x.x on linux_amd64 ``` ## Paso 2: Configurar el proveedor Docker Crea un directorio para el proyecto. Terraform lee todos los archivos `.tf` de un directorio, así que puedes repartir la configuración como prefieras: ```bash mkdir -p ~/terraform-docker cd ~/terraform-docker ``` Declara el proveedor y su versión. Fijar la versión principal evita que una actualización con cambios incompatibles rompa tu configuración: ```bash nano versions.tf ``` ```hcl terraform { required_version = ">= 1.5" required_providers { docker = { source = "kreuzwerker/docker" version = "~> 3.0" } } } provider "docker" { host = "unix:///var/run/docker.sock" } ``` Inicializa el proyecto. `terraform init` descarga el proveedor y crea el archivo de bloqueo `.terraform.lock.hcl`, que fija la versión exacta usada: ```bash terraform init ``` ```output Initializing provider plugins... - Finding kreuzwerker/docker versions matching "~> 3.0"... - Installing kreuzwerker/docker v3.x.x... ... Terraform has been successfully initialized! ``` ## Paso 3: Definir variables La contraseña de la base de datos no debe estar escrita en el código. Declárala como variable sensible, para que Terraform no la muestre en `plan` ni en `apply`: ```bash nano variables.tf ``` ```hcl variable "db_password" { description = "Contraseña del usuario postgres" type = string sensitive = true } variable "adminer_port" { description = "Puerto del host donde se publica Adminer" type = number default = 8080 } ``` Terraform lee automáticamente las variables de entorno con el prefijo `TF_VAR_`. Exporta la contraseña en la sesión actual (el espacio inicial evita que se guarde en el historial de bash con la configuración por defecto de Ubuntu): ```bash export TF_VAR_db_password='your_strong_password' ``` ## Paso 4: Definir imágenes, red y volumen Crea el archivo principal con los recursos de base. `docker_image` descarga la imagen; con `keep_locally = true` Terraform no la borra al destruir el entorno. La red de tipo bridge permite que los contenedores se encuentren por nombre, y el volumen guarda los datos de PostgreSQL fuera del contenedor: ```bash nano main.tf ``` ```hcl resource "docker_image" "postgres" { name = "postgres:16" keep_locally = true } resource "docker_image" "adminer" { name = "adminer:latest" keep_locally = true } resource "docker_network" "backend" { name = "tf-backend" driver = "bridge" } resource "docker_volume" "pgdata" { name = "tf-pgdata" } ``` ## Paso 5: Definir los contenedores Añade al final de `main.tf` los dos contenedores. El contenedor de PostgreSQL no publica ningún puerto en el host: solo es accesible desde la red `tf-backend`. Adminer sí se publica, y como está en la misma red puede conectar con la base de datos usando el nombre `db`: ```bash nano main.tf ``` ```hcl resource "docker_container" "db" { name = "db" image = docker_image.postgres.image_id restart = "unless-stopped" env = [ "POSTGRES_PASSWORD=${var.db_password}", ] networks_advanced { name = docker_network.backend.id } volumes { volume_name = docker_volume.pgdata.name container_path = "/var/lib/postgresql/data" } healthcheck { test = ["CMD-SHELL", "pg_isready -U postgres"] interval = "10s" timeout = "5s" retries = 5 } } resource "docker_container" "adminer" { name = "adminer" image = docker_image.adminer.image_id restart = "unless-stopped" env = [ "ADMINER_DEFAULT_SERVER=db", ] networks_advanced { name = docker_network.backend.id } ports { internal = 8080 external = var.adminer_port } depends_on = [docker_container.db] } ``` Usar `image_id` en lugar del nombre de la imagen crea una dependencia: si la imagen cambia (por ejemplo, tras un nuevo `pull`), Terraform sabe que debe recrear el contenedor. Añade unas salidas para tener a mano la información útil tras el despliegue: ```bash nano outputs.tf ``` ```hcl output "adminer_url" { value = "http://localhost:${var.adminer_port}" } output "db_container_id" { value = docker_container.db.id } ``` Formatea y valida la configuración antes de aplicarla: ```bash terraform fmt terraform validate ``` ```output Success! The configuration is valid. ``` ## Paso 6: Revisar el plan y aplicar `terraform plan` compara la configuración con el estado actual y muestra lo que va a crear, cambiar o destruir, sin tocar nada: ```bash terraform plan ``` ```output Plan: 6 to add, 0 to change, 0 to destroy. ``` Fíjate en que la variable `POSTGRES_PASSWORD` aparece como `(sensitive value)`. Aplica los cambios y confirma con `yes`: ```bash terraform apply ``` ```output Apply complete! Resources: 6 added, 0 changed, 0 destroyed. Outputs: adminer_url = "http://localhost:8080" db_container_id = "3f2a9c..." ``` ## Paso 7: Verificar el despliegue Comprueba con Docker que los contenedores están en marcha y que PostgreSQL pasa su healthcheck: ```bash docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' ``` ```output NAMES STATUS PORTS adminer Up 30 seconds 0.0.0.0:8080->8080/tcp db Up 31 seconds (healthy) 5432/tcp ``` Verifica que la base de datos responde y que Adminer sirve su página: ```bash docker exec db pg_isready -U postgres curl -sI http://localhost:8080 | head -n 1 ``` ```output /var/run/postgresql:5432 - accepting connections HTTP/1.1 200 OK ``` Para abrir Adminer desde tu navegador sin exponerlo a Internet, crea un túnel SSH desde tu equipo con `ssh -L 8080:localhost:8080 your_user@your_server_ip` y visita `http://localhost:8080`. Entra con el servidor `db`, el usuario `postgres` y la contraseña de `TF_VAR_db_password`. > Advertencia: Docker publica los puertos añadiendo sus propias reglas de iptables, que no pasan por UFW. Si no quieres que Adminer sea accesible desde fuera, publícalo solo en localhost añadiendo `ip = "127.0.0.1"` dentro del bloque `ports`. Lista los recursos que Terraform tiene en su estado: ```bash terraform state list ``` ```output docker_container.adminer docker_container.db docker_image.adminer docker_image.postgres docker_network.backend docker_volume.pgdata ``` ## Paso 8: Modificar y detectar cambios La ventaja de Terraform frente a lanzar contenedores a mano es que corrige las desviaciones. Borra el contenedor de Adminer directamente con Docker: ```bash docker rm -f adminer ``` Ejecuta de nuevo el plan. Terraform detecta que falta y propone recrearlo: ```bash terraform plan ``` ```output # docker_container.adminer will be created ... Plan: 1 to add, 0 to change, 0 to destroy. ``` Aplica para volver al estado declarado: ```bash terraform apply ``` Los cambios de configuración funcionan igual. Si cambias `adminer_port` a otro valor con `terraform apply -var 'adminer_port=8081'`, el plan mostrará que el contenedor debe reemplazarse, porque los puertos publicados no se pueden cambiar en un contenedor existente. ## Paso 9: Proteger el estado y destruir el entorno El archivo `terraform.tfstate` guarda todos los atributos de los recursos, **incluida la contraseña de la base de datos en texto claro**, aunque la variable sea sensible. Protégelo y no lo subas a Git: ```bash chmod 600 terraform.tfstate nano .gitignore ``` ```text .terraform/ *.tfstate *.tfstate.* ``` Sí debes versionar `.terraform.lock.hcl` para que todo el equipo use la misma versión del proveedor. En equipos de varias personas, guarda el estado en un backend remoto con bloqueo en lugar de en el disco local. Cuando ya no necesites el entorno, elimínalo: ```bash terraform destroy ``` Terraform borra los contenedores, la red y **el volumen `tf-pgdata` con todos los datos**. Las imágenes se conservan por `keep_locally = true`. Si quieres impedir que un `destroy` borre el volumen por error, añade al recurso `docker_volume` un bloque `lifecycle { prevent_destroy = true }`. ## Solución de problemas - **`Cannot connect to the Docker daemon at unix:///var/run/docker.sock`**: tu usuario no está en el grupo `docker` o no has vuelto a iniciar sesión tras añadirlo. Comprueba con `docker ps`. - **`No value for required variable` al hacer `plan`**: falta exportar `TF_VAR_db_password` en la sesión actual. - **`port is already allocated`**: otro proceso usa el puerto 8080 del host. Cambia `adminer_port`. - **El contenedor `db` se reinicia en bucle**: revisa `docker logs db`. Si reutilizas un volumen creado con otra versión mayor de PostgreSQL, el servidor no arranca con esos datos. ## Conclusión Has instalado Terraform, configurado el proveedor Docker y desplegado una base de datos con volumen persistente y una interfaz web en una red privada, con detección de desviaciones y destrucción controlada. Como siguientes pasos, puedes apuntar el proveedor a un host remoto con `host = "ssh://your_user@your_server_ip"`, mover el estado a un backend remoto y agrupar los recursos en módulos reutilizables por entorno.