* feat: audit the icon library against brand guidelines
Every icon component checked against its brand's own published guidelines for
correct artwork, current colours, and readability on both app surfaces.
- 127 marks now carry a per-theme pair (text-[#light] dark:text-[#dark]), applied
only where the brand publishes a reversed or dark variant. twMerge where the
component exposes a class prop, so callers can still pass sizing.
- 296 of 304 brand icons record their source in a comment above the <svg>,
including the rule where the brand imposes one (Google forbids recolouring,
Cal.com is deliberately greyscale, Oracle reserves the MySQL dolphin).
- BRAND_COLORS.md is generated from the components, so the table cannot drift
from the code.
- Marks that were unreadable on a surface: 13 -> 1 on dark, 9 -> 4 on light.
The remainder are blocked by trademark terms, not unfixed.
- Wrong artwork replaced where a first-party or CC0 source existed: PayPal is
the real three-colour monogram, Stripe is the bare S rather than an app tile,
gcloud resolves to Google's mark instead of a generic hexagon.
- Concept icons (CACertificate, DbIcon, Webdav, Asset*, Bcrypt) inherit
currentColor instead of hardcoding a colour.
Fixes a cross-component CSS bug: ten icons embedded <style> inside their <svg>.
Svelte only scopes a component's top-level style block, so those were injected as
document-global rules under names like .st0 and .cls-2, which four icons each
defined differently. WindmillIcon renders from the root logged-in layout, putting
.st0 { fill:#ffffff } on every page. Class names are now namespaced per icon.
Adds /kitchen_sink/icons, a gallery rendering every icon on both surfaces at once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: render brand icons in the surrounding text colour in control strips
A trigger picker mixing lucide glyphs (Webhook, Route, Database) with brand marks
(Kafka, GCP, AWS) read as two sets of controls once the marks became coloured.
Adds an .icon-mono utility that redirects descendant fills to currentColor, applied
by the container rather than passed to the icon. That is what makes it work on every
icon: GoogleCloudIcon has four hardcoded fills, no currentColor and no class prop, so
nothing passed to it could change its colour, and gradient-based marks cannot express
a monochrome variant at all without being redrawn.
- ToggleButton takes a monochromeIcon prop, opt-in since it is used app-wide.
- TriggersBadge, SidebarContent and QuickMenuItem (which backs GlobalSearchModal)
apply it unconditionally: these are uniform lists where one coloured entry among
grey ones reads as an error.
- DropdownV2 gains menuClass, because it portals its menu and a wrapper around the
component cannot style it. CaptureButton passes icon-mono through it.
!important is required because a handful of icons paint through style="fill:…", which
no selector outranks. Stroke is redirected only where one is declared, so shapes
carrying stroke="none" do not sprout outlines. Wrappers use display:contents, so no
layout box is added.
RowIcon is deliberately untouched — table rows keep showing brand colour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: close out the icon provenance gaps
Sources the 8 icons that had none and settles the 54 records whose author rated
itself below "high" and which no verifier ever reached — the earlier run's
verification stage was killed by a session limit.
46 confirmed as already correct, 11 citations corrected, 3 colours corrected.
Two changes were refuted and reverted by the adversarial pass:
- Mysql: the comment had the colour-to-shape mapping inverted. Rasterising the
first-party asset shows #00758F paints the dolphin and "My" while #F29111 paints
"SQL", not the reverse. The mark renders monochrome here, so nothing on screen
was ever wrong — only the note. Also rescoped the trademark sentence to what the
page literally says.
- AdobeAcrobatSignIcon: a "corrected" citation was rejected on evidence. The agent
claimed the original URL 404s; three fetches returned HTTP 200 with a genuine
Adobe SVG whose stylesheet is .a{fill:#584ccc}. Reverted to the original comment,
which also resolves the one unverified colour change on this branch — #584CCC is
current and first-party confirmed.
AmqpIcon is deliberately left with no brand colour: AMQP is an OASIS protocol, not
a vendor, and amqp.org publishes no palette.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: add icons for 11 resource types that had none
19 hub resource types fell back to a generic Boxes glyph. One agent per brand went
looking for a square vector logomark from a first-party source, with an adversarial
check on everything it produced; 11 landed and 8 correctly came back empty.
Added: beamer, campayn, codat, comapeo_server, klaviyo, matteroom, mollie, motimate,
paychex, terra, vectara. Each records its source, and the components follow the
library's conventions — no <style> block (Svelte does not scope those, which is what
made .st0 leak document-wide), gradient ids prefixed with the component name.
The other 8 keep the fallback, which is the right outcome rather than a failure:
- actimo, adrapid, aero_workflow, matteroom-adjacent niche products publish their mark
only as raster. Upscaled PNGs would look soft beside 300+ vector marks.
- gfw redirects to Global Nature Watch and publishes a wordmark, not a mark.
- leonardoai, localcontexts, weatherapi, webscrapingai serve nothing usable.
No hand-tracing: approximating a mark from a screenshot is invention, not sourcing,
and a wrong logo is worse than the tidy fallback glyph.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: lettermark fallback for reserved marks, and fix the resources table rows
Icons
- Brands that reserve their logo for licensees no longer ship it. BrandLetterIcon draws
the initial in the brand's own colour instead: recognisable, not their mark, and not
invented artwork. Adobe Acrobat Sign and MySQL use it, plus the eight resource types
whose brands publish no vector mark at all.
Adobe: "does not allow the use of its product icons by third parties in their products
or related materials of any kind, except through an Adobe partnership agreement".
On dark the letter inverts to a filled rounded square, because a mid-tone brand colour
chosen to read on white goes dim as a foreground on #2e3441. Where white-on-tile is
also dim, the tile takes a near-black letter instead; light-mode letters are darkened
along their own hue until they clear 3:1. Every pair was measured, not eyeballed.
- Google Docs was drawing a generic monochrome document glyph while carrying a comment
claiming Google's colours. Replaced with Google's own 192px product icon.
- Azure was drawn monochrome, justified by a comment citing Microsoft's rule against
distorting the mark — which drawing it monochrome is. Replaced with Microsoft's own
logo_azure.svg. Their terms say to use the icons "as they would appear within Azure";
permitted use is diagrams, training and documentation, which is recorded in the file.
- Adobe Acrobat Sign's artwork was a geometric "A" plus a squiggle, not Adobe's ribbon
swirl. Moot now that it is a lettermark, but the mark was wrong.
- Gradient, mask and clip ids in the new artwork are namespaced per icon; ids are
document-global and collide the same way the .st0 class names did.
Resources tables
- Description cells are a fixed two lines: min-h floors short ones, line-clamp ceilings
long ones, so every row is the same height. Full text on hover via title.
- Widened to 30rem (84 chars/line) and vertically centred. The clamp needs
display:-webkit-box, which stacks lines from the top, so the span sits in a
flex items-center wrapper rather than carrying the height itself.
- w-full min-w-0 max-w-[30rem] instead of a fixed w-96, so a narrow viewport shrinks the
column and truncates rather than forcing the page to scroll sideways.
- The actions column loses its border-l separator and right-aligns the "Shared globally"
badge, matching the rows that show buttons.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: icon-mono filled lucide outlines and missed currentColor brand marks
Two bugs in the monochrome utility, both from the fill rule being too blunt.
- Lucide icons are outlines: fill="none" with stroke="currentColor" and no fills on
their children. Forcing fill on every descendant overrode that none and turned each
glyph into a solid blob. The filled case is now scoped to svgs that do not declare
fill="none", and svgs that do only get children redirected if they declare a real
fill of their own — so a brand mark drawn as an outline still works.
- Brand marks that paint with currentColor carry their own text-[#hex] class, so
redirecting fills left them branded: MQTT stayed #660066, NATS #375C93. The svg now
inherits the container's colour, which is what actually makes them monochrome.
Also wires the sidebar's trigger section, which was never covered: those links render
through MenuLink, not the sub-item block that had the class.
Verified in the browser across all five shapes an icon can take — lucide outline,
hardcoded fill, currentColor plus brand class, outline root with filled children, and
inline style="fill:#..". Lucide keeps fill:none and a grey stroke; the rest follow the
container.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: dedicated monochrome trigger icons instead of a CSS override
Reverts the trigger surfaces to the icons that were there before the brand-colour
audit, as ./icons/triggers/ variants. A trigger picker lists brand marks beside lucide
glyphs (Webhook, Route, Database), so a coloured mark reads as a different kind of
thing rather than a peer.
Ten variants, restored from main where they were already monochrome: Kafka, NATS, MQTT,
AMQP, AWS, Azure, Nextcloud, Google, GitHub. Google Cloud is the exception — main's copy
is a greyscale rendition rather than currentColor, so it is rebuilt from the current
four-colour artwork with the fills dropped.
Separate files rather than the CSS override that was there, because coercion cannot work
in general: forcing fills to currentColor breaks lucide's outline icons, which are
fill="none" with a stroke, and marks that set their own text-[#hex] class ignore a fill
rule entirely. Both bugs were live. The .icon-mono utility, ToggleButton's monochromeIcon
prop and the DropdownV2 menuClass pass-through are gone with it.
index.ts documents which folder to use where: ./triggers/ for trigger surfaces, the
full-colour mark for the resource picker, AppConnect and docs, and keep the two in sync.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: trigger pages and global search still used the colour brand marks
The ToggleButtonGroup on each trigger page pairs a brand icon with a lucide Code
glyph, so GCP Pub/Sub rendered Google's four-colour mark next to a monochrome one.
Kafka, NATS, MQTT and the rest had the same wiring; they were just less obvious
because their marks are near-monochrome already.
Repoints all seven trigger pages and the global search nav entries at the
./icons/triggers/ variants. RowIcon is left on the full-colour marks: table rows
show brand colour by design.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: restore the greyscale GCP trigger icon, and show variants in the gallery
The trigger variant had been flattened to currentColor, which collapses Google's cloud
into one flat silhouette and loses the tonal steps that give it shape. The pre-audit
icon was greyscale, not monochrome — #B0B0B0 / #D0D0D0 / #E0E0E0 / #FFFFFF — so it is
restored verbatim from main.
Also globs icons/**/*.svelte in /kitchen_sink/icons so trigger variants render next to
the full-colour marks they shadow, labelled by folder. Comparing the two is the thing
this page was missing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: flow trigger dropdown rows use the desaturated marks too
The flow-graph badge menu still rendered the full-colour brand marks next to
lucide glyphs. Route both dropdowns through triggerIconMapMono: the badge
itself keeps the colour mark, only the rows it opens change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: rank resource-type search results by best match
Searching the description is what makes `gdrive` findable as "google", but it
also means "google" matches a dozen types that only mention the product in
passing. Rank a match on the type's own name above any description match, and
break ties on where the match starts, so `googleai` leads and a description
opening with "Google OAuth token..." beats one mentioning Google halfway
through.
Applied to all three resource-type searches: the Resource Types tab (whose bare
term also only searched the name until now), the add-resource drawer, and the
schema-narrowing picker.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: trigger pages and global search show the full-colour marks
The desaturated variants belong to the two dense lists that sit beside lucide
glyphs -- the sidebar trigger list and the capture dropdown. Everywhere else a
brand mark stands on its own and should be the real one: the per-kind trigger
pages, the command palette, the capture table and the chat tool cards. Records
the rule in icons/index.ts so the next caller picks the right folder.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review round on the icon and resource-type work
- AppConnectInner went back to listResourceTypeNames for the list: /resources/type/list
is not on the public app domain's route allow-list, so a published app's resource
picker 403'd and, because the throw left connectsManual unset, stayed empty on every
retry. Descriptions now load best-effort behind it.
- Dropped DropdownV2's menuClass: nothing passes it; the flow-graph badge menu styles
melt's Menu, which has its own.
- icons/index.ts named two surfaces for the desaturated variants; there are four, and
the flow-graph badge and the menu it opens differ. Dropped the stale GCloudIcon note.
- GoogleCloudIcon takes width/height again: generic call sites resolve it through
APP_TO_ICON_COMPONENT and pass no size, so gcloud rendered at 16px after the remap.
- The path explainer is one ResourcePathHint component instead of the same copy twice.
- BRAND_COLORS.md recorded Ansible, Datadog, Deno, DeepL and Toggl as fixed; each
publishes a second artwork swapped in by class, so their dark hex and ratio were
wrong. Header no longer claims a generator that isn't in the repo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: drop the duplicate gcloud icon and unblock the connect list
GCloudIcon.svelte was rewritten into the same four-colour mark as
GoogleCloudIcon.svelte and nothing pointed at it any more, so it was two files
drawing one logo waiting to drift apart.
The description fetch also sat on the critical path: the "Others" list showed
skeletons until a request for every type's full schema returned -- one that a
published app is guaranteed to get a 403 on. It now runs unawaited, and search
re-ranks when the descriptions land.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: AwsIcon and GoogleIcon take size again
The audit narrowed both to width/height with a 24px default, but every dynamic
call site passes size — RowIcon, the flow trigger badges, ToggleButton, global
search, the chat tool cards, the native-trigger page — so the SQS and Google
marks rendered at 24px wherever a smaller size was asked for. Both take size
again, keep width/height for the call sites that use those, and accept a class
so RowIcon's grey still applies.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: row-strip brand marks keep their colour
RowIcon greyed five of its seven brand marks with text-gray-400 while gcp and
azure rendered in colour. Now that AwsIcon accepts a class, the grey took its
wordmark but not its hardcoded #FF9900 smile, so the SQS row came out half
grey, half orange.
The rule this branch settled on is that only the four trigger menus desaturate;
a table is not one of them. Dropping the class from all five makes the strip
agree with the gcp and azure rows beside them, and with the lucide glyphs
staying grey since they carry no brand colour to keep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Open-source developer platform for internal code: APIs, background jobs, workflows and UIs. Self-hostable alternative to Retool, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs and custom UIs to trigger workflows and scripts as internal apps.
Scripts are turned into sharable UIs automatically, and can be composed together into flows or used into richer apps built with low-code. Supported languages: Python, TypeScript, Go, Bash, SQL, GraphQL, PowerShell, Rust, and more.
Try it - Website - Docs - Discord - Hub - Contributing
Windmill - Developer platform for APIs, background jobs, workflows and UIs
Windmill is fully open-sourced (AGPLv3) and Windmill Labs offers dedicated instances and commercial support and licenses.
https://github.com/user-attachments/assets/d80de1d9-64de-4d89-aacd-6df23fa81fc4
- Windmill - Developer platform for APIs, background jobs, workflows and UIs
Main Concepts
- Define a minimal and generic script in Python, TypeScript, Go or Bash that solves a specific task. The code can be defined in the provided Web IDE or synchronized with your own GitHub repo (e.g. through VS Code extension): provided Web IDE or synchronized with your own GitHub repo (e.g. through VS Code extension):
- Your scripts parameters are automatically parsed and generate a frontend.
- Make it flow! You can chain your scripts or scripts made by the community shared on WindmillHub.
- Build complex UIs on top of your scripts and flows.
Scripts and flows can be triggered by schedules, webhooks, HTTP routes, Kafka, WebSockets, emails, and more.
Build your entire infra on top of Windmill!
Show me some actual script code
//import any dependency from npm
import * as wmill from "windmill-client";
import * as cowsay from "cowsay@1.5.0";
// fill the type, or use the +Resource type to get a type-safe reference to a resource
type Postgresql = {
host: string;
port: number;
user: string;
dbname: string;
sslmode: string;
password: string;
};
export async function main(
a: number,
b: "my" | "enum",
c: Postgresql,
d = "inferred type string from default arg",
e = { nested: "object" }
//f: wmill.Base64
) {
const email = process.env["WM_EMAIL"];
// variables are permissioned and by path
let variable = await wmill.getVariable("f/company-folder/my_secret");
const lastTimeRun = await wmill.getState();
// logs are printed and always inspectable
console.log(cowsay.say({ text: "hello " + email + " " + lastTimeRun }));
await wmill.setState(Date.now());
// return is serialized as JSON
return { foo: d, variable };
}
Local Development
Windmill supports multiple ways to develop locally and sync with your instance:
| Tool | Description |
|---|---|
| CLI | Sync scripts from local files or GitHub, run scripts/flows from the command line |
| VS Code Extension | Edit and test scripts & flows directly from VS Code / Cursor with full IDE support |
| Git Sync | Two-way sync between Windmill and your Git repository |
| Claude Code | AI-assisted development with Claude for scripts, flows, and apps |
https://github.com/user-attachments/assets/c541c326-e9ae-4602-a09a-1989aaded1e9
You can run scripts locally by passing the right environment variables for the wmill client library to fetch resources and variables from your instance. See local development docs.
Stack
- Database: Postgres (compatible with Aurora, Cloud SQL, Neon, Azure PostgreSQL)
- Backend: Rust - stateless API servers and workers pulling jobs from a Postgres queue
- Frontend: Svelte 5
- Sandboxing: nsjail and PID namespace isolation
- Runtimes:
- TypeScript/JavaScript: Bun (default) and Deno
- Python: python3 with uv for dependency management
- Go, Bash, PowerShell, PHP, Rust, C#, Java, Ansible
Fastest Self-Hostable Workflow Engine
We have compared Windmill to other self-hostable workflow engines (Airflow, Prefect & Temporal) and Windmill is the most performant solution for both benchmarks: one flow composed of 40 lightweight tasks & one flow composed of 10 long-running tasks.
All methodology & results on our Benchmarks page.
Security
- Sandboxing: nsjail for filesystem/resource isolation, and PID namespace isolation (enabled by default) to prevent jobs from accessing worker process memory
- Secrets: One encryption key per workspace for credentials stored in Windmill's K/V store. We recommend encrypting the Postgres database as well.
See Security documentation for details.
Performance
Once a job started, there is no overhead compared to running the same script on the node with its corresponding runner (Deno/Go/Python/Bash). The added latency from a job being pulled from the queue, started, and then having its result sent back to the database is ~50ms. A typical lightweight deno job will take around 100ms total.
Architecture
How to self-host
For detailed setup options, see Self-Host documentation.
Docker compose
Deploy Windmill with 3 files (docker-compose.yml, Caddyfile, .env):
curl https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml -o docker-compose.yml
curl https://raw.githubusercontent.com/windmill-labs/windmill/main/Caddyfile -o Caddyfile
curl https://raw.githubusercontent.com/windmill-labs/windmill/main/.env -o .env
docker compose up -d
Go to http://localhost - default credentials: admin@windmill.dev / changeme
Using an external database: Set DATABASE_URL in .env to point to your managed Postgres (AWS RDS, GCP Cloud SQL, Azure, Neon, etc.) and set db replicas to 0.
Kubernetes (Helm charts)
helm repo add windmill https://windmill-labs.github.io/windmill-helm-charts/
helm install windmill-chart windmill/windmill --namespace=windmill --create-namespace
See windmill-helm-charts for configuration options.
Cloud providers
Windmill works on AWS (EKS/ECS), GCP, Azure, Ubicloud, Fly.io, Render.com, Hetzner, Digital Ocean, and others. Rule of thumb: 1 worker per 1vCPU and 1-2 GB RAM.
OAuth, SSO & SMTP
Configure OAuth and SSO (Google Workspace, Microsoft/Azure, Okta) directly from the superadmin UI. See documentation.
License
The Community Edition is free to use internally. For commercial redistribution or managed services, contact sales@windmill.dev. See LICENSE and Pricing for details.
The "Community Edition" of Windmill available in the docker images hosted under ghcr.io/windmill-labs/windmill and the github binary releases contains the files under the AGPLv3 and Apache 2 sources but also includes proprietary and non-public code and features which are not open source and under the following terms: Windmill Labs, Inc. grants a right to use all the features of the "Community Edition" for free without restrictions other than the limits and quotas set in the software and a right to distribute the community edition as is but not to sell, resell, serve Windmill as a managed service, modify or wrap under any form without an explicit agreement.
The binary compilable from source code in this repository without the "enterprise" feature flag is open-source under the LICENSE-AGPLv3 License terms and conditions.
To re-expose directly any Windmill parts to your users as a feature of your product, with the exception of iframed public Windmill "apps", or to build a feature on top of "Windmill Community Edition" that you sell commercially or embed in a distributable product or binary, you must get a commercial license. Contact us at sales@windmill.dev if you have any questions. To do the same from the binary compiled from the source code in this repository without the "enterprise" feature flag, you must comply with the AGPLv3 license terms and conditions or get a commercial license from Windmill Labs, Inc.
To use Windmill "Community Edition" as is internally in your organization, or to use its APIs as is, you do NOT need a commercial license.
Integrations
In Windmill, integrations are referred to as resources and resource types. Each Resource has a Resource Type that defines the schema that the resource needs to implement.
On self-hosted instances, you might want to import all the approved resource types from WindmillHub. A setup script will prompt you to have it being synced automatically everyday.
Environment Variables
| Environment Variable name | Default | Description | Api Server/Worker/All |
|---|---|---|---|
| DATABASE_URL | The Postgres database url. | All | |
| WORKER_GROUP | default | The worker group the worker belongs to and get its configuration pulled from | Worker |
| MODE | standalone | The mode if the binary. Possible values: standalone, worker, server, agent | All |
| METRICS_ADDR | None | (ee only) The socket addr at which to expose Prometheus metrics at the /metrics path. Set to "true" to expose it on port 8001 | All |
| JSON_FMT | false | Output the logs in json format instead of logfmt | All |
| BASE_URL | http://localhost:8000 | The base url that is exposed publicly to access your instance. Is overriden by the instance settings if any. | Server |
| ZOMBIE_JOB_TIMEOUT | 30 | The timeout after which a job is considered to be zombie if the worker did not send pings about processing the job (every server check for zombie jobs every 30s) | Server |
| RESTART_ZOMBIE_JOBS | true | If true then a zombie job is restarted (in-place with the same uuid and some logs), if false the zombie job is failed | Server |
| NATIVE_MODE | false | Enable native mode: sets NUM_WORKERS=8, rejects non-native jobs (nativets, postgresql, mysql, etc.) | Worker |
| SLEEP_QUEUE | 50 | The number of ms to sleep in between the last check for new jobs in the DB. It is multiplied by NUM_WORKERS such that in average, for one worker instance, there is one pull every SLEEP_QUEUE ms. | Worker |
| KEEP_JOB_DIR | false | Keep the job directory after the job is done. Useful for debugging. | Worker |
| EXIT_AFTER_N_JOBS | None | Exit the worker process after it has executed that many jobs, so that a supervisor restarts it and no process runs more than that many, bar the steps of a same-worker flow it has started, which it always finishes (set it to 1 for a process per job; jobs handed to a dedicated worker, and the worker's own init and periodic scripts, do not count). Not counting the init and periodic scripts means they run again on every restart: an init script's runtime is added to the latency of every batch of that many jobs, and a periodic script fires once per process start whatever its interval says. The worker's shell in the workers page also starts backed off rather than after the two minutes it otherwise takes, since a process due to be recycled cannot count on living that long: the first command of a session can wait up to 15s, later ones are immediate. For deployments that isolate executions by process lifetime rather than with nsjail; note that a container restart resets the process, not the container filesystem, so caches and /tmp survive it. The worker name is then derived from the hostname instead of being random, so the restarted worker keeps its row in the workers list (an agent worker keeps the row but restarts its job count). Use one worker per process: workers of one process share its environment, so the first to reach the limit shuts the others down too. |
Worker |
| WORKER_SUFFIX | None | Pins the last part of the worker name, which is otherwise random, so that a restarted worker keeps its row in the workers list. Only needed when several worker processes of the same worker group run on one host, since the name is derived from the hostname: give each of them a distinct value, as two processes sharing one must never happen. At most 64 letters, digits and underscores; anything else is refused at startup. | Worker |
| LICENSE_KEY (EE only) | None | License key checked at startup for the Enterprise Edition of Windmill | Worker |
| SLACK_SIGNING_SECRET | None | The signing secret of your Slack app. See Slack documentation | Server |
| COOKIE_DOMAIN | None | The domain of the cookie. If not set, the cookie will be set by the browser based on the full origin | Server |
| DENO_PATH | /usr/bin/deno | The path to the deno binary. | Worker |
| PYTHON_PATH | The path to the python binary if wanting to not have it managed by uv. | Worker | |
| GO_PATH | /usr/bin/go | The path to the go binary. | Worker |
| GOPRIVATE | The GOPRIVATE env variable to use private go modules | Worker | |
| GOPROXY | The GOPROXY env variable to use | Worker | |
| NETRC | The netrc content to use a private go registry | Worker | |
| PY_CONCURRENT_DOWNLOADS | 20 | Sets the maximum number of in-flight concurrent python downloads that windmill will perform at any given time. | Worker |
| PATH | None | The path environment variable, usually inherited | Worker |
| HOME | None | The home directory to use for Go and Bash , usually inherited | Worker |
| DATABASE_CONNECTIONS | 50 (Server)/3 (Worker) | The max number of connections in the database connection pool | All |
| SUPERADMIN_SECRET | None | A token that would let the caller act as a virtual superadmin superadmin@windmill.dev | Server |
| TIMEOUT_WAIT_RESULT | 20 | The number of seconds to wait before timeout on the 'run_wait_result' endpoint | Worker |
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
| DISABLE_RESPONSE_LOGS | false | Disable response logs | Server |
| CREATE_WORKSPACE_REQUIRE_SUPERADMIN | true | If true, only superadmins can create new workspaces | Server |
| MIN_FREE_DISK_SPACE_MB | 15000 | Minimum amount of free space on worker. Sends critical alert if worker has less free space. | Worker |
| RUN_UPDATE_CA_CERTIFICATE_AT_START | false | If true, runs CA certificate update command at startup before other initialization | All |
| RUN_UPDATE_CA_CERTIFICATE_PATH | /usr/sbin/update-ca-certificates | Path to the CA certificate update command/script to run when RUN_UPDATE_CA_CERTIFICATE_AT_START is true | All |
Run a local dev setup
We recommend using Nix. See ./frontend/README_DEV.md for all options.
Frontend only
Uses the backend of https://app.windmill.dev with local frontend (hot-reload):
cd frontend
npm install
npm run generate-backend-client # or generate-backend-client-mac on Mac
npm run dev
Windmill available at http://localhost/
Backend + Frontend
See the ./frontend/README_DEV.md file for all running options.
- Start a local Postgres database using for instance the
start-dev-db.shscript which will make a database available atpostgres://postgres:changeme@localhost:5432/windmillThen run the migrations using the following command:This will also avoid compile time issue with sqlx'scargo install sqlx-cli env DATABASE_URL=<YOUR_DATABASE_URL> sqlx migrate runquery!macro. - (optional, linux only) Install nsjail and have it accessible in your PATH
- Install bun, deno and python3 (+ any languages you want to use), have the bins at
/usr/bin/bun,/usr/bin/deno, and/usr/local/bin/python3or set the corresponding environment variables. - (optional) Install the lld linker
- Go to
frontend/:npm install,npm run generate-backend-clientthenREMOTE=http://localhost:8000 npm run dev- You might need to set some extra heap space for the node runtime
export NODE_OPTIONS="--max-old-space-size=4096" - Create an empty
frontend/buildfolder usingmkdir frontend/build
- Go to
backend/:env DATABASE_URL=<YOUR_DATABASE_URL> RUST_LOG=info cargo run- You can specify any feature flag you want to enable, for example
cargo run --features pythonto enable the python executor.
- Windmill should be available at
http://localhost:3000
Contributing
At this time, we are not seeking outside contribution. Bug reports and feature requests remain very welcome, and small, trivially-verified PRs that fix a problem are still accepted. See CONTRIBUTING.md for the full policy.
Contributors
Copyright
© 2023-2026 Windmill Labs, Inc.






