Pourquoi Windmill plutôt que n8n ou Make en 2026
n8n 3.0 marque un tournant : Docker obligatoire, 30+ nœuds supprimés, et les nœuds Code qui ne peuvent plus lire les variables d'environnement. Make reste un SaaS fermé avec des limites d'opérations par mois. Windmill prend une direction opposée : tout votre code est un script de première classe, versionné, testable unitairement, et exécutable depuis un terminal comme depuis l'UI. La licence AGPLv3 garantit que la version self-hosted reste fonctionnellement identique à l'offre cloud — pas de features réservées à l'entreprise pour l'usage courant.
Bénéfices concrets pour les équipes DevOps et agences
- TypeScript et Python natifs — vos scripts s'exécutent dans un worker Node.js ou Python sans transpilation ni couche de nœuds intermédiaires ; les dépendances npm/pip sont résolues automatiquement par Windmill à la première exécution.
- Éditeur web avec exécution directe — écrivez, testez et relancez un script depuis le navigateur en quelques secondes, sans avoir à pousser un commit ou redémarrer un service.
- Multilocataire intégré — un seul déploiement supporte plusieurs workspaces isolés, idéal pour une agence gérant plusieurs clients ou une équipe avec des environnements dev/staging/prod.
- Jobs cron et webhooks sans plugin — planifier un job
0 9 * * 1-5ou exposer un endpoint webhook/api/w/<workspace>/jobs/run/p/<path>est une opération native, pas un add-on. - Variables secrètes chiffrées — les secrets sont chiffrés en base et jamais exposés dans les logs ; chaque workspace a son propre périmètre de secrets.
- Pas de lock-in — tous les scripts, flows et ressources sont exportables en JSON/YAML et importables dans n'importe quel autre déploiement Windmill.
Prérequis chiffrés
Windmill s'appuie sur PostgreSQL pour la persistance et sur un ou plusieurs workers pour l'exécution. Le serveur HTTP lui-même est léger ; c'est la charge des workers qui dimensionne le VPS. Comptez 1 worker par vCPU disponible et environ 1 à 2 Go de RAM par worker Python actif (les dépendances pip sont installées en sandbox et mises en cache sur disque).
Configuration minimale et recommandée
- Minimum absolu — 2 vCPU, 4 Go de RAM, 20 Go de disque SSD ; convient à un workspace solo avec 1 worker et des jobs occasionnels.
- Petite équipe (recommandé) — 4 vCPU, 8 Go de RAM, 40 Go SSD ; permet 3 workers simultanés et PostgreSQL sur le même hôte sans saturation mémoire.
- Équipe ou multi-clients — 8 vCPU, 16 Go de RAM, 80 Go SSD ; supporte 6 à 8 workers, plusieurs workspaces actifs et des jobs Python avec dépendances lourdes.
- OS — Ubuntu 22.04 LTS ou Debian 12 ; Docker Engine 24+ et Docker Compose v2 requis.
- Réseau — une IPv4 dédiée et un nom de domaine pour le certificat TLS ; Windmill expose par défaut le port 8000 derrière un reverse proxy.
Déploiement Docker Compose en 7 étapes
Installer Docker et Docker Compose v2
Sur Ubuntu 22.04 ou Debian 12 :
curl -fsSL https://get.docker.com | sh && docker compose version. Vérifiez que la commande retourneDocker Compose version v2.x.x(sans tiret). Si votre système propose encoredocker-compose(v1), désinstallez-le :apt remove docker-compose.Récupérer le docker-compose.yml officiel
Créez un répertoire dédié et récupérez la configuration de référence :
mkdir -p /opt/windmill && cd /opt/windmill && curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml -o docker-compose.yml. Ce fichier déclare quatre services :db(PostgreSQL 16),windmill_server,windmill_workeretlsp(serveur de complétion de code).Créer le fichier .env
Windmill attend deux variables obligatoires. Générez une clé aléatoire :
openssl rand -hex 32. Puis créez/opt/windmill/.envavec :WM_BASE_URL=https://windmill.votre-domaine.cometWINDMILL_SECRET_KEY=<clé générée>. Ne commitez jamais ce fichier — ajoutez.envà votre.gitignoresi vous versionnez la configuration.Ajuster le nombre de workers
Par défaut,
docker-compose.ymldémarre 1 replica du servicewindmill_worker. Pour en démarrer 3 :docker compose up -d --scale windmill_worker=3. Sur un VPS 4 vCPU / 8 Go, 2 ou 3 workers est le bon équilibre. Vous pouvez ajuster à chaud sans redémarrer le serveur :docker compose up -d --no-recreate --scale windmill_worker=2.Configurer Nginx en reverse proxy avec TLS
Installez Nginx et Certbot :
apt install -y nginx certbot python3-certbot-nginx. Créez/etc/nginx/sites-available/windmillavec un blocserverécoutant le port 80, unproxy_pass http://127.0.0.1:8000;et les en-têtesX-Real-IP,X-Forwarded-For,UpgradeetConnection(pour les WebSockets). Obtenez le certificat :certbot --nginx -d windmill.votre-domaine.com. Certbot modifie automatiquement le vhost pour écouter en HTTPS.Démarrer la stack et créer le workspace initial
Lancez la stack :
cd /opt/windmill && docker compose up -d. Attendez 30 secondes que PostgreSQL soit prêt, puis ouvrezhttps://windmill.votre-domaine.comdans un navigateur. L'interface vous propose de créer un workspace — choisissez un identifiant court (prod,acme-client) — et de définir le compte administrateur. Ce compte est local à Windmill ; vous pourrez connecter un fournisseur SSO (GitHub, OIDC) depuisSettings → Authentication.Activer le redémarrage automatique et vérifier les logs
Ajoutez la politique de redémarrage à la stack : dans
docker-compose.yml, assurez-vous que chaque service arestart: unless-stopped. Rechargez si nécessaire :docker compose up -d. Vérifiez les logs du serveur :docker compose logs -f windmill_server. La ligneWindmill server started on port 8000confirme que l'instance est opérationnelle. Pour les workers :docker compose logs -f windmill_worker.
Configuration : workspace, variables secrètes et crons
Une fois l'instance démarrée, trois zones de configuration sont à maîtriser avant de créer vos premiers workflows.
Les réglages essentiels post-installation
- Workspace et permissions — dans
Settings → Workspace, définissez le fuseau horaire (utilisé pour les crons) et activez le modeAuto-approve executionsuniquement si tous les membres sont de confiance. Par défaut, les scripts créés par des utilisateurs non-admin passent en mode de revue avant exécution en production. - Variables et ressources —
Variablesstocke des clés scalaires (tokens, URLs) chiffrées AES-256.Resourcesstocke des objets structurés (connexions de bases de données, credentials OAuth) avec un schéma JSON typé. Préférez les ressources pour tout ce qui contient plusieurs champs : moins d'erreurs de copier-coller. - Schedules (crons) — dans l'éditeur d'un script, cliquez
Add schedule. La syntaxe acceptée est le format cron standard à 5 champs. Le timezone par workspace s'applique automatiquement :0 8 * * 1-5déclenche bien à 8h00 heure du workspace, pas UTC. - Webhooks — chaque script expose un endpoint de la forme
/api/w/<workspace>/jobs/run/p/<chemin-du-script>. Authentifiez les appels entrants avec un token généré dansSettings → Tokens. Passez les paramètres du script en corps JSON POST. - Synchronisation Git — dans
Settings → Git Sync, connectez un dépôt Git pour pousser automatiquement tous les scripts, flows et ressources à chaque modification. Idéal pour la traçabilité et le déploiement entre environnements.
Exemple de workflow Python complet
Voici un script Python concret : il interroge une API externe, filtre les résultats et insère les lignes dans une base PostgreSQL via une ressource Windmill. Créez un nouveau script de type Python 3.11, collez le code ci-dessous et exécutez-le directement depuis l'éditeur pour valider.
Script Python : import API → filtre → PostgreSQL
- Déclaration des dépendances — les lignes
# requirements:en tête du script indiquent à Windmill les packages pip à installer dans le sandbox isolé :# requirements:\n# httpx\n# psycopg2-binary. Windmill gère le cache de ces dépendances entre exécutions. - Signature de la fonction principale — Windmill appelle toujours la fonction
def main(...). Les paramètres sont injectés depuis l'UI, le cron ou le corps du webhook :def main(api_url: str, db: dict) -> dict:. Le typedictpourdbcorrespond à une ressource PostgreSQL. - Appel HTTP avec httpx —
import httpx; resp = httpx.get(api_url, timeout=10); resp.raise_for_status(); data = resp.json(). Windmill capture les exceptions non gérées et les affiche dans le journal d'exécution avec la stack trace complète. - Filtrage et insertion —
rows = [r for r in data if r.get('active')]; conn = psycopg2.connect(**db); cur = conn.cursor(); cur.executemany('INSERT INTO items(id, name) VALUES (%(id)s, %(name)s) ON CONFLICT DO NOTHING', rows); conn.commit(). La ressourcedbest passée comme dictionnaire Python — les clés correspondent aux paramètres depsycopg2.connect. - Valeur de retour —
return {'inserted': len(rows), 'source': api_url}. La valeur retournée est affichée dans l'UI et accessible aux étapes suivantes d'un Flow.
Durcissement et sécurité
Limitez l'accès réseau au VPS : fermez le port 8000 dans UFW (ufw deny 8000/tcp) — seul Nginx doit y accéder depuis localhost. Activez les headers de sécurité dans Nginx : add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; add_header Referrer-Policy strict-origin-when-cross-origin. Configurez les sauvegardes PostgreSQL : docker exec windmill-db-1 pg_dump -U windmill windmill | gzip > /backups/windmill-$(date +%Y%m%d).sql.gz. Planifiez ce dump avec un cron systemd ou… un job Windmill lui-même. Activez la politique de mots de passe dans Settings → Security et forcez 2FA pour les comptes admin. Limitez les permissions des workers non-root : Windmill supporte les Worker Groups avec des images Docker restreintes pour les scripts non fiables.
Dépannage — erreurs fréquentes
Voici les cinq problèmes rencontrés le plus souvent lors d'un premier déploiement Windmill, avec leurs causes réelles et leurs corrections.
5 erreurs réelles et leurs correctifs
connection refusedau démarrage du serveur — le conteneurwindmill_serverdémarre avant que PostgreSQL soit prêt. Ajoutez unhealthchecksur le servicedbet une clausedepends_on: db: condition: service_healthysurwindmill_server. Sans cela, le serveur tente de se connecter à PostgreSQL pendant son initialisation et abandonne. Vérifiez :docker compose logs db | grep 'ready to accept'.- Workers qui restent
IDLEindéfiniment — cause classique : la variableDATABASE_URLdans l'environnement des workers pointe verslocalhostau lieu du nom du service Docker (db). Dansdocker-compose.yml, la valeur correcte estpostgresql://windmill:windmill@db/windmill.localhostdepuis un conteneur ne résout pas le service PostgreSQL de la stack. - Scripts Python qui échouent avec
ModuleNotFoundError— Windmill résout les dépendances pip au premier lancement du script dans un cache local. Si le worker n'a pas d'accès internet (pare-feu restrictif), l'installation silencieuse échoue. Vérifiez :docker exec <worker-id> pip install httpx. Ouvrez le port 443 sortant pourpypi.orgetfiles.pythonhosted.org, ou montez un miroir pip interne via la variablePIP_INDEX_URL. - Webhook retourne 403 Forbidden — le token passé en header
Authorization: Bearer <token>est correct, mais le script est en modeRequire approval. Désactivez ce mode dans l'éditeur du script (Settings → Execution policy → Direct execution) ou utilisez un token de service avec le rôleexecutor. - Cron qui ne se déclenche pas à l'heure prévue — le fuseau horaire du workspace est UTC par défaut. Si votre workspace est en
Africa/Casablanca(UTC+1 en hiver, UTC+0 en été selon DST), une expression0 9 * * *déclenche à 9h00 du fuseau du workspace. Vérifiez le timezone dansSettings → Workspace → Timezoneet relancez le scheduler :docker compose restart windmill_server.
Vers la production : checklist finale
Avant d'exposer Windmill à du trafic réel ou à des jobs critiques, parcourez cette liste. Elle couvre les points qui passent le plus souvent à la trappe lors d'un premier déploiement.
Points de validation avant mise en production
- TLS valide et HSTS — vérifiez que
https://windmill.votre-domaine.comrépond sans avertissement de certificat et ajoutezStrict-Transport-Security: max-age=31536000dans le vhost Nginx. - Sauvegardes testées — faites un dump PostgreSQL, restaurez-le dans un conteneur de test et vérifiez que l'interface Windmill se connecte. Une sauvegarde non testée n'existe pas.
- Monitoring des workers — la page
Admin → Workersaffiche l'état en temps réel. Branchez une alerte (Grafana, UptimeRobot, ou un job Windmill lui-même) si le nombre de workers actifs passe sous 1. - Rotation des secrets — les variables Windmill sont chiffrées avec la
WINDMILL_SECRET_KEY. Si vous perdez cette clé, les secrets chiffrés en base sont irrécupérables. Sauvegardez la clé dans un gestionnaire de secrets (Vaultwarden, Bitwarden) séparé du VPS. - Version fixée dans docker-compose.yml — remplacez
image: ghcr.io/windmill-labs/windmill:latestparimage: ghcr.io/windmill-labs/windmill:1.830.0pour éviter les mises à jour automatiques non contrôlées. Mettez à jour délibérément en consultant le CHANGELOG. - Git Sync activé — connectez un dépôt privé pour versionner tous vos scripts et flows. En cas de perte du VPS, vous restaurez la base et réimportez depuis Git.
Windmill vs n8n vs Temporal
Faites défiler le tableau
| Critère | Windmill 1.830 | n8n 3.x | Temporal Cloud/Self-hosted |
|---|---|---|---|
| Langage des workflows | TypeScript, Python, Go, Bash | JavaScript (nœuds) + Code nodes limités | Go, Java, Python, TypeScript via SDK |
| Modèle de déploiement | Docker Compose, 1 image unique | Docker obligatoire depuis v3.0 | Docker ou Temporal Cloud SaaS |
| Licence | AGPLv3 (self-host complet) | Sustainable Use License (restrictions agences) | MIT (core) + offre cloud payante |
| Variables secrètes self-hosted | Inclus, chiffrées AES-256 | Inclus | Inclus (Temporal Namespace) |
| Éditeur web intégré | Oui — IDE complet avec autocomplétion | Oui — canvas nodal | Non — développement en local uniquement |
| Multilocataire | Oui — workspaces isolés natifs | Partiel — instances séparées recommandées | Oui — Namespaces Temporal |
| RAM minimale | 4 Go (1 worker + PostgreSQL) | 2 Go (SQLite), 4 Go (PostgreSQL) | 4 Go (self-hosted) / N/A (cloud) |
| Durée max d'un workflow | Sans durée limite (jobs longue durée) | Limitée par le timeout des nœuds | Sans limite (durabilité des activités) |
| Courbe d'apprentissage | Faible — code Python/TS standard | Faible — canvas visuel | Élevée — modèle d'activités/signaux |