--- title: "Cómo instalar Windmill en Ubuntu 24.04 con Docker Compose" description: "Despliega Windmill en Ubuntu 24.04 con Docker Compose y HTTPS automático, crea scripts en Python y Bash y ejecútalos por webhook o de forma programada." category: "Automatización de Workflows" language: es source: https://cubepath.com/es/docs/automatización-de-workflows/windmill-plataforma-de-automatizacion-de-scripts updated: 2026-09-25 --- # Cómo instalar Windmill en Ubuntu 24.04 con Docker Compose Windmill es una plataforma de código abierto para convertir scripts en Python, TypeScript, Go, Bash o SQL en tareas reutilizables: genera automáticamente un formulario a partir de los parámetros, los encadena en flujos, los programa como cron y los expone como webhooks. Es una alternativa autoalojada a herramientas como Retool o Zapier. En este tutorial desplegarás Windmill en Ubuntu 24.04 con los archivos oficiales de Docker Compose, lo publicarás con HTTPS gracias a Caddy y crearás un script que podrás ejecutar desde la interfaz, por webhook y según un horario. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 vCPU y 4 GB de RAM. Cada worker tiene un límite de 2 GB y el archivo oficial arranca tres. - Unos 20 GB libres en disco para imágenes, la base de datos y la caché de dependencias. - Un usuario no root con privilegios `sudo`. - Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker. - Un dominio o subdominio, por ejemplo `windmill.tu_dominio`, con un registro DNS A que apunte a la IP pública del servidor (`tu_ip_del_servidor`). - Los puertos 80 y 443 accesibles desde Internet, necesarios para que Caddy obtenga el certificado de Let's Encrypt. Comprueba que Docker y Compose responden: ```bash docker --version docker compose version ``` ```output Docker version 28.x.x, build xxxxxxx Docker Compose version v2.x.x ``` ## Paso 1: Descargar los archivos oficiales de Windmill Windmill publica tres archivos para instalaciones con Docker Compose: `docker-compose.yml` (base de datos PostgreSQL, servidor, workers, servicio de LSP y Caddy), `Caddyfile` (proxy inverso) y `.env` (cadena de conexión e imagen). No hace falta clonar el repositorio completo. Crea un directorio para la instalación y hazlo propiedad de tu usuario: ```bash sudo mkdir -p /opt/windmill sudo chown "$USER":"$USER" /opt/windmill cd /opt/windmill ``` Descarga los tres archivos: ```bash curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml -o docker-compose.yml curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/main/Caddyfile -o Caddyfile curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/main/.env -o .env ``` Verifica que están en su sitio: ```bash ls -la /opt/windmill ``` ```output -rw-rw-r-- 1 tu_usuario tu_usuario ... .env -rw-rw-r-- 1 tu_usuario tu_usuario ... Caddyfile -rw-rw-r-- 1 tu_usuario tu_usuario ... docker-compose.yml ``` ## Paso 2: Cambiar la contraseña de PostgreSQL Los archivos oficiales usan `changeme` como contraseña de PostgreSQL, tanto en `POSTGRES_PASSWORD` dentro de `docker-compose.yml` como en `DATABASE_URL` dentro de `.env`. Genera una contraseña aleatoria y sustituye ambas apariciones a la vez para que coincidan: ```bash PGPASS=$(openssl rand -hex 24) sed -i "s/changeme/${PGPASS}/" /opt/windmill/.env /opt/windmill/docker-compose.yml ``` Se usa una cadena hexadecimal porque no contiene caracteres que haya que escapar en la URL de conexión. Comprueba el resultado: ```bash grep -n "POSTGRES_PASSWORD" /opt/windmill/docker-compose.yml grep -n "DATABASE_URL" /opt/windmill/.env ``` ```output 40: POSTGRES_PASSWORD: 3f9c...e41a 1:DATABASE_URL=postgres://postgres:3f9c...e41a@db/windmill?sslmode=disable ``` > Importante: PostgreSQL solo aplica `POSTGRES_PASSWORD` la primera vez que inicializa el volumen de datos. Haz este cambio antes del primer arranque; si ya arrancaste con `changeme`, cambia la contraseña dentro de PostgreSQL o elimina el volumen `db_data` si todavía no contiene nada. ## Paso 3: Configurar Caddy con tu dominio y HTTPS Por defecto, el servicio `caddy` escucha solo en HTTP por el puerto 80 y también publica el puerto 25, que Windmill usa para los disparadores por correo electrónico. Para servir Windmill con HTTPS automático basta con indicar tu dominio en `BASE_URL` y publicar el puerto 443. Abre el archivo: ```bash nano /opt/windmill/docker-compose.yml ``` Localiza el servicio `caddy` y deja las secciones `ports` y `environment` así, sustituyendo `windmill.tu_dominio` por tu subdominio: ```yaml ports: - 80:80 - 443:443 environment: - BASE_URL=windmill.tu_dominio ``` Se ha quitado `25:25` porque en este tutorial no usarás disparadores por correo; si más adelante los necesitas, vuelve a añadir la línea y abre el puerto. Con un nombre de dominio en `BASE_URL`, Caddy solicita y renueva el certificado de Let's Encrypt por su cuenta y redirige HTTP a HTTPS. Comprueba que el archivo sigue siendo válido: ```bash cd /opt/windmill docker compose config --quiet && echo "configuración correcta" ``` ```output configuración correcta ``` Si usas UFW, permite SSH, HTTP y HTTPS: ```bash sudo ufw allow OpenSSH sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status ``` > Nota: Docker añade sus propias reglas de iptables para los puertos publicados, que no pasan por UFW. Por eso solo `caddy` publica puertos en este archivo: PostgreSQL y el servidor de Windmill usan `expose` y solo son accesibles dentro de la red de Docker. ## Paso 4: Arrancar Windmill Descarga las imágenes y levanta todos los servicios en segundo plano: ```bash cd /opt/windmill docker compose up -d ``` La primera vez tarda unos minutos porque descarga varias imágenes. Comprueba el estado de los contenedores: ```bash docker compose ps ``` ```output NAME IMAGE SERVICE STATUS windmill-caddy-1 ghcr.io/windmill-labs/caddy-l4:... caddy Up 1 minute windmill-db-1 postgres:18 db Up 1 minute (healthy) windmill-windmill_extra-1 ghcr.io/windmill-labs/windmill-extra windmill_extra Up 1 minute windmill-windmill_server-1 ghcr.io/windmill-labs/windmill:main windmill_server Up 1 minute windmill-windmill_worker-1 ghcr.io/windmill-labs/windmill:main windmill_worker Up 1 minute windmill-windmill_worker-2 ghcr.io/windmill-labs/windmill:main windmill_worker Up 1 minute windmill-windmill_worker-3 ghcr.io/windmill-labs/windmill:main windmill_worker Up 1 minute windmill-windmill_worker_native-1 ghcr.io/windmill-labs/windmill:main windmill_worker_native Up 1 minute ``` Revisa que Caddy haya obtenido el certificado: ```bash docker compose logs caddy | grep -i "certificate obtained" ``` Por último, consulta la versión a través de HTTPS: ```bash curl -s https://windmill.tu_dominio/api/version ``` ```output CE v1.xxx.x ``` Si recibes la versión, el proxy, el certificado y el servidor de Windmill funcionan. > Consejo: en un servidor con 4 GB de RAM puedes bajar los workers de 3 a 1 cambiando `replicas: 3` en el servicio `windmill_worker` y ejecutando de nuevo `docker compose up -d`. ## Paso 5: Primer acceso y creación del workspace Abre `https://windmill.tu_dominio` en el navegador. La instalación crea un superadministrador con estas credenciales por defecto: - Email: `admin@windmill.dev` - Contraseña: `changeme` Al iniciar sesión, Windmill abre un asistente de configuración inicial. Úsalo para: 1. Sustituir el email y la contraseña del superadministrador por los tuyos. No dejes las credenciales por defecto en un servidor expuesto a Internet. 2. Revisar los ajustes de la instancia y fijar la **Base URL** en `https://windmill.tu_dominio`. Windmill la usa para construir las URL de webhooks, de aprobación y de los enlaces que envía. Después crea un workspace para tu equipo, por ejemplo con el identificador `operaciones`. Los scripts, flujos, recursos, secretos y permisos se aíslan por workspace, así que conviene separar entornos o equipos en workspaces distintos. ## Paso 6: Crear un script en Python En Windmill, cada script define una función `main`. Sus parámetros tipados se convierten en un formulario en la interfaz y en el cuerpo JSON aceptado por la API. Las dependencias se detectan a partir de los `import` y se instalan en la caché del worker la primera vez. En tu workspace, pulsa **+ Script**, elige **Python**, dale una ruta como `u/tu_usuario/comprobar_url` y pega este código: ```python import requests def main(url: str = "https://example.com", timeout: int = 10): """Comprueba si una URL responde y cuánto tarda.""" respuesta = requests.get(url, timeout=timeout) return { "url": url, "estado": respuesta.status_code, "ok": respuesta.ok, "segundos": round(respuesta.elapsed.total_seconds(), 3), } ``` Pulsa **Test** en el panel derecho. La primera ejecución tarda algo más porque el worker instala `requests`; las siguientes usan la caché. Deberías ver un resultado como este: ```output { "url": "https://example.com", "estado": 200, "ok": true, "segundos": 0.142 } ``` Cuando funcione, pulsa **Deploy** para publicar la versión. Cada despliegue queda versionado y puedes volver a una versión anterior desde el historial del script. ## Paso 7: Crear un script en Bash Los scripts de Bash reciben los argumentos de forma posicional. Windmill detecta los parámetros a partir de las asignaciones del tipo `variable="$1"` al principio del script y genera el formulario con ellas. Crea un nuevo script con **+ Script**, elige **Bash**, usa la ruta `u/tu_usuario/uso_disco` y pega: ```bash directorio="${1:-/tmp}" set -euo pipefail echo "Uso de disco en ${directorio}:" df -h "$directorio" du -sh "$directorio" 2>/dev/null || true ``` Pulsa **Test** y comprueba la salida en el panel de logs. Ten en cuenta que el script se ejecuta dentro del contenedor del worker, no en el sistema del host: ve el sistema de archivos del contenedor. Para actuar sobre otros servidores, guarda la clave SSH como secreto o recurso de Windmill y conéctate desde el script. Despliega el script con **Deploy**. ## Paso 8: Ejecutar un script por webhook Cada script y flujo desplegado tiene endpoints HTTP para ejecutarlo. Primero necesitas un token: abre el menú de tu usuario, entra en la configuración de la cuenta, ve a **Tokens** y crea uno con una etiqueta descriptiva, por ejemplo `ci-webhook`. Copia el valor, porque solo se muestra una vez. Guárdalo en una variable de la sesión para no escribirlo en cada comando: ```bash export WM_TOKEN="tu_token" ``` El endpoint `run_wait_result` ejecuta el script y espera a que termine para devolver el resultado. Es la opción más cómoda para tareas cortas: ```bash curl -s -X POST \ "https://windmill.tu_dominio/api/w/operaciones/jobs/run_wait_result/p/u/tu_usuario/comprobar_url" \ -H "Authorization: Bearer ${WM_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"url": "https://www.cubepath.com", "timeout": 5}' ``` ```output {"url":"https://www.cubepath.com","estado":200,"ok":true,"segundos":0.211} ``` Para tareas largas, usa el endpoint asíncrono `run`, que encola el trabajo y devuelve inmediatamente su identificador: ```bash curl -s -X POST \ "https://windmill.tu_dominio/api/w/operaciones/jobs/run/p/u/tu_usuario/comprobar_url" \ -H "Authorization: Bearer ${WM_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"url": "https://www.cubepath.com"}' ``` ```output 0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b ``` Puedes seguir ese trabajo en la sección **Runs** de la interfaz, donde aparecen todos los trabajos con sus argumentos, logs y resultado. La pestaña de webhooks de cada script muestra las URL exactas y te permite crear un token limitado a ese script, lo que es preferible para integraciones externas como un pipeline de CI. > Advertencia: un token de usuario tiene los mismos permisos que su propietario. No lo guardes en repositorios; usa los secretos de tu sistema de CI. ## Paso 9: Programar la ejecución Para ejecutar un script de forma periódica, abre el script desplegado y crea una programación desde su sección de disparadores (**Schedules**). Indica: - **Expresión cron**: Windmill usa cron con un campo inicial de segundos. Por ejemplo, `0 0 2 * * *` ejecuta la tarea cada día a las 02:00 y `0 */15 * * * *` cada 15 minutos. La interfaz muestra las próximas ejecuciones para que confirmes que la expresión es la que quieres. - **Zona horaria**: por ejemplo `Europe/Madrid`, para que el horario no cambie con el horario de verano. - **Argumentos**: los valores fijos con los que se ejecutará el script en cada disparo. Activa la programación y guarda. Las ejecuciones programadas aparecen en **Runs** igual que las manuales, y puedes configurar un script como manejador de errores para recibir un aviso cuando alguna falle. ## Paso 10: Encadenar scripts en un flujo con aprobación Los flujos combinan scripts en pasos, con bifurcaciones, bucles, reintentos y pausas. Un caso típico es preparar un cambio, pedir aprobación a una persona y solo entonces aplicarlo. 1. En el workspace, pulsa **+ Flow** y dale una ruta, por ejemplo `f/operaciones/despliegue`. 2. Añade un primer paso que reutilice un script existente o uno nuevo en línea que prepare la operación. 3. Añade un paso de aprobación (en el editor aparece como paso de tipo **Approval** o como opción de suspensión en los ajustes avanzados del paso). Define cuántas aprobaciones hacen falta y el tiempo máximo de espera. 4. Añade el paso final que aplica el cambio. Sus entradas pueden referenciar los resultados de pasos anteriores, por ejemplo `results.a.version`. 5. Pulsa **Test flow**. La ejecución se detendrá en el paso de aprobación y mostrará los botones para aprobar o rechazar; al aprobar, continúa con el último paso. Los flujos tienen los mismos endpoints de webhook y las mismas opciones de programación que los scripts, así que puedes lanzarlos desde tu CI y dejar que alguien apruebe el paso crítico desde el navegador. ## Mantenimiento: actualizaciones y copias de seguridad Las imágenes de Windmill usan la etiqueta `main` con `pull_policy: always`. Para actualizar, descarga las imágenes nuevas y recrea los contenedores: ```bash cd /opt/windmill docker compose pull docker compose up -d ``` Todo el estado (scripts, flujos, historial, recursos y secretos cifrados) vive en PostgreSQL. Haz una copia completa del clúster antes de cada actualización; se usa `pg_dumpall` porque Windmill crea bases de datos y roles adicionales además de `windmill`: ```bash mkdir -p ~/backups docker compose -f /opt/windmill/docker-compose.yml exec -T db pg_dumpall -U postgres | gzip > ~/backups/windmill-$(date +%F).sql.gz ``` Comprueba que la copia no está vacía: ```bash ls -lh ~/backups/ ``` ## Solución de problemas **El navegador devuelve error de certificado o Caddy no arranca con HTTPS.** Revisa `docker compose logs caddy`. Las causas habituales son que el registro DNS A todavía no apunta al servidor o que el puerto 80 o 443 está bloqueado por el firewall. Comprueba la resolución con `dig +short windmill.tu_dominio`. **El servidor se reinicia con errores de autenticación en PostgreSQL.** La contraseña de `DATABASE_URL` en `.env` no coincide con la del volumen. Recuerda que `POSTGRES_PASSWORD` solo se aplica al crear el volumen: si lo cambiaste después, actualiza la contraseña del usuario `postgres` dentro de la base de datos o vuelve a la anterior. Consulta los logs con `docker compose logs windmill_server --tail=50`. **Los trabajos se quedan en cola sin ejecutarse.** No hay workers disponibles para el grupo del trabajo. Comprueba que los contenedores `windmill_worker` están en marcha con `docker compose ps` y revisa `docker compose logs windmill_worker --tail=50`. En la interfaz, la sección **Workers** muestra los workers conectados. **Un worker se reinicia durante la ejecución.** Probablemente alcanza el límite de memoria de 2 GB del servicio. Revisa `docker stats` y aumenta `memory` en `deploy.resources.limits` si el servidor tiene RAM suficiente. ## Conclusión Tienes Windmill funcionando en Ubuntu 24.04 con PostgreSQL, varios workers y HTTPS automático mediante Caddy, y has creado scripts en Python y Bash que puedes ejecutar desde la interfaz, por webhook o con una programación. Como siguientes pasos, puedes guardar credenciales de bases de datos y APIs como recursos del workspace, crear grupos para repartir permisos entre tu equipo y sincronizar tus scripts con un repositorio Git usando la CLI `wmill`.