Skip to main content

Deployment

Full-code apps can be deployed via the CLI, the UI editor or the API. This page covers deployment options and configuration.

Deploy via CLI​

Push your app to Windmill:

wmill sync push

The CLI reads raw_app.yaml, bundles your frontend code, uploads backend runnables and deploys the app. See CLI workflow for the full push flow.

Deploy via UI​

From the in-browser editor, click the deploy button to save and publish your app.

Deploy from sources via the API​

A deployed full-code app is served as a compiled bundle, which is why the CLI and the editor both build before deploying. An API client has no bundler of its own, so two endpoints take the app's sources as JSON and compile them on a worker instead:

Endpoint
POST /w/{workspace}/apps/create_raw_sourceCreate an app from its sources. Requires path, value, summary and policy, and returns 201
POST /w/{workspace}/apps/update_raw_source/{path}Deploy a new version of an existing full-code app
curl -X POST "$WM_BASE_URL/api/w/$WORKSPACE/apps/update_raw_source/f/folder/my_app" \
-H "Authorization: Bearer $WM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"value": {
"files": {
"/index.tsx": "import { createRoot } from \"react-dom/client\" ...",
"/App.tsx": "export default function App() { ... }",
"/package.json": "{ \"dependencies\": { \"react\": \"^19.0.0\" } }"
},
"runnables": {},
"data": {}
},
"deployment_message": "deployed from the API"
}'

files maps each source path to its content and must contain an entry point (/index.tsx, /index.ts or /index.js). On update, send the whole value, not a patch: read the current one with GET /w/{workspace}/apps/get/p/{path} and edit it. runnables and data are carried through unchanged, and /ui/ imports of the workspace shared UI folder resolve as they do locally.

The compile runs as an ordinary job on a worker, using the same build as wmill app bundle and the editor. It shows up in the runs list as bundle raw app with the build logs, and the app's dependencies are installed but never executed. Because the build happens before the version is written, a build failure deploys nothing and returns the compiler's own error:

Bad request: raw app bundling failed (job 019fcc07-...): Could not resolve: "./does-not-exist"
App.tsx:1:22 import { nope } from "./does-not-exist"

Deploying sources over a low-code app is refused unless allow_kind_change: true is set, so an app is never converted by accident. Operators cannot deploy apps, the workspace protection rulesets apply as for any other deploy, and a token needs both the apps:write and jobs:run scopes since the call makes a worker build the sources it was given.

Both endpoints are exposed over MCP as the createApp and updateApp tools, so an AI agent can read a full-code app, edit its sources and deploy them, getting compile errors back as the error of the call. For the same reason, an MCP token has to name those two tools rather than expose them through a blanket scope.

Public and guest apps​

raw_app.yaml carries the access mode as a tri-state: public: true for anonymous access, guests: true for guest access, and neither for the default, members only.

summary: "Public dashboard"
public: true

Public apps are accessible at their app URL without requiring a Windmill login.

summary: "Partner dashboard"
guests: true

Guest apps require an identity, through a sign-in with the identity provider or a JWT your own backend signs, but no Windmill account and no workspace membership. Pushing guests: true does not by itself let anyone in: the workspace and instance guest switches are checked on every guest request.

The rest of the execution policy is auto-generated at deployment time.

Custom paths​

Admins can set a custom URL path for an app:

custom_path: "my-dashboard"

The app will then be accessible at https://<instance>/a/my-dashboard (on multi-workspace deployments such as the cloud, the workspace is part of the path: https://<instance>/a/<workspace>/my-dashboard).

Execution policy​

The execution policy is auto-generated at deployment time. It controls how backend runnables are executed:

ModeDescription
publisherRequires login and read access on the app. Runnables execute as the app publisher (the user who deployed the app), so users don't need direct permissions on the underlying scripts. This is the default.
guestAn identity required, workspace membership not. Anyone with no Windmill account whom the identity provider authenticates, or a guest JWT names, can open the app, and runnables execute as the app publisher. Only honored where the workspace and the instance admit guests.
anonymousNo login required. Runnables still execute as the app publisher; unauthenticated callers appear as anonymous. This is the mode used by public apps.
viewerRequires login. Runnables execute with the permissions of the viewer themselves, for per-user access control.

The triggerables and triggerables_v2 fields map each runnable to its execution configuration and are populated automatically during push/deploy.

Sandbox isolation​

The execution policy decides who the backend runnables run as. Sandbox isolation (alpha) is the separate, browser-side question: with it on, the app's bundle runs in an opaque-origin iframe and cannot reach the viewer's Windmill session. It is turned on from the app editor's Deploy panel and stored in the app's policy, which wmill sync pull writes into raw_app.yaml and push sends back. Unlike the rest of the policy it is not regenerated at deploy time, so a raw_app.yaml written by hand without that policy block turns isolation back off on the next push.

Versioning​

Each deployment creates a new version of the app. The version counter increments on every update. Previous versions are retained and can be accessed through the API.

CI/CD integration​

Full-code apps work with Windmill's git sync and deploy to prod workflows:

  1. Develop locally and commit your .raw_app/ (or __raw_app/) directory to git
  2. Use wmill sync push in CI to deploy to a staging workspace
  3. Promote to production using workspace-to-workspace deployment