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 apps

Make your app accessible without authentication by setting public: true in raw_app.yaml:

summary: "Public dashboard"
public: true

Public apps are accessible at their app URL without requiring a Windmill login. The execution policy is auto-generated at deployment time. See public apps for more details.

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.
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.

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