Pulumi es una herramienta de infraestructura como código que describe recursos con lenguajes de programación generales (Python, TypeScript, Go, C# o Java) en lugar de un lenguaje propio como HCL. Detrás hay el mismo modelo que en Terraform: un estado que registra qué existe, un plan que muestra los cambios y proveedores que hablan con cada API. En este tutorial instalarás Pulumi en Ubuntu 24.04, crearás un proyecto en Python que despliega un contenedor Nginx con el proveedor de Docker, usarás configuración y secretos por stack, y verás en qué se diferencia de Terraform.
Se usa Docker como destino para que puedas practicar sin cuenta en ninguna nube; el flujo de trabajo es idéntico con AWS, Azure, Kubernetes o cualquier otro proveedor.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios
sudo. - Python 3.12 (incluido en Ubuntu 24.04).
- Al menos 1 GB de RAM libre para Docker y los plugins de Pulumi.
- El puerto 8080 libre, que se usará para publicar el contenedor de prueba.
Paso 1: Instalar Docker y las dependencias de Python
Pulumi crea un entorno virtual de Python para cada proyecto, así que necesitas el módulo venv. Instala también Docker desde los repositorios de Ubuntu:
sudo apt update
sudo apt install python3-venv docker.io
Añade tu usuario al grupo docker para que Pulumi pueda usar el socket de Docker sin sudo:
sudo usermod -aG docker "$USER"
newgrp docker
Comprueba que Docker responde:
docker info --format '{{.ServerVersion}}'
27.5.1
Notapertenecer al grupo
dockerequivale a tener acceso root en el servidor. Hazlo solo con usuarios de confianza.
Paso 2: Instalar la CLI de Pulumi
Pulumi distribuye la CLI con un script de instalación oficial. Descárgalo primero para revisarlo antes de ejecutarlo:
curl -fsSL https://get.pulumi.com -o install-pulumi.sh
less install-pulumi.sh
Ejecútalo con tu usuario normal, sin sudo. Instala los binarios en ~/.pulumi/bin y añade esa ruta a tu ~/.bashrc:
sh install-pulumi.sh
Recarga la configuración del shell y comprueba la versión:
source ~/.bashrc
pulumi version
v3.197.0
La versión exacta será la última disponible. Para actualizar más adelante, vuelve a ejecutar el script.
Paso 3: Elegir dónde se guarda el estado
Por defecto Pulumi guarda el estado en Pulumi Cloud, un servicio que requiere cuenta. Para esta guía usarás el backend local, que guarda el estado en ~/.pulumi y no depende de nada externo:
pulumi login --local
Logged in to servidor as tu_usuario (file://~)
Con el backend local, Pulumi cifra los secretos del stack con una frase de paso. Expórtala en la sesión para que no la pida en cada comando, y guárdala en tu gestor de contraseñas, porque sin ella no podrás leer los secretos:
export PULUMI_CONFIG_PASSPHRASE="tu_frase_de_paso_segura"
Sustituye tu_frase_de_paso_segura por una frase larga y única.
Para trabajar en equipo, el estado puede ir también a un bucket con pulumi login s3://nombre-del-bucket (u otros almacenamientos como Azure Blob o Google Cloud Storage). El resto de comandos de la guía no cambia.
Paso 4: Crear el proyecto
Crea un directorio vacío y genera un proyecto a partir de la plantilla de Python:
mkdir -p ~/pulumi-web && cd ~/pulumi-web
pulumi new python --name pulumi-web --stack dev --yes
--yes acepta los valores por defecto del resto de preguntas. El comando crea el stack dev, un entorno virtual e instala las dependencias básicas. El proyecto queda así:
ls -A
.gitignore Pulumi.yaml __main__.py requirements.txt venv
Pulumi.yaml: nombre del proyecto y runtime (Python y el entorno virtual que usa).__main__.py: el programa que define la infraestructura.requirements.txt: dependencias de Python, incluido el SDKpulumi.
Un stack es una instancia independiente del proyecto con su propio estado y su propia configuración, por ejemplo dev, staging y produccion.
Paso 5: Añadir el proveedor de Docker
Cada proveedor de Pulumi es un paquete de Python. Añade el de Docker a requirements.txt:
nano requirements.txt
pulumi>=3.0.0,<4.0.0
pulumi-docker>=4.0.0,<5.0.0
Instala las dependencias en el entorno virtual del proyecto:
pulumi install
pulumi install lee Pulumi.yaml, instala los paquetes de requirements.txt en el entorno virtual y descarga los plugins de proveedor necesarios.
Paso 6: Escribir el programa
Sustituye el contenido de __main__.py por un programa que descarga la imagen de Nginx y arranca un contenedor:
nano __main__.py
import pulumi
import pulumi_docker as docker
config = pulumi.Config()
puerto = config.get_int("puerto") or 8080
imagen = docker.RemoteImage(
"nginx",
name="nginx:stable",
keep_locally=True,
)
contenedor = docker.Container(
"web",
name=f"web-{pulumi.get_stack()}",
image=imagen.image_id,
ports=[docker.ContainerPortArgs(internal=80, external=puerto)],
restart="unless-stopped",
)
pulumi.export("contenedor", contenedor.name)
pulumi.export("url", f"http://localhost:{puerto}")
Fíjate en image=imagen.image_id: el contenedor recibe una salida de otro recurso. Pulumi deduce de ahí que debe crear la imagen antes que el contenedor, igual que Terraform con las referencias entre recursos.
Paso 7: Previsualizar y desplegar
Muestra qué cambios haría Pulumi sin aplicarlos:
pulumi preview
Previewing update (dev):
Type Name Plan
+ pulumi:pulumi:Stack pulumi-web-dev create
+ ├─ docker:index:RemoteImage nginx create
+ └─ docker:index:Container web create
Resources:
+ 3 to create
Aplica los cambios:
pulumi up --yes
Updating (dev):
Type Name Status
+ pulumi:pulumi:Stack pulumi-web-dev created (4s)
+ ├─ docker:index:RemoteImage nginx created (3s)
+ └─ docker:index:Container web created (1s)
Outputs:
contenedor: "web-dev"
url : "http://localhost:8080"
Resources:
+ 3 created
Comprueba que el contenedor responde:
curl -sI http://localhost:8080 | head -n 1
HTTP/1.1 200 OK
Consulta los outputs del stack cuando quieras con:
pulumi stack output url
Paso 8: Configuración y secretos por stack
El programa lee el puerto con config.get_int("puerto"). Cambia el valor solo para el stack dev:
pulumi config set puerto 8081
El valor se guarda en Pulumi.dev.yaml, que se versiona junto al código:
cat Pulumi.dev.yaml
encryptionsalt: v1:...
config:
pulumi-web:puerto: "8081"
Aplica el cambio. Pulumi reemplaza el contenedor porque el puerto publicado no se puede modificar en caliente:
pulumi up --yes
Para valores sensibles usa --secret. Pulumi los cifra en el archivo de configuración y en el estado:
pulumi config set --secret token_api "valor_secreto"
grep token_api Pulumi.dev.yaml
pulumi-web:token_api:
secure: v1:3kZ0...
En el programa, léelo con config.require_secret("token_api"). El resultado es una salida marcada como secreta: si la exportas o la pasas a un recurso, Pulumi la oculta en la consola y la mantiene cifrada en el estado. Para verla en claro:
pulumi config get token_api
Paso 9: Trabajar con varios stacks
Crea un segundo stack para simular otro entorno:
pulumi stack init staging
pulumi config set puerto 8090
pulumi up --yes
Ahora hay dos contenedores, web-dev y web-staging, cada uno con su estado. Lista los stacks y cambia entre ellos:
pulumi stack ls
pulumi stack select dev
NAME LAST UPDATE RESOURCE COUNT
dev* 2 minutes ago 3
staging 30 seconds ago 3
Paso 10: Destruir los recursos
Borra los recursos de cada stack y después el propio stack:
pulumi destroy --yes --stack staging
pulumi stack rm staging --yes
Repite con dev si ya no lo necesitas. pulumi stack rm se niega a borrar un stack que todavía tiene recursos, lo que evita dejar infraestructura huérfana sin estado.
Pulumi frente a Terraform
| Aspecto | Pulumi | Terraform |
|---|---|---|
| Lenguaje | Python, TypeScript, Go, C#, Java, YAML | HCL |
| Lógica | Bucles, funciones, clases y paquetes del lenguaje | count, for_each, módulos y funciones integradas |
| Estado | Pulumi Cloud, local o bucket (S3, Azure, GCS) | Local o backend remoto (S3, HCP Terraform, pg, etc.) |
| Secretos | Cifrados en configuración y estado | En texto plano en el estado; sensitive solo oculta la salida |
| Pruebas | Tests unitarios con las herramientas del lenguaje (pytest, Jest) | terraform test |
| Licencia | Apache 2.0 | BSL 1.1 (OpenTofu es la bifurcación libre) |
Elige Pulumi si tu equipo ya programa en uno de esos lenguajes y la infraestructura tiene lógica que en HCL resulta forzada, o si quieres reutilizar componentes como paquetes normales. Elige Terraform u OpenTofu si prefieres un lenguaje declarativo que cualquiera pueda leer sin saber programar, o si ya tienes módulos y experiencia en HCL.
Solución de problemas
error: getting secrets manager: passphrase must be set: falta la variable PULUMI_CONFIG_PASSPHRASE en la sesión. Expórtala con la misma frase que usaste al crear el stack.
Cannot connect to the Docker daemon at unix:///var/run/docker.sock: tu usuario no está en el grupo docker en la sesión actual. Cierra sesión y vuelve a entrar, o ejecuta newgrp docker.
ModuleNotFoundError: No module named 'pulumi_docker': el paquete no está en el entorno virtual del proyecto. Comprueba requirements.txt y ejecuta pulumi install de nuevo.
Bind for 0.0.0.0:8080 failed: port is already allocated: otro proceso usa el puerto. Cambia el valor con pulumi config set puerto y vuelve a aplicar.
Conclusión
Has instalado Pulumi, creado un proyecto en Python con estado local, desplegado y modificado un contenedor, cifrado secretos en la configuración y gestionado dos stacks independientes. Como siguientes pasos, mueve el estado a un bucket compartido con pulumi login s3://..., sustituye el proveedor de Docker por el de tu nube o por pulumi-kubernetes, y agrupa recursos relacionados en un ComponentResource para reutilizarlos entre proyectos.
