[{"data":1,"prerenderedAt":264},["ShallowReactive",2],{"seo-verification":3,"blog-windmill-on-vps-typescriptpython-workflows-without-lock-in-en":6},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-windmill-on-vps-typescriptpython-workflows-without-lock-in-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":27,"featuredImage":29,"bgImage":30,"posterImage":31,"relatedSolution":32,"intro":35,"sections":36,"ctaTitle":199,"ctaBody":200,"ctaButton":201,"ctaUrl":202,"relatedPosts":203},438,"windmill-on-vps-typescriptpython-workflows-without-lock-in",{"fr":12,"en":10,"ar":13,"es":14},"windmill-workflow-engine-vps","محرك-سير-العمل-windmill-على-vps-python-typescript","windmill-en-vps-workflows-typescriptpython-sin-dependencias","Windmill on VPS: TypeScript\u002FPython Workflows Without Lock-in","Deploy Windmill v1.830 on a VPS in under an hour: TypeScript\u002FPython workflows, crons, webhooks and a built-in UI, with no dependency on n8n or Make.",11,0,false,"2026-10-13T00:00:00+00:00","2026-10-13T14:10:28+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},2,"Automation","automatisation","bg-brand-action\u002F10 text-brand-action",[28],{"id":23,"name":24,"slug":25,"color":26,"icon":25},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fwindmill-workflow-engine-vps-poster.svg",{"categorySlug":33,"appSlug":34},"automation-workflows","windmill","n8n 3.0, released in October 2026, makes Docker mandatory and removes over 30 legacy nodes. For teams evaluating alternatives, Windmill stands out: write native TypeScript or Python, run scripts directly from the web editor, and orchestrate dozens of workers on a single VPS. No paid plan required for secret variables, no proprietary nodes — just code versioned in a Git-backed workspace. This guide gets you to a production-ready Windmill v1.830 instance in under an hour.",[37,42,52,56,64,90,94,102,106,114,119,123,131,135,144],{"type":38,"title":39,"body":40,"items":41},"h2","Why Windmill Instead of n8n or Make in 2026","n8n 3.0 marks a turning point: Docker is now mandatory, 30+ nodes have been removed, and Code nodes can no longer read environment variables. Make remains a closed SaaS with monthly operation limits. Windmill takes the opposite approach: your code is a first-class script, versioned, unit-testable, and runnable from a terminal just as easily as from the UI. The AGPLv3 license ensures the self-hosted version is functionally identical to the cloud offering — no enterprise-only features for everyday use.",[],{"type":43,"title":44,"items":45,"body":5},"ul","Concrete Benefits for DevOps Teams and Agencies",[46,47,48,49,50,51],"**Native TypeScript and Python** — scripts run in a Node.js or Python worker without transpilation or intermediate node layers; npm\u002Fpip dependencies are resolved automatically by Windmill on first execution.","**Web editor with direct execution** — write, test, and re-run a script from the browser in seconds, without pushing a commit or restarting any service.","**Built-in multi-tenancy** — a single deployment supports multiple isolated workspaces, ideal for an agency managing several clients or a team with dev\u002Fstaging\u002Fprod environments.","**Cron jobs and webhooks out of the box** — scheduling a job with `0 9 * * 1-5` or exposing a webhook endpoint `\u002Fapi\u002Fw\u002F\u003Cworkspace>\u002Fjobs\u002Frun\u002Fp\u002F\u003Cpath>` is a native operation, not an add-on.","**Encrypted secret variables** — secrets are AES-256 encrypted at rest and never exposed in logs; each workspace has its own secrets scope.","**No vendor lock-in** — all scripts, flows, and resources are exportable as JSON\u002FYAML and importable into any other Windmill deployment.",{"type":38,"title":53,"body":54,"items":55},"Minimum Requirements","Windmill relies on PostgreSQL for persistence and one or more workers for execution. The HTTP server itself is lightweight; worker load determines VPS sizing. Budget 1 worker per available vCPU and approximately 1 to 2 GB of RAM per active Python worker (pip dependencies are installed in a sandboxed cache on disk).",[],{"type":43,"title":57,"items":58,"body":5},"Minimum and Recommended Configurations",[59,60,61,62,63],"**Bare minimum** — 2 vCPU, 4 GB RAM, 20 GB SSD; suitable for a solo workspace with 1 worker and occasional jobs.","**Small team (recommended)** — 4 vCPU, 8 GB RAM, 40 GB SSD; supports 3 concurrent workers and PostgreSQL on the same host without memory pressure.","**Team or multi-client** — 8 vCPU, 16 GB RAM, 80 GB SSD; supports 6 to 8 workers, multiple active workspaces, and Python jobs with heavy dependencies.","**OS** — Ubuntu 22.04 LTS or Debian 12; Docker Engine 24+ and Docker Compose v2 required.","**Network** — a dedicated IPv4 and a domain name for the TLS certificate; Windmill exposes port 8000 by default behind a reverse proxy.",{"type":65,"title":66,"steps":67,"body":5,"items":89},"steps","Docker Compose Deployment in 7 Steps",[68,71,74,77,80,83,86],{"title":69,"body":70},"Install Docker and Docker Compose v2","On Ubuntu 22.04 or Debian 12: `curl -fsSL https:\u002F\u002Fget.docker.com | sh && docker compose version`. Verify the command returns `Docker Compose version v2.x.x` (no hyphen). If your system still has `docker-compose` (v1), uninstall it: `apt remove docker-compose`.",{"title":72,"body":73},"Fetch the official docker-compose.yml","Create a dedicated directory and download the reference configuration: `mkdir -p \u002Fopt\u002Fwindmill && cd \u002Fopt\u002Fwindmill && curl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fwindmill-labs\u002Fwindmill\u002Fmain\u002Fdocker-compose.yml -o docker-compose.yml`. This file declares four services: `db` (PostgreSQL 16), `windmill_server`, `windmill_worker`, and `lsp` (code completion server).",{"title":75,"body":76},"Create the .env file","Windmill requires two mandatory variables. Generate a random key: `openssl rand -hex 32`. Then create `\u002Fopt\u002Fwindmill\u002F.env` with: `WM_BASE_URL=https:\u002F\u002Fwindmill.your-domain.com` and `WINDMILL_SECRET_KEY=\u003Cgenerated-key>`. Never commit this file — add `.env` to your `.gitignore` if you version your configuration.",{"title":78,"body":79},"Adjust the number of workers","By default `docker-compose.yml` starts 1 replica of `windmill_worker`. To start 3: `docker compose up -d --scale windmill_worker=3`. On a 4 vCPU \u002F 8 GB VPS, 2 or 3 workers is the right balance. You can adjust live without restarting the server: `docker compose up -d --no-recreate --scale windmill_worker=2`.",{"title":81,"body":82},"Configure Nginx as a reverse proxy with TLS","Install Nginx and Certbot: `apt install -y nginx certbot python3-certbot-nginx`. Create `\u002Fetc\u002Fnginx\u002Fsites-available\u002Fwindmill` with a `server` block listening on port 80, a `proxy_pass http:\u002F\u002F127.0.0.1:8000;` directive and the `X-Real-IP`, `X-Forwarded-For`, `Upgrade`, and `Connection` headers (for WebSocket support). Obtain the certificate: `certbot --nginx -d windmill.your-domain.com`. Certbot will automatically update the vhost to listen on HTTPS.",{"title":84,"body":85},"Start the stack and create the initial workspace","Launch the stack: `cd \u002Fopt\u002Fwindmill && docker compose up -d`. Wait 30 seconds for PostgreSQL to be ready, then open `https:\u002F\u002Fwindmill.your-domain.com` in a browser. The interface prompts you to create a workspace — choose a short identifier (`prod`, `acme-client`) — and set the admin account. This account is local to Windmill; you can connect an SSO provider (GitHub, OIDC) from `Settings → Authentication`.",{"title":87,"body":88},"Enable automatic restarts and verify logs","Add the restart policy to the stack: in `docker-compose.yml`, ensure each service has `restart: unless-stopped`. Reload if needed: `docker compose up -d`. Check the server logs: `docker compose logs -f windmill_server`. The line `Windmill server started on port 8000` confirms the instance is running. For workers: `docker compose logs -f windmill_worker`.",[],{"type":38,"title":91,"body":92,"items":93},"Configuration: Workspace, Secret Variables, and Crons","Once the instance is running, three configuration areas should be mastered before creating your first workflows.",[],{"type":43,"title":95,"items":96,"body":5},"Essential Post-Installation Settings",[97,98,99,100,101],"**Workspace and permissions** — in `Settings → Workspace`, set the timezone (used for crons) and enable `Auto-approve executions` only if all members are trusted. By default, scripts created by non-admin users enter a review queue before production execution.","**Variables and resources** — `Variables` stores AES-256 encrypted scalar keys (tokens, URLs). `Resources` stores typed structured objects (database connections, OAuth credentials) with a JSON schema. Prefer resources for anything with multiple fields: fewer copy-paste errors.","**Schedules (crons)** — in the script editor, click `Add schedule`. The accepted syntax is standard 5-field cron format. The per-workspace timezone applies automatically: `0 8 * * 1-5` triggers at 8:00 AM workspace time, not UTC.","**Webhooks** — each script exposes an endpoint of the form `\u002Fapi\u002Fw\u002F\u003Cworkspace>\u002Fjobs\u002Frun\u002Fp\u002F\u003Cscript-path>`. Authenticate incoming calls with a token generated in `Settings → Tokens`. Pass script parameters as a JSON POST body.","**Git Sync** — in `Settings → Git Sync`, connect a Git repository to automatically push all scripts, flows, and resources on every change. Ideal for traceability and cross-environment deployment.",{"type":38,"title":103,"body":104,"items":105},"Complete Python Workflow Example","Here is a concrete Python script: it queries an external API, filters the results, and inserts rows into a PostgreSQL database via a Windmill resource. Create a new Python 3.11 script, paste the code below, and run it directly from the editor to validate.",[],{"type":43,"title":107,"items":108,"body":5},"Python Script: API Import → Filter → PostgreSQL",[109,110,111,112,113],"**Dependency declaration** — `# requirements:` lines at the top of the script tell Windmill which pip packages to install in the isolated sandbox: `# requirements:\\n# httpx\\n# psycopg2-binary`. Windmill caches these dependencies between executions.","**Main function signature** — Windmill always calls the `def main(...)` function. Parameters are injected from the UI, the cron, or the webhook body: `def main(api_url: str, db: dict) -> dict:`. The `dict` type for `db` corresponds to a PostgreSQL resource.","**HTTP call with httpx** — `import httpx; resp = httpx.get(api_url, timeout=10); resp.raise_for_status(); data = resp.json()`. Windmill captures unhandled exceptions and displays them in the execution log with a full stack trace.","**Filtering and 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()`. The `db` resource is passed as a Python dict — keys match `psycopg2.connect` parameters.","**Return value** — `return {'inserted': len(rows), 'source': api_url}`. The returned value is displayed in the UI and accessible to subsequent steps in a Flow.",{"type":115,"title":116,"body":117,"items":118},"tip","Hardening and Security","Restrict network access to the VPS: block port 8000 in UFW (`ufw deny 8000\u002Ftcp`) — only Nginx should reach it via localhost. Enable security headers in Nginx: `add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; add_header Referrer-Policy strict-origin-when-cross-origin`. Configure PostgreSQL backups: `docker exec windmill-db-1 pg_dump -U windmill windmill | gzip > \u002Fbackups\u002Fwindmill-$(date +%Y%m%d).sql.gz`. Schedule this dump with a systemd cron — or a Windmill job itself. Enable password policy in `Settings → Security` and enforce 2FA for admin accounts. Restrict worker permissions: Windmill supports Worker Groups with restricted Docker images for untrusted scripts.",[],{"type":38,"title":120,"body":121,"items":122},"Troubleshooting — Common Errors","Here are the five most common problems encountered during a first Windmill deployment, with their real causes and fixes.",[],{"type":43,"title":124,"items":125,"body":5},"5 Real Errors and Their Fixes",[126,127,128,129,130],"**`connection refused` at server startup** — the `windmill_server` container starts before PostgreSQL is ready. Add a `healthcheck` to the `db` service and a `depends_on: db: condition: service_healthy` clause on `windmill_server`. Without this, the server attempts to connect to PostgreSQL during initialization and gives up. Check: `docker compose logs db | grep 'ready to accept'`.","**Workers that stay `IDLE` indefinitely** — classic cause: the `DATABASE_URL` variable in worker environments points to `localhost` instead of the Docker service name (`db`). In `docker-compose.yml`, the correct value is `postgresql:\u002F\u002Fwindmill:windmill@db\u002Fwindmill`. `localhost` from inside a container does not resolve the stack's PostgreSQL service.","**Python scripts failing with `ModuleNotFoundError`** — Windmill resolves pip dependencies on the first script run via a local cache. If the worker has no internet access (restrictive firewall), the silent installation fails. Check: `docker exec \u003Cworker-id> pip install httpx`. Open outbound port 443 for `pypi.org` and `files.pythonhosted.org`, or mount an internal pip mirror via the `PIP_INDEX_URL` variable.","**Webhook returns 403 Forbidden** — the token passed in the `Authorization: Bearer \u003Ctoken>` header is correct, but the script is in `Require approval` mode. Disable this in the script editor (`Settings → Execution policy → Direct execution`) or use a service token with the `executor` role.","**Cron that does not trigger at the expected time** — the workspace timezone defaults to UTC. If your workspace is in `Africa\u002FCasablanca` or `Europe\u002FParis`, a `0 9 * * *` expression triggers at 9:00 AM workspace time. Verify the timezone in `Settings → Workspace → Timezone` and restart the scheduler: `docker compose restart windmill_server`.",{"type":38,"title":132,"body":133,"items":134},"Toward Production: Final Checklist","Before exposing Windmill to real traffic or critical jobs, go through this list. It covers the points most commonly overlooked during a first deployment.",[],{"type":43,"title":136,"items":137,"body":5},"Validation Points Before Going Live",[138,139,140,141,142,143],"**Valid TLS and HSTS** — verify that `https:\u002F\u002Fwindmill.your-domain.com` responds without certificate warnings and add `Strict-Transport-Security: max-age=31536000` to the Nginx vhost.","**Tested backups** — make a PostgreSQL dump, restore it in a test container, and verify that the Windmill interface connects. An untested backup does not exist.","**Worker monitoring** — the `Admin → Workers` page shows real-time status. Set up an alert (Grafana, UptimeRobot, or a Windmill job itself) if the number of active workers drops below 1.","**Secret rotation** — Windmill variables are encrypted with `WINDMILL_SECRET_KEY`. If you lose this key, secrets encrypted in the database are unrecoverable. Back up the key in a separate secrets manager (Vaultwarden, Bitwarden) off the VPS.","**Pinned version in docker-compose.yml** — replace `image: ghcr.io\u002Fwindmill-labs\u002Fwindmill:latest` with `image: ghcr.io\u002Fwindmill-labs\u002Fwindmill:1.830.0` to avoid uncontrolled automatic updates. Update deliberately by consulting the CHANGELOG.","**Git Sync enabled** — connect a private repository to version all your scripts and flows. If you lose the VPS, restore the database and reimport from Git.",{"type":145,"title":146,"headers":147,"rows":152,"body":5,"items":198},"comparison","Windmill vs n8n vs Temporal",[148,149,150,151],"Criterion","Windmill 1.830","n8n 3.x","Temporal Cloud\u002FSelf-hosted",[153,158,163,168,173,178,183,188,193],[154,155,156,157],"Workflow language","TypeScript, Python, Go, Bash","JavaScript (nodes) + limited Code nodes","Go, Java, Python, TypeScript via SDK",[159,160,161,162],"Deployment model","Docker Compose, single image","Docker required since v3.0","Docker or Temporal Cloud SaaS",[164,165,166,167],"License","AGPLv3 (full self-host)","Sustainable Use License (agency restrictions)","MIT (core) + paid cloud offering",[169,170,171,172],"Secret variables (self-hosted)","Included, AES-256 encrypted","Included","Included (Temporal Namespace)",[174,175,176,177],"Built-in web editor","Yes — full IDE with autocompletion","Yes — visual canvas","No — local development only",[179,180,181,182],"Multi-tenancy","Yes — native isolated workspaces","Partial — separate instances recommended","Yes — Temporal Namespaces",[184,185,186,187],"Minimum RAM","4 GB (1 worker + PostgreSQL)","2 GB (SQLite), 4 GB (PostgreSQL)","4 GB (self-hosted) \u002F N\u002FA (cloud)",[189,190,191,192],"Max workflow duration","Unrestricted (long-running jobs)","Limited by node timeout","Unrestricted (activity durability)",[194,195,196,197],"Learning curve","Low — standard Python\u002FTS code","Low — visual canvas","High — activities\u002Fsignals model",[],"A VPS Ready for Windmill in Minutes","ServOrbit offers VPS with root access, dedicated IPv4, and a choice of Ubuntu\u002FDebian OS. Get your Windmill Docker Compose stack running from the first SSH login.","Deploy Windmill on ServOrbit VPS","\u002Fmarketplace\u002Fautomation-workflows\u002Fwindmill",[204,222,246],{"id":205,"slug":206,"slugs":207,"title":211,"excerpt":212,"readTime":213,"views":23,"isPinned":19,"publishedAt":214,"updatedAt":215,"category":216,"categories":217,"featuredImage":29,"bgImage":30,"posterImage":219,"relatedSolution":220},3,"install-n8n-on-vps-with-docker-complete-2026-guide",{"fr":208,"en":206,"ar":209,"es":210},"installer-n8n-vps","تثبيت-n8n-على-vps-مع-docker-دليل-شامل-2026","instalar-n8n-en-vps-con-docker","Install n8n on VPS with Docker: complete 2026 guide","Deploy n8n on VPS with Docker, reverse proxy and HTTPS. Covers V8 crash, 502 nginx, npm migration and execution security (GHSA-vrv8-j27g-g7cr advisory, August 2026).",16,"2026-06-05T00:00:00+00:00","2026-09-23T15:23:17+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[218],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Finstaller-n8n-vps-poster.svg",{"categorySlug":33,"appSlug":221},"n8n",{"id":223,"slug":224,"slugs":225,"title":229,"excerpt":230,"readTime":231,"views":18,"isPinned":19,"publishedAt":232,"updatedAt":233,"category":234,"categories":240,"featuredImage":29,"bgImage":30,"posterImage":242,"relatedSolution":243},359,"open-source-ai-stack-vps-ollama-openwebui-n8n",{"fr":226,"en":224,"ar":227,"es":228},"construire-stack-ia-open-source-vps","بيئة-ذكاء-اصطناعي-ذاتية-الاستضافة-vps","stack-ia-open-source-vps-ollama-openwebui-n8n","Open source AI stack on VPS: Ollama, Open WebUI and n8n","Assemble Ollama, Open WebUI and n8n on a single VPS: local LLM engine, chat interface and automation, 100% self-hosted without any cloud API.",8,"2026-09-18T00:00:00+00:00","2026-09-21T15:22:03+00:00",{"id":235,"name":236,"slug":237,"color":238,"icon":239},1,"Artificial Intelligence","intelligence-artificielle","bg-purple-500\u002F10 text-purple-400","ia",[241],{"id":235,"name":236,"slug":237,"color":238,"icon":239},"\u002Fblog\u002Fcovers\u002Fconstruire-stack-ia-open-source-vps-poster.svg",{"categorySlug":244,"appSlug":245},"artificial-intelligence","open-webui",{"id":247,"slug":248,"slugs":249,"title":252,"excerpt":253,"readTime":17,"views":18,"isPinned":19,"publishedAt":254,"updatedAt":21,"category":255,"categories":261,"featuredImage":29,"bgImage":30,"posterImage":263,"relatedSolution":29},436,"laravel-octane-vps-frankenphp-docker-traefik",{"fr":250,"en":248,"ar":251,"es":248},"deployer-laravel-vps-octane","laravel-octane-على-vps-frankenphp-docker-traefik","Laravel Octane on VPS: FrankenPHP, Docker and Traefik","Move from PHP-FPM to Laravel Octane with FrankenPHP in Docker Compose behind Traefik, understand request lifecycle changes and avoid memory leaks in production.","2026-10-12T00:00:00+00:00",{"id":256,"name":257,"slug":258,"color":259,"icon":260},4,"Development","developpement","bg-warning\u002F10 text-warning","dev",[262],{"id":256,"name":257,"slug":258,"color":259,"icon":260},"\u002Fblog\u002Fcovers\u002Fdeployer-laravel-vps-octane-poster.svg",1791901060036]