# Collaboration and deployment stages

> How do I set up collaboration and deployment in Windmill? A staged progression from shared workspace to forks and multi-workspace promotion.

This page describes how to set up collaboration and deployment in Windmill as a progression of four stages, from simplest to most advanced. Each stage is additive: it builds on the previous one and adds one capability. Most teams settle at stage 2 or stage 3 — stage 4 is only needed when you need a separate production environment with a promotion workflow.

| Stage | What you add | Git required | Forks | Workspaces |
|-------|--------------|--------------|-------|------------|
| [Stage 1](#stage-1--shared-workspace) | Shared workspace, `u/` → `f/` | No | No | 1 |
| [Stage 2](#stage-2--add-forks-and-the-merge-ui) | Forks, merge UI, protection rulesets | No | Yes | 1 |
| [Stage 3](#stage-3--add-git-sync) | Bi-directional git sync, local development | Yes | Yes | 1 |
| [Stage 4](#stage-4--add-multi-workspace-promotion) | Multi-workspace (dev/staging/prod) | Yes | Yes | 2+ |

:::info Edition requirements

- **Stage 1** works on any edition.
- **Stage 2** relies on [workspace forks](../20_workspace_forks/index.mdx) and [protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx). Forks are available on all self-hosted editions but count toward the global 3-workspace limit on Community Edition. On [Windmill Cloud](https://app.windmill.dev/), forks require a [paid plan](../20_workspace_forks/index.mdx#forks-on-windmill-cloud).
- **Stage 3** adds [git sync](../11_git_sync/index.mdx), available on [Cloud and Enterprise Self-Hosted](/pricing), and on Community Edition for workspaces with **up to 2 users**.
- **Stage 4** requires git sync and is [Cloud or Enterprise Self-Hosted](/pricing) only.

:::

## Stage 1 — Shared workspace

Everyone on the team works in the same workspace. Developers iterate in their own [user space](../../core_concepts/16_roles_and_permissions/index.mdx#path) (`u/<user>/`) and, once an item is ready to be shared, move it to a shared [folder](../../core_concepts/8_groups_and_folders/index.mdx) (`f/<folder>/`). This is the simplest possible setup and the right starting point for almost every team.

### Architecture

- One workspace (for example `main`)
- Developers own `u/<username>/` and iterate there
- Shared items live under `f/<folder>/`, with folder-level [permissions](../../core_concepts/16_roles_and_permissions/index.mdx) granting write access to the relevant group
- No git, no forks, no protection rulesets

### Setup

1. Create a workspace and invite your team.
2. Create one or more [folders](../../core_concepts/8_groups_and_folders/index.mdx#folders) that represent your projects, and assign write permission to the groups that should own each project.
3. That's it — developers can start building.

### Developer workflow

1. A developer creates a script, flow or app under `u/<their-name>/`. Items in user space are private to them by default.
2. They iterate freely, test, and share drafts with teammates via the [draft system](../../core_concepts/0_draft_and_deploy/index.mdx).
3. When the item is ready, they move it from `u/<user>/` to the appropriate `f/<folder>/`. Moving an item into a folder transfers ownership to that folder's permissions.
4. Other members of the folder's group can now edit, run, and depend on the item.

### When to move to stage 2

- Someone broke an item in the shared workspace and you wish it had been isolated.
- You want a review gate before changes reach the shared workspace.
- Developers want sandboxes to test risky changes without affecting anyone else.

## Stage 2 — Add forks and the merge UI

Stop editing the shared workspace directly. Instead, developers create a [workspace fork](../20_workspace_forks/index.mdx) for each change, iterate in the fork, and merge back via the [merge UI](../20_workspace_forks/index.mdx#merge-workspaces-from-the-ui-merge-ui). [Protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx) enforce this: direct deploys to the shared workspace are blocked except for a small `wm_deployer` group.

This is still a pure UI workflow — no git, no CI/CD, no external tools. Stage 3 will add git sync on top, and forks integrate seamlessly with it when you get there.

:::note Forks are optional
Forks are not a prerequisite for stage 3. Git sync works on a plain shared workspace, so if you already want git history and local development, you can skip this stage and go straight to [stage 3](#stage-3--add-git-sync). Adopt forks later if you need isolated sandboxes and a review gate.
:::

### Architecture

- One shared workspace (same as stage 1), now called `prod` or `main`
- [Protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx) block direct deploys; a `wm_deployer` group has bypass permissions
- Developers create [workspace forks](../20_workspace_forks/index.mdx) to develop features
- Changes merge back through the [merge UI](../20_workspace_forks/index.mdx#merge-workspaces-from-the-ui-merge-ui)

![UI only forks](./workspace_forks_ui_only.png)

### Setup

#### 1. Create the `wm_deployer` group

1. Open the workspace settings and go to **Groups**.
2. Create a group called `wm_deployer` and add the users who are allowed to deploy directly or approve merges.

#### 2. Enable protection rulesets

1. Go to **Workspace settings** → **Protection Rulesets**.
2. Add a rule that enables **Disable direct deployment**.
3. Add the `wm_deployer` group to the bypass list.

This blocks everyone except the deployer group from modifying `f/` items directly. Developers must now use forks.

### Developer workflow

1. A developer creates a [workspace fork](../20_workspace_forks/index.mdx#fork-creation) from the shared workspace via the workspace menu.
2. They iterate, test, and deploy freely *inside* the fork — triggers are not copied over to avoid unwanted executions.
3. When ready, they open the fork home page, click **Review & Deploy Changes**, and review the diff in the [merge UI](../20_workspace_forks/index.mdx#merge-workspaces-from-the-ui-merge-ui).
4. A member of `wm_deployer` approves the merge. The changes land in the shared workspace.

### When to move to stage 3

- You want a history of changes outside Windmill (audit, backup, rollback).
- You want to edit scripts and flows locally in your IDE or with [agentic coding tools](../../misc/9_guides/local_dev_with_ai/index.mdx) like Claude Code.
- You want fork branches mirrored in git so PRs can be reviewed there too.
- You're planning to add a separate production environment (stage 4), which requires git sync.

## Stage 3 — Add git sync

Connect the workspace from stages 1 and 2 to a git repository using [git sync](../11_git_sync/index.mdx). Every deploy is committed to git, and changes pushed to the repository are synced back to Windmill via CI/CD. This gives you a full audit trail, external version control, and [local development](../4_local_development/index.mdx) — and each fork from stage 2 now automatically gets its own git branch, so the merge UI and git PRs stay in lockstep.

Git sync is available on [Cloud and Enterprise Self-Hosted](/pricing), and on Community Edition for workspaces with up to 2 users.

### Architecture

- The shared workspace and its forks from stage 2, unchanged
- A git repository with a single branch (for example `main`)
- [Git sync](../11_git_sync/index.mdx) configured on the workspace: each deploy pushes a commit
- A CI/CD workflow that syncs commits back to the workspace, enabling bi-directional sync
- A `wmill.yaml` file in the repository with a single entry in the [`workspaces:`](../3_cli/environment-specific-items.mdx) key
- Fork branches auto-created as `wm-fork/<parent-branch>/<fork-name>`

![Workspace Forks + git sync](./workspace_forks.png)

### Setup

#### 1. Create the git repository

Create an empty repository on GitHub, GitLab or similar with a single `main` branch.

#### 2. Configure git sync on the workspace

1. Go to **Workspace settings** → **Git Sync**.
2. Click **+ Add connection** and create a [`git_repository`](../../integrations/git_repository.mdx) resource pointing at your repository and the `main` branch.
3. Save. Use **Initialize Git repository** to populate the repository with the current workspace contents and a default `wmill.yaml`.

See the full walkthrough in [git sync setup](../11_git_sync/index.mdx#setup---git-sync-from-windmill).

#### 3. Add the CI/CD workflows

Git sync only pushes *from* Windmill *to* git by default. To make changes in git flow back into the workspace, add GitHub Actions workflows (or equivalent). A working reference is the [windmill-sync-example](https://github.com/windmill-labs/windmill-sync-example) repository. You will want:

- [`push-on-merge.yaml`](../11_git_sync/index.mdx#setup---cicd-from-git-repository) — runs `wmill sync push` when commits land on `main`.
- [`push-on-merge-to-forks.yaml`](../11_git_sync/index.mdx#setup---cicd-from-git-repository) and [`open-pr-on-fork-commit.yaml`](../11_git_sync/index.mdx#setup---cicd-from-git-repository) — keep fork branches in sync and open PRs automatically.

Without the fork workflows, forks still work exactly as in stage 2 — their git branches just won't receive external edits.

You will need:

- A [Windmill user token](../../core_concepts/4_webhooks/index.mdx#user-token) saved as a `WMILL_TOKEN` GitHub secret.
- The `WMILL_WORKSPACE` and `WMILL_URL` variables set in the workflows.

Full details in [git sync CI/CD setup](../11_git_sync/index.mdx#setup---cicd-from-git-repository).

#### 4. Minimal `wmill.yaml`

A single-workspace `wmill.yaml` looks like this:

```yaml
defaultTs: bun
includes:
  - f/**
excludes: []
skipVariables: true
skipResources: true
skipSecrets: true

workspaces:
  main:
    baseUrl: https://app.windmill.dev
    gitBranch: main
```

See [workspace-specific items](../3_cli/environment-specific-items.mdx) for the full schema.

### Developer workflow

1. Developers continue to use forks from stage 2. When they create a fork, a matching `wm-fork/main/<name>` git branch is created automatically.
2. They can now also edit the fork locally: [work on the workspace outside of Windmill](../4_local_development/index.mdx) with `wmill sync pull`, edit in their IDE, the [VS Code extension](../../cli_local_dev/1_vscode-extension/index.mdx), or an [agentic coding tool](../../misc/9_guides/local_dev_with_ai/index.mdx) like Claude Code, then `wmill sync push` back.
3. Every deploy creates a commit in the git repository.
4. Merging a fork via the merge UI *or* via a normal git PR both work — pick whichever your team prefers for review.
5. External commits (from local development or PR merges) are replayed into the workspace by the CI/CD workflow.

### When to move to stage 4

- You need a separate production environment that is fully isolated from day-to-day development.
- You want PR-based review on the *promotion* step, not just the merge-to-shared step.
- You're deploying across multiple Windmill instances.

## Stage 4 — Add multi-workspace promotion

Introduce one or more additional workspaces (for example `staging` and `prod`), each connected to its own git branch. Day-to-day development happens on `staging` using forks (exactly as in stage 3). When changes are ready for production, they are promoted from `staging` to `prod` through [git promotion](../9_deploy_gh_gl/index.mdx), which opens a PR on the `prod` branch.

The `prod` workspace is never edited directly. Its only source of change is a merged PR on the `prod` branch.

### Architecture

- Two (or more) workspaces, each on its own git branch
- Each workspace has its own `git_repository` resource and its own entry under `workspaces:` in `wmill.yaml`
- [Git Promotion](../11_git_sync/index.mdx#git-promotion-workflow-cross-instance-deployment-using-a-git-workflow) is configured from `staging` to `prod`
- Developers edit `staging` via [forks](../20_workspace_forks/index.mdx) (stage 2 pattern)
- `prod` is protected by [protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx) and only the promotion CI/CD can deploy to it

| Workspace | Git branch | Promotes to |
|-----------|------------|-------------|
| staging   | staging    | prod        |
| prod      | prod       | —           |

![Git Promotion](./git_promotion.png 'Edits to staging use the stage 2 fork workflow')

### Setup

#### 1. Create the branches and workspaces

1. In your git repository, create a `staging` branch and a `prod` branch.
2. Create a `staging` workspace and a `prod` workspace in Windmill.

#### 2. Configure git sync on each workspace

For each workspace, go to **Workspace settings** → **Git Sync** and create a `git_repository` resource pointing at the corresponding branch:

- `staging` workspace → `git_repository` on `staging` branch
- `prod` workspace → `git_repository` on `prod` branch

#### 3. Configure the promotion target

On the `staging` workspace:

1. Under the git sync connection, click **Add promotion target**.
2. Create a **separate** `git_repository` resource targeting the `prod` branch (this is not the same resource as the one used for git sync).
3. Save.

:::caution Two resources are required

The git sync resource points at the workspace's *own* branch (`staging`). The promotion target points at the *target* branch (`prod`). They must be different `git_repository` resources, even if they point at the same repository.

:::

#### 4. Add the CI/CD workflows

- `push-on-merge.yaml` — one copy per workspace (`WMILL_WORKSPACE=staging`, `WMILL_WORKSPACE=prod`), triggered by merges to `staging` and `prod` respectively.
- `open-pr-on-promotion-commit.yaml` — opens a PR on `prod` when staging promotes a change.
- `push-on-merge-to-forks.yaml` and `open-pr-on-fork-commit.yaml` — unchanged from stage 3, now scoped to the `staging` workspace.

See [git sync CI/CD setup](../11_git_sync/index.mdx#setup---cicd-from-git-repository) for the workflow files.

#### 5. Multi-workspace `wmill.yaml`

```yaml
defaultTs: bun
includes:
  - f/**
excludes: []

workspaces:
  staging:
    baseUrl: https://app.windmill.dev
    gitBranch: staging
    specificItems:
      resources:
        - "f/config/**"
      settings: true

  prod:
    baseUrl: https://app.windmill.dev
    gitBranch: prod
    specificItems:
      resources:
        - "f/config/**"
      settings: true
```

Each workspace declares its own `gitBranch`. Items listed under `specificItems` (for example, environment-specific database credentials) are stored per workspace on disk. See [workspace-specific items](../3_cli/environment-specific-items.mdx) for the full schema.

:::info Per-folder ownership defaults on deploy

By default, a newly deployed script, flow, app, schedule or trigger runs as whichever user pushed it. For CLI / CI/CD deploys that's the identity behind `WMILL_TOKEN` — typically a workspace admin or a dedicated deploy user that you've added to `wm_deployers`. If you want items under a folder to run as a specific service account instead — and to differ per environment — set `default_permissioned_as` rules on the folder in the target workspace:

```yaml
# f/customer_x/folder.meta.yaml — committed per workspace via specificItems
summary: ''
display_name: customer_x
owners: []
extra_perms: {}
default_permissioned_as:
  - path_glob: 'jobs/**'
    permissioned_as: u/customer_x_svc    # or g/customer_x or an email
  - path_glob: '**'
    permissioned_as: u/ops
```

Rules are ordered; the first `path_glob` (relative to the folder root) that matches an item wins. Applied only at **create time** and only when the caller is admin or a member of `wm_deployers` — existing items are untouched, and non-deployer users still deploy as themselves. Set `folders: ["f/**/folder.meta.yaml"]` under `specificItems` in `wmill.yaml` if you want different rules per workspace on the same path.

Edit the rules in the folder settings UI, or commit them directly in `folder.meta.yaml`. The CLI also exposes per-item overrides for already-deployed items via `wmill script|flow|app|schedule|trigger set-permissioned-as <path> <email>`.

Folder defaults are advisory: they only fill in the default when the pushed item does not already carry an explicit `on_behalf_of`. To opt in to CLI preview of which items will be affected by rules on the next push, set `syncBehavior: v1` in `wmill.yaml`.

:::

#### 6. Protect `prod`

Enable [protection rulesets](../../core_concepts/56_protection_rulesets/index.mdx) on the `prod` workspace with no bypass group — the only way in is the promotion PR.

### Developer workflow

1. A developer needs to change something. They [fork](../20_workspace_forks/index.mdx) the **staging** workspace (not prod).
2. They iterate in the fork and merge back to `staging` via the merge UI. This is the stage 2 workflow plus the git sync integration from stage 3.
3. When the change needs to go live, they trigger a [git promotion](../9_deploy_gh_gl/index.mdx) from `staging` to `prod`.
4. A PR is opened on the `prod` branch. The team reviews it in their git platform.
5. Merging the PR triggers the `push-on-merge.yaml` workflow, which syncs `prod` to the new state.

### Advantages

- Fully isolated production environment with a git PR as the only gate.
- Dev/staging/prod across multiple Windmill instances if needed.
- Per-workspace configuration via [workspace-specific items](../3_cli/environment-specific-items.mdx).
- All the benefits of stages 1–3 still apply.

## Choosing a stage

Most teams should start at **stage 1** and only move up when they feel the pain the next stage solves. Stage 4 is real operational overhead and should not be the default — the majority of teams are well served by stages 2 or 3.

- **Stage 1** if you have a small team, no compliance requirements, and a single environment is fine.
- **Stage 2** if you want enforced review on every change and isolated sandboxes, but don't need external version control.
- **Stage 3** if you want git history, backup, local development, and the option of git PR-based review on top of stage 2.
- **Stage 4** if you need a separate production environment with a promotion gate, or you deploy across multiple Windmill instances.

Stages 1 → 2 → 3 → 4 are designed to be adopted in order, but you can skip stages: a small team on Community Edition can go stage 1 → stage 3 (skip forks) to get git sync and local development without needing fork-based review.

## Related documentation
