Por qué Windmill en lugar de n8n o Make en 2026
n8n 3.0 marca un punto de inflexión: Docker es obligatorio, se eliminan más de 30 nodos y los nodos Code ya no pueden leer variables de entorno. Make sigue siendo un SaaS cerrado con límites de operaciones mensuales. Windmill toma el camino opuesto: tu código es un script de primera clase, versionado, testeable unitariamente y ejecutable desde un terminal igual que desde la interfaz. La licencia AGPLv3 garantiza que la versión autoalojada sea funcionalmente idéntica a la oferta cloud — sin características reservadas para empresas en el uso cotidiano.
Beneficios concretos para equipos DevOps y agencias
- TypeScript y Python nativos — los scripts se ejecutan en un worker Node.js o Python sin transpilación ni capas intermedias; las dependencias npm/pip se resuelven automáticamente en la primera ejecución.
- Editor web con ejecución directa — escribe, prueba y relanza un script desde el navegador en segundos, sin hacer push de un commit ni reiniciar ningún servicio.
- Multi-tenancy integrado — un único despliegue soporta múltiples workspaces aislados, ideal para una agencia que gestiona varios clientes o un equipo con entornos dev/staging/prod.
- Cron jobs y webhooks sin plugins — programar un job con
0 9 * * 1-5o exponer un endpoint webhook es una operación nativa, no un add-on. - Variables secretas cifradas — los secretos se cifran con AES-256 en base de datos y nunca se exponen en logs; cada workspace tiene su propio ámbito de secretos.
- Sin vendor lock-in — todos los scripts, flows y recursos son exportables en JSON/YAML e importables en cualquier otro despliegue de Windmill.
Requisitos mínimos en cifras
Windmill se apoya en PostgreSQL para la persistencia y en uno o más workers para la ejecución. El servidor HTTP es ligero; la carga de los workers determina el dimensionamiento del VPS. Cuenta 1 worker por vCPU disponible y aproximadamente 1 a 2 GB de RAM por worker Python activo.
Configuración mínima y recomendada
- Mínimo absoluto — 2 vCPU, 4 GB RAM, 20 GB SSD; adecuado para un workspace individual con 1 worker y jobs ocasionales.
- Equipo pequeño (recomendado) — 4 vCPU, 8 GB RAM, 40 GB SSD; soporta 3 workers concurrentes y PostgreSQL en el mismo host sin presión de memoria.
- Equipo o multi-cliente — 8 vCPU, 16 GB RAM, 80 GB SSD; soporta 6 a 8 workers y múltiples workspaces activos.
- Sistema operativo — Ubuntu 22.04 LTS o Debian 12; Docker Engine 24+ y Docker Compose v2 requeridos.
- Red — una IPv4 dedicada y un nombre de dominio para el certificado TLS; Windmill expone el puerto 8000 por defecto detrás de un reverse proxy.
Despliegue con Docker Compose en 7 pasos
Instalar Docker y Docker Compose v2
En Ubuntu 22.04 o Debian 12:
curl -fsSL https://get.docker.com | sh && docker compose version. Verifica que el comando devuelvaDocker Compose version v2.x.x(sin guion). Si tu sistema aún tienedocker-compose(v1), desinstálalo:apt remove docker-compose.Obtener el docker-compose.yml oficial
Crea un directorio dedicado y descarga la configuración de referencia:
mkdir -p /opt/windmill && cd /opt/windmill && curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml -o docker-compose.yml. Este archivo declara cuatro servicios:db(PostgreSQL 16),windmill_server,windmill_workerylsp(servidor de completado de código).Crear el archivo .env
Windmill requiere dos variables obligatorias. Genera una clave aleatoria:
openssl rand -hex 32. Luego crea/opt/windmill/.envcon:WM_BASE_URL=https://windmill.tu-dominio.comyWINDMILL_SECRET_KEY=<clave-generada>. Nunca hagas commit de este archivo.Ajustar el número de workers
Por defecto
docker-compose.ymlinicia 1 réplica dewindmill_worker. Para iniciar 3:docker compose up -d --scale windmill_worker=3. En un VPS con 4 vCPU / 8 GB, 2 o 3 workers es el equilibrio óptimo.Configurar Nginx como reverse proxy con TLS
Instala Nginx y Certbot:
apt install -y nginx certbot python3-certbot-nginx. Crea/etc/nginx/sites-available/windmillcon un bloqueserverescuchando en el puerto 80,proxy_pass http://127.0.0.1:8000;y las cabeceras para WebSocket. Obtén el certificado:certbot --nginx -d windmill.tu-dominio.com.Iniciar la stack y crear el workspace inicial
Lanza la stack:
cd /opt/windmill && docker compose up -d. Espera 30 segundos a que PostgreSQL esté listo y abrehttps://windmill.tu-dominio.com. La interfaz te pide crear un workspace y definir la cuenta de administrador.Activar el reinicio automático y verificar logs
En
docker-compose.ymlasegúrate de que cada servicio tengarestart: unless-stopped. Verifica los logs del servidor:docker compose logs -f windmill_server. La líneaWindmill server started on port 8000confirma que la instancia funciona.
Configuración: workspace, variables secretas y crons
Una vez la instancia arrancada, hay tres áreas de configuración que dominar antes de crear tus primeros workflows.
Ajustes esenciales tras la instalación
- Workspace y permisos — en
Settings → Workspacedefine la zona horaria (usada para crons) y activaAuto-approve executionssolo si todos los miembros son de confianza. - Variables y recursos —
Variablesalmacena claves escalares cifradas con AES-256.Resourcesalmacena objetos estructurados con esquema JSON (conexiones de base de datos, credenciales OAuth). - Schedules (crons) — en el editor de un script, haz clic en
Add schedule. La sintaxis aceptada es el formato cron estándar de 5 campos. La zona horaria del workspace se aplica automáticamente. - Webhooks — cada script expone un endpoint de la forma
/api/w/<workspace>/jobs/run/p/<path>. Autentica las llamadas entrantes con un token generado enSettings → Tokens. - Git Sync — en
Settings → Git Syncconecta un repositorio Git para sincronizar automáticamente todos los scripts, flows y recursos en cada modificación.
Ejemplo de workflow Python completo
Un script Python concreto: consulta una API externa, filtra los resultados e inserta filas en PostgreSQL mediante un recurso Windmill. Crea un nuevo script Python 3.11 y ejecútalo directamente desde el editor.
Script Python: importación API → filtro → PostgreSQL
- Declaración de dependencias — las líneas
# requirements:al inicio del script indican a Windmill qué paquetes pip instalar en el sandbox aislado. - Firma de la función principal — Windmill siempre llama a
def main(...). Los parámetros se inyectan desde la UI, el cron o el cuerpo del webhook. - Llamada HTTP con httpx —
import httpx; resp = httpx.get(api_url, timeout=10); resp.raise_for_status(); data = resp.json(). Windmill captura las excepciones no manejadas y las muestra en el log de ejecución. - Filtrado e inserción —
rows = [r for r in data if r.get('active')]; conn = psycopg2.connect(**db); cur.executemany('INSERT INTO items ...', rows); conn.commit(). El recursodbse pasa como dict Python. - Valor de retorno —
return {'inserted': len(rows), 'source': api_url}. El valor retornado se muestra en la UI y es accesible a los pasos siguientes de un Flow.
Endurecimiento y seguridad
Bloquea el puerto 8000 en UFW (ufw deny 8000/tcp) — solo Nginx debe acceder a él vía localhost. Añade cabeceras de seguridad en Nginx: add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff. Configura backups de PostgreSQL: docker exec windmill-db-1 pg_dump -U windmill windmill | gzip > /backups/windmill-$(date +%Y%m%d).sql.gz. Activa la política de contraseñas en Settings → Security y fuerza 2FA para las cuentas admin.
Resolución de problemas — errores frecuentes
Estos son los cinco problemas más comunes en un primer despliegue de Windmill, con sus causas reales y soluciones.
5 errores reales y sus correcciones
connection refusedal arrancar el servidor — el contenedorwindmill_serverarranca antes de que PostgreSQL esté listo. Añade unhealthcheckal serviciodby una cláusuladepends_on: db: condition: service_healthy.- Workers que permanecen
IDLEindefinidamente — causa clásica:DATABASE_URLapunta alocalhosten vez del nombre del servicio Docker (db). El valor correcto:postgresql://windmill:windmill@db/windmill. - Scripts Python que fallan con
ModuleNotFoundError— Windmill resuelve las dependencias pip en la primera ejecución. Si el worker no tiene acceso a internet, la instalación falla silenciosamente. Abre el puerto 443 saliente parapypi.org. - Webhook devuelve 403 Forbidden — el script está en modo
Require approval. Desactívalo en el editor o usa un token de servicio con el rolexecutor. - Cron que no se dispara a la hora prevista — la zona horaria del workspace es UTC por defecto. Verifica el ajuste en
Settings → Workspace → Timezoney reinicia:docker compose restart windmill_server.
Hacia la producción: checklist final
Antes de exponer Windmill a tráfico real o jobs críticos, repasa esta lista.
Puntos de validación antes de ir a producción
- TLS válido y HSTS — verifica que
https://windmill.tu-dominio.comresponde sin advertencias y añadeStrict-Transport-Security: max-age=31536000. - Backups probados — haz un dump de PostgreSQL, restáuralo en un contenedor de prueba y verifica que la interfaz conecta.
- Monitorización de workers — la página
Admin → Workersmuestra el estado en tiempo real. Configura una alerta si el número de workers activos cae por debajo de 1. - Rotación de secretos — si pierdes
WINDMILL_SECRET_KEY, los secretos cifrados en base de datos son irrecuperables. Guarda la clave en un gestor de secretos separado del VPS. - Versión fijada en docker-compose.yml — reemplaza
:latestpor:1.830.0para evitar actualizaciones automáticas no controladas. - Git Sync activado — conecta un repositorio privado para versionar todos tus scripts y flows.
Windmill vs n8n vs Temporal
Desplace la tabla
| Criterio | Windmill 1.830 | n8n 3.x | Temporal (cloud/autoalojado) |
|---|---|---|---|
| Lenguaje de workflows | TypeScript, Python, Go, Bash | JavaScript (nodos) + Code nodes limitados | Go, Java, Python, TypeScript vía SDK |
| Modelo de despliegue | Docker Compose, imagen única | Docker obligatorio desde v3.0 | Docker o Temporal Cloud SaaS |
| Licencia | AGPLv3 (autoalojado completo) | Sustainable Use License (restricciones agencias) | MIT (núcleo) + oferta cloud de pago |
| Variables secretas (autoalojado) | Incluidas, cifradas AES-256 | Incluidas | Incluidas (Temporal Namespace) |
| Editor web integrado | Sí — IDE completo con autocompletado | Sí — canvas visual | No — solo desarrollo local |
| Multi-tenancy | Sí — workspaces aislados nativos | Parcial — instancias separadas recomendadas | Sí — Temporal Namespaces |
| RAM mínima | 4 GB (1 worker + PostgreSQL) | 2 GB (SQLite), 4 GB (PostgreSQL) | 4 GB (autoalojado) / N/A (cloud) |
| Duración máxima del workflow | Ilimitada (jobs de larga duración) | Limitada por timeout del nodo | Ilimitada (durabilidad de actividades) |
| Curva de aprendizaje | Baja — código Python/TS estándar | Baja — canvas visual | Alta — modelo de actividades/señales |