[{"data":1,"prerenderedAt":206},["ShallowReactive",2],{"seo-verification":3,"blog-installing-windmill-on-a-vps-self-hosted-automation-ma-en":6,"promo-bar-likely":205},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-installing-windmill-on-a-vps-self-hosted-automation-ma-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"noindex":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":27,"featuredImage":29,"bgImage":30,"posterImage":31,"relatedSolution":32,"intro":35,"sections":36,"ctaTitle":148,"ctaBody":149,"ctaButton":150,"ctaUrl":151,"relatedPosts":152},27,"installing-windmill-on-a-vps-self-hosted-automation",{"fr":12,"en":10,"ar":13,"es":14},"installer-windmill-vps","تثبيت-windmill-على-خادم-vps-أتمتة-ذاتية-الاستضافة","instalar-windmill-en-un-vps","Installing Windmill on a VPS: self-hosted automation","Install Windmill on your VPS: turn your Python, TypeScript and Bash scripts into internal workflows and UIs, self-hosted via Docker.",13,1,false,"2026-05-24T00:00:00+00:00","2026-11-08T01:13:13+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\u002Finstaller-windmill-vps-poster.svg",{"categorySlug":33,"appSlug":34},"automation-workflows","windmill","Windmill is an open source platform that turns code (Python, TypeScript, Go, Bash, SQL) into workflows, APIs and internal interfaces. Designed for developers, it combines the flexibility of code with the speed of no-code. Self-hosted on a VPS, it becomes your automation engine and your internal app builder, without depending on a SaaS.",[37,41,52,55,62,87,90,99,102,106,109,117,146],{"type":38,"title":39,"body":40},"h2","Why self-host Windmill on a VPS","Windmill runs continuously on your server and exposes each of your scripts as a token-authenticated REST endpoint, a scheduled task or an auto-generated interface. Unlike a serverless function, there is no cold start and no execution limit imposed by a third party. Your Python, TypeScript or Bash scripts reach the databases and internal services of your VPS directly, without going through a cloud gateway. The backend is written in Rust: stateless API servers and workers that pull jobs from a queue stored in PostgreSQL, which lets you add workers without touching the rest.\n\nThe choice comes down mostly to how you work. If your need is to connect SaaS tools on a visual canvas, \u003Ca href=\"\u002Fen\u002Fblog\u002Finstall-n8n-on-vps-with-docker-complete-2026-guide\">n8n\u003C\u002Fa> remains more direct; for very long business processes with guaranteed resumption, \u003Ca href=\"\u002Fen\u002Fblog\u002Finstalling-temporal-on-a-vps-self-hosted-automation\">Temporal\u003C\u002Fa> is built for that. Windmill targets teams that already write code and want to make it runnable, schedulable and usable by non-developers within minutes. On licensing, the core is AGPLv3 and the Community Edition image is free, with quotas and features reserved for the Enterprise edition; reselling it or offering it as a managed service to your clients requires a commercial license.",{"type":42,"title":43,"items":44},"ul","Concrete benefits of self-hosting",[45,46,47,48,49,50,51],"No per-execution billing: thousands of jobs per day without a cloud bill.","Direct access to the VPS databases and internal services, without a tunnel.","Secrets and API keys stored encrypted in Windmill and never leaving your server.","Input forms and REST endpoints generated automatically for each script, from the signature of its `main` function.","Additional workers that can be added on the fly to absorb load peaks.","Several isolated workspaces on a single instance: one per team, per project or per environment.","Versioning: each deployment of a script is kept, and Git Sync pushes the workspace to a Git repository (included in Community Edition for workspaces of at most 2 users).",{"type":38,"title":53,"body":54},"Technical prerequisites","Windmill requires at least 2 vCPU and 4 GB of RAM. The official stack starts PostgreSQL 18, the server, three general-purpose workers capped at 2 GB of memory each, a native worker for lightweight jobs, the `windmill_extra` service (LSP autocompletion and the editor's debugger) and a Caddy in front: on 4 GB, reduce the number of workers. Plan 20 GB of disk for the database, the logs and the workers' dependency cache, which grows with every pip or npm package used. Ubuntu 22.04\u002F24.04 or Debian 12 with Docker Engine and the Docker Compose v2 plugin is enough, plus a domain name pointed at the VPS for the TLS certificate. No GPU is required. If you plan to run heavy Python workers (ML, pandas on large datasets), go for at least 4 vCPU and 8 GB of RAM.",{"type":42,"title":56,"items":57},"Size the VPS to the workload",[58,59,60,61],"**The vendor's rule**: about 1 worker per vCPU and 1 to 2 GB of RAM per worker. A job that exceeds its worker's memory limit (2048 MB by default) is killed with exit code 137.","**Solo use or trial**: 2 vCPU, 4 GB of RAM, 20 GB of disk, with a single general-purpose worker on top of the native worker. Enough for scheduled scripts and a few webhooks.","**Small team**: 4 vCPU, 8 GB of RAM, 40 GB of disk. The three workers of the official file and PostgreSQL fit on the same host without saturating memory.","**Several teams or heavy Python jobs**: 8 vCPU, 16 GB of RAM, 80 GB of disk for six to eight workers. At that point, a separate PostgreSQL database simplifies backups and upgrades.",{"type":63,"title":64,"steps":65},"steps","Deploy Windmill with Docker Compose",[66,69,72,75,78,81,84],{"title":67,"body":68},"Download the official files","Connect to your VPS over SSH and download the three reference files published in the Windmill repository: the `docker-compose.yml`, the reverse proxy's `Caddyfile` and the `.env`:\n\n```bash\nmkdir -p \u002Fopt\u002Fwindmill && cd \u002Fopt\u002Fwindmill\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fwindmill-labs\u002Fwindmill\u002Fmain\u002Fdocker-compose.yml -o docker-compose.yml\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fwindmill-labs\u002Fwindmill\u002Fmain\u002FCaddyfile -o Caddyfile\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fwindmill-labs\u002Fwindmill\u002Fmain\u002F.env -o .env\n```\n\nThere is no need to clone the whole repository. These files follow the `main` branch: reread them before each update, because the stack evolves (move to PostgreSQL 18, the `windmill_extra` service…).",{"title":70,"body":71},"Fill in the .env file","The `.env` holds only two variables. Replace the default PostgreSQL password and pin the image to a released version rather than `main` (1.864.1 as of 06\u002F11\u002F2026, to be checked on the GitHub releases page):\n\n```bash\nDATABASE_URL=postgres:\u002F\u002Fpostgres:MOT_DE_PASSE_FORT@db\u002Fwindmill?sslmode=disable\nWM_IMAGE=ghcr.io\u002Fwindmill-labs\u002Fwindmill:1.864.1\n```\n\nCopy the same password into `POSTGRES_PASSWORD`, under the `db` service of the `docker-compose.yml`. The `db` host is the name of the PostgreSQL service in the stack: do not replace it with `localhost`. To use an existing database, point `DATABASE_URL` to it and set the `db` service to `replicas: 0`.",{"title":73,"body":74},"Start the stack","Launch everything and follow the server's startup:\n\n```bash\ndocker compose up -d\ndocker compose ps\ndocker compose logs -f windmill_server\n```\n\n`docker compose ps` must list `db`, `windmill_server`, three replicas of `windmill_worker`, `windmill_worker_native`, `windmill_extra` and `caddy`; the full-text search indexer (`windmill_indexer`) stays at zero replicas, as it is an Enterprise feature. The server waits for PostgreSQL to pass its healthcheck, then applies the schema migrations itself. It listens on port 8000 of the Docker network without being published on the host: Caddy is what receives the traffic.",{"title":76,"body":77},"Configure the reverse proxy and TLS","The simplest option is to let the bundled Caddy obtain the Let's Encrypt certificate. In the `caddy` service of the `docker-compose.yml`, uncomment port 443 and replace `BASE_URL=\":80\"` with your domain, then run `docker compose up -d` again:\n\n```yaml\n  caddy:\n    ports:\n      - 80:80\n      - 25:25\n      - 443:443\n    environment:\n      - BASE_URL=windmill.example.com\n```\n\nKeep port 80 open for validation. If an nginx already serves other sites on the VPS, remove the `caddy` service, publish `127.0.0.1:8000:8000` on `windmill_server` and `127.0.0.1:3000:3000` on `windmill_extra`, then reproduce the `Caddyfile` routing: the `\u002Fws\u002F`, `\u002Fws_mp\u002F` and `\u002Fws_debug\u002F` paths go to `windmill_extra`, everything else to the server.\n\n```nginx\nlocation ~ ^\u002Fws(_mp|_debug)?\u002F {\n    proxy_pass http:\u002F\u002F127.0.0.1:3000;\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Host $host;\n}\nlocation \u002F {\n    proxy_pass http:\u002F\u002F127.0.0.1:8000;\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n}\n```\n\nThe `Upgrade` and `Connection` headers are essential: the editor uses WebSockets for autocompletion and debugging. The certificate is then obtained with `certbot --nginx -d windmill.example.com`.",{"title":79,"body":80},"Log in and secure","Open `https:\u002F\u002Fwindmill.example.com` and log in with the default superadmin account, `admin@windmill.dev` \u002F `changeme`. The first-run wizard offers to replace it with your own account: do it, because if you skip the step the default credentials stay active, and that is the most common flaw on publicly exposed Windmill instances. In the **Instance settings**, then set the Base URL to the public `https:\u002F\u002F` address: Windmill uses it to build webhook URLs and the return links of flows. Finally create your first workspace with a short identifier (`prod`, `data-team`).",{"title":82,"body":83},"Adjust the number of workers","The official file starts three replicas of `windmill_worker`, each capped at 2048 MB. On a 4 GB VPS, go down to one or two; on a bigger machine, go up to one worker per vCPU:\n\n```bash\ndocker compose up -d --scale windmill_worker=2\ndocker compose ps windmill_worker\n```\n\nTo make the setting permanent, change the service's `deploy.replicas` in the `docker-compose.yml`. The server does not need to restart: workers register themselves and appear on the interface's **Workers** page, with their group (`default` or `native`).",{"title":85,"body":86},"Update Windmill","Change the tag in `WM_IMAGE` after reading the release notes, then:\n\n```bash\ndocker compose pull\ndocker compose up -d\ndocker image prune\n```\n\nThe `pull` downloads the new images while the old containers are still running; on restart, the server migrates the database by itself and the workers finish their running jobs within Docker's stop timeout (30 seconds). Back up the database just before, and only run `docker image prune -a` if the VPS hosts nothing else: the command also removes the images of other projects.",{"type":38,"title":88,"body":89},"Configuration after installation","Once the instance is online, six settings decide how your scripts will run in production. They are all made from the interface, workspace by workspace.",{"type":42,"title":91,"items":92},"The essential settings",[93,94,95,96,97,98],"**Workspaces**: a workspace isolates scripts, flows, secrets and members. Create one per team or per environment rather than mixing everything, and keep the superadmin role for administering the instance.","**Variables and secrets**: all variables are encrypted with a symmetric key specific to the workspace; those marked secret are no longer displayed in the interface once entered. This is where API tokens and passwords belong, never in the script's code.","**Resources**: typed JSON objects (PostgreSQL connection, SMTP account, OAuth credentials). A parameter typed `postgresql` in a script then only offers resources of that type in the generated form.","**Schedules**: each script or flow accepts one or more schedules in five-field cron format, with an optional seconds field at the start. The schedule error handler is an Enterprise feature; in Community Edition, watch failed executions from the runs list.","**Webhooks**: a script is called asynchronously through `\u002Fapi\u002Fw\u002F\u003Cworkspace>\u002Fjobs\u002Frun\u002Fp\u002F\u003Cpath>` (response: the job identifier) or synchronously through `\u002Fapi\u002Fw\u002F\u003Cworkspace>\u002Fjobs\u002Frun_wait_result\u002Fp\u002F\u003Cpath>` (response: the result). Authenticate with the `Authorization: Bearer \u003Ctoken>` header rather than `?token=`, which ends up in access logs.","**Git Sync**: in the workspace settings, link a repository to push scripts, flows and resources to it on every deployment. Free in Community Edition up to 2 users per workspace; automatic deployment from Git, however, is reserved for the Enterprise edition.",{"type":38,"title":100,"body":101},"Example: an end-to-end Python script","This script queries an API, keeps the active items and inserts them into PostgreSQL through a Windmill resource. Create a Python script, paste the code and run it from the editor:\n\n```python\n# requirements:\n# httpx\n# psycopg2-binary\nfrom typing import TypedDict\n\nimport httpx\nimport psycopg2\n\n\nclass postgresql(TypedDict):\n    host: str\n    port: int\n    user: str\n    dbname: str\n    sslmode: str\n    password: str\n\n\ndef main(api_url: str, db: postgresql) -> dict:\n    resp = httpx.get(api_url, timeout=10)\n    resp.raise_for_status()\n    rows = [r for r in resp.json() if r.get(\"active\")]\n    conn = psycopg2.connect(\n        host=db[\"host\"], port=db[\"port\"], user=db[\"user\"],\n        password=db[\"password\"], dbname=db[\"dbname\"], sslmode=db[\"sslmode\"],\n    )\n    with conn, conn.cursor() as cur:\n        cur.executemany(\n            \"INSERT INTO items (id, name) VALUES (%(id)s, %(name)s) ON CONFLICT DO NOTHING\",\n            rows,\n        )\n    conn.close()\n    return {\"candidates\": len(rows), \"source\": api_url}\n```\n\nThe `# requirements:` comments replace the automatic inference of dependencies from imports: useful here, because the `psycopg2` import is published under the `psycopg2-binary` package. Windmill resolves these packages, writes a lockfile and caches them on the worker, so the same version of the script always runs with the same dependencies. The `postgresql` class, named after the resource type, makes a resource picker appear in the form. Once deployed under the path `f\u002Fetl\u002Fimport_items`, the script can be called from any tool:\n\n```bash\ncurl -X POST \"https:\u002F\u002Fwindmill.example.com\u002Fapi\u002Fw\u002Fprod\u002Fjobs\u002Frun_wait_result\u002Fp\u002Ff\u002Fetl\u002Fimport_items\" \\\n  -H \"Authorization: Bearer $WM_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"api_url\": \"https:\u002F\u002Fapi.example.com\u002Fitems\"}'\n```",{"type":103,"title":104,"body":105},"tip","Hardening the instance","Never publish ports 8000 and 3000 on the public interface: only the reverse proxy should reach them, locally. Back up the database with `docker compose exec -T db pg_dumpall -U postgres | gzip > \u002Fbackups\u002Fwindmill-$(date +%F).sql.gz`: the dump must cover the whole cluster, not just the `windmill` database, because Windmill creates side databases and attaches its security policies to cluster-level roles. This dump contains your encrypted secrets: store it off the VPS and protect it like a secret. Only mount the Docker socket in the workers if you trust every user of the instance, since it gives scripts control of the host. Finally, leave `REQUIRE_SIGNED_DEBUG_REQUESTS=true` on `windmill_extra`: disabling it on an instance reachable from the Internet exposes a debugger able to run code without authentication.",{"type":38,"title":107,"body":108},"Troubleshooting: first-deployment errors","Most incidents come from the database, worker memory or the reverse proxy. The symptoms and their fixes:",{"type":42,"title":110,"items":111},"Symptoms and fixes",[112,113,114,115,116],"**The `db` container stops right after an update**: the volume was created by PostgreSQL 16 and the image moved to 18, which refuses to read it. Follow the migration procedure in the documentation (full dump then restore), or pin `postgres:16` with its former mount point: version 16 remains maintained until November 2028.","**Job interrupted with code 137**: it exceeded its worker's memory limit. Increase `resources.limits.memory` on the `windmill_worker` service, or reduce the size of the data the script loads into memory.","**Server or workers unable to reach the database**: `DATABASE_URL` points to `localhost` instead of the `db` service, or the password differs from `POSTGRES_PASSWORD`. Fix the `.env`, then run `docker compose up -d` to recreate the containers.","**`ModuleNotFoundError` or failing dependency installation**: the worker downloads packages on the first run and must reach `pypi.org` and `files.pythonhosted.org` over outbound HTTPS. Behind a corporate proxy, the `PY_TRUSTED_HOST` and `PY_INDEX_CERT` variables handle trust; a private index is declared in the Instance settings (Enterprise edition).","**Editor without autocompletion, silent debugger**: the reverse proxy does not route `\u002Fws\u002F`, `\u002Fws_mp\u002F` and `\u002Fws_debug\u002F` to `windmill_extra`, or it strips the `Upgrade` and `Connection` headers. Reuse the routing of the official `Caddyfile`.",{"type":118,"title":119,"headers":120,"rows":125},"comparison","Windmill, n8n or Temporal",[121,122,123,124],"Criterion","Windmill","n8n","Temporal",[126,131,136,141],[127,128,129,130],"Writing workflows","Python, TypeScript, Go, Bash, SQL… scripts assembled into flows","Node canvas; Code node in JavaScript or Python","Application code via SDK (Go, Java, Python, TypeScript, .NET, PHP, Ruby, Rust)",[132,133,134,135],"License","AGPLv3 for the core; free Community Edition with quotas, licensed Enterprise features","Sustainable Use License (fair-code), `.ee` files under a commercial license","MIT for the server",[137,138,139,140],"Interface","Web editor, generated forms and internal apps","Visual node editor","Web UI for observation; code is written outside the tool",[142,143,144,145],"Best for","Teams that write code and want to expose it fast","Visual integrations between SaaS tools","Long processes with strong durability requirements",{"type":103,"body":147},"**Deploy it in one click from the ServOrbit Marketplace.** If you prefer to skip manual configuration, the ServOrbit Marketplace provisions Windmill automatically — server, two workers, PostgreSQL 16, nginx reverse proxy and a Let's Encrypt certificate — in a few minutes. The \"First access\" section of your client area shows the URL and generated credentials. The Windmill listing, reachable from the button at the end of this article, gets you started.","Deploy Windmill in one click on your ServOrbit VPS","The ServOrbit Marketplace automatically provisions Windmill with PostgreSQL 16, two workers and a reverse proxy — your scripts run in production in minutes, with no configuration needed.","See the Windmill listing","\u002Fapplications\u002Fautomation-workflows\u002Fwindmill",[153,171,188],{"id":154,"slug":155,"slugs":156,"title":160,"excerpt":161,"readTime":162,"views":163,"isPinned":19,"noindex":19,"publishedAt":164,"updatedAt":165,"category":166,"categories":167,"featuredImage":29,"bgImage":30,"posterImage":169,"relatedSolution":170},3,"install-n8n-on-vps-with-docker-complete-2026-guide",{"fr":157,"en":155,"ar":158,"es":159},"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).",19,4,"2026-06-05T00:00:00+00:00","2026-11-09T20:55:18+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[168],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Finstaller-n8n-vps-poster.svg",{"categorySlug":33,"appSlug":123},{"id":172,"slug":173,"slugs":174,"title":178,"excerpt":179,"readTime":163,"views":23,"isPinned":19,"noindex":19,"publishedAt":180,"updatedAt":181,"category":182,"categories":183,"featuredImage":29,"bgImage":30,"posterImage":185,"relatedSolution":186},26,"installing-activepieces-on-a-vps-self-hosted-automation",{"fr":175,"en":173,"ar":176,"es":177},"installer-activepieces-vps","تثبيت-activepieces-على-خادم-vps-أتمتة-ذاتية-الاستضافة","instalar-activepieces-en-un-vps","Installing Activepieces on a VPS: self-hosted automation","Deploy Activepieces on your VPS: self-hosted no-code automation, private data, unlimited integrations and a Docker deployment in 15 minutes.","2026-05-25T00:00:00+00:00","2026-09-07T11:26:10+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[184],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Finstaller-activepieces-vps-poster.svg",{"categorySlug":33,"appSlug":187},"activepieces",{"id":189,"slug":190,"slugs":191,"title":195,"excerpt":196,"readTime":163,"views":18,"isPinned":19,"noindex":19,"publishedAt":197,"updatedAt":198,"category":199,"categories":200,"featuredImage":29,"bgImage":30,"posterImage":202,"relatedSolution":203},24,"installing-node-red-on-a-vps-self-hosted-automation",{"fr":192,"en":190,"ar":193,"es":194},"installer-node-red-vps","تثبيت-node-red-على-خادم-vps-أتمتة-ذاتية-الاستضافة","instalar-node-red-en-un-vps","Installing Node-RED on a VPS: self-hosted automation","Install Node-RED on a VPS to automate your flow-based workflows, connect your APIs and IoT devices, all self-hosted and secure.","2026-05-27T00:00:00+00:00","2026-11-04T16:30:24+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[201],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Finstaller-node-red-vps-poster.svg",{"categorySlug":33,"appSlug":204},"node-red",true,1794270437156]