> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloudflare Quickstart

> Run Mirage workspaces and the Mirage server API on Cloudflare Workers with @struktoai/mirage-cloudflare.

`@struktoai/mirage-cloudflare` runs Mirage on Cloudflare Workers. A workspace runs in the Worker's own isolate: the shell, the commands built on Rust, and `python3` on [Monty](https://docs.mirage.strukto.ai/typescript/runtime/python.md), all in one thread.

The Worker needs `nodejs_compat` for Node's `buffer`, `crypto`, `path`, `process` and `util` modules. Wrangler loads Mirage's Rust module and Monty as compiled `WebAssembly.Module` imports.

## Install

```bash theme={null}
npm install @struktoai/mirage-cloudflare
npm install -D wrangler
```

```toml wrangler.toml theme={null}
name = "mirage"
main = "src/worker.ts"
compatibility_date = "2026-10-01"
compatibility_flags = ["nodejs_compat", "no_handle_cross_request_promise_resolution"]
```

## Run a workspace

```ts src/worker.ts theme={null}
import { MountMode, RAMVFS, Workspace } from '@struktoai/mirage-cloudflare'

export default {
  async fetch(request: Request): Promise<Response> {
    const { command } = (await request.json()) as { command: string }
    const ws = new Workspace(
      { '/data': new RAMVFS() },
      { mode: MountMode.EXEC, runtimes: ['workspace', 'monty'], maxMemory: '32MB' },
    )
    try {
      const result = await ws.shell(command)
      return Response.json({ exit: result.exitCode, stdout: result.stdoutText })
    } finally {
      await ws.close()
    }
  },
}
```

```bash theme={null}
npx wrangler dev
curl -s localhost:8787 -d '{"command": "seq 1 100 | sort -rn | head -3"}'
```

`python3` runs on Monty in the isolate's own thread, where no timer can interrupt it, so Monty's own clock stops a run: at the line's time limit, which ends the line with exit 124, or after 30 seconds with `TimeoutError` if that comes first. A loop that never ends leaves the isolate free for the next line.

For anything else from core, import its subpaths (`@struktoai/mirage-core/vfs/registry`), not its package root: the root also imports optional packages, such as Pyodide, that a Worker bundle cannot resolve.

## Serve the server API

`createApp` answers the [HTTP](https://docs.mirage.strukto.ai/home/access/http.md) routes, [MCP](https://docs.mirage.strukto.ai/home/access/mcp.md) and [RPC](https://docs.mirage.strukto.ai/home/access/rpc.md) as the [server](https://docs.mirage.strukto.ai/typescript/access/server.md) does, over the workspaces the isolate holds:

```ts src/worker.ts theme={null}
import { createApp } from '@struktoai/mirage-cloudflare'

export default createApp({ maxWorkspaces: 8 })
```

A workspace outlives the request that created it, so a later request finishes work an earlier one started; `no_handle_cross_request_promise_resolution` above lets it.

It checks each request the way the server does, from the same `MIRAGE_AUTH_MODE`, `MIRAGE_AUTH_TOKEN` and `MIRAGE_JWT_*` [variables](https://docs.mirage.strukto.ai/home/access/http.md#configure-authentication). Without a token or a JWT issuer it serves nothing, since a Worker's URL is public.

```toml wrangler.toml theme={null}
[vars]
MIRAGE_AUTH_MODE = "token"
```

```bash theme={null}
npx wrangler secret put MIRAGE_AUTH_TOKEN
curl -s localhost:8787/v1/workspaces -H "Authorization: Bearer $MIRAGE_AUTH_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"id": "demo", "config": {"mounts": {"/": {"vfs": "ram"}}, "runtimes": ["workspace", "monty"]}}'
curl -s localhost:8787/v1/workspaces/demo/shell -H "Authorization: Bearer $MIRAGE_AUTH_TOKEN" \
  -H 'content-type: application/json' -d '{"command": "python3 -c \"print(6 * 7)\""}'
```

| Option | Meaning |
| - | - |
| `auth` | An `AuthConfig` in place of the env variables: `{ mode: 'token', bearerToken }`, or `{ mode: 'jwt', jwt }`. |
| `maxWorkspaces` | The most workspaces the isolate holds at once. A create past it answers 503. |
| `maxSessions` | The most sessions each workspace holds. A new session past it answers 429. |
| `policies` | The server's own [policies](https://docs.mirage.strukto.ai/home/access/http.md#server-policies). |
| `snapshotStore` | An `S3Config`: the store a snapshot request may name a key in. |

`/v1/workspaces/:id/codex-exec` serves Codex's exec-server over a WebSocket, as the server does, so `codex exec-server forward` runs its tools in a workspace on the Worker.

## Keep a workspace's files

A Worker's memory goes with its isolate. To keep a workspace's files and sessions, put them in SQL: [`SqlVFS`](https://docs.mirage.strukto.ai/typescript/setup/sql) over a Durable Object's own storage or a D1 database, and `SqlWorkspaceStateStore` over the same driver for each session's working directory, variables and functions. One database holds one filesystem, so a Durable Object per workspace gives each workspace its own.

```ts theme={null}
import { DurableObjectSqlDriver, SqlVFS, SqlWorkspaceStateStore, Workspace } from '@struktoai/mirage-cloudflare'

const driver = new DurableObjectSqlDriver(this.ctx.storage)
const ws = new Workspace(
  { '/work': new SqlVFS(driver) },
  { workspaceId: 'w1', store: new SqlWorkspaceStateStore(driver), ownsStore: true },
)
```

## Mounts and CLIs

Mounts:

* `ram`, `github`, `gdrive`, `gdocs`, `gsheets`, `gslides`, `gcal`, `gmail`, `onedrive`, `sharepoint`, `box`, `airtable`, `linear`, `trello`, `langfuse`, `wandb`, `mem0`, `dify`, `slack`, `discord`, `notion`, `dropbox`, `jaeger`, `databricks_volume`, `tidb_filesystem`, `hf_models`, `hf_datasets`, `hf_spaces`
* `s3`, `r2`, `gcs`, `minio`, `ceph`, `seaweedfs`, `wasabi`, `backblaze`, `digitalocean`, `tencent`, `aliyun`, `scaleway`, `qingstor`, `oci`, `supabase`, without `proxy`
* `qdrant`: install `@qdrant/js-client-rest`
* `postgres`: install `pg`
* `redis`: any Redis over TCP, Upstash included, at a `redis://` or `rediss://` url, through Cloudflare's `connect()`
* `email`: install `imapflow`, `mailparser` and `nodemailer`

`chroma` does not run on Cloudflare.

CLIs: `airtable`, `discord`, `gh`, `git`, `gws`, `hf`, `himalaya`, `linear`, `mcp`, `ntn`, `search`, `slack`, `trello`.

## Limits

* A Worker's isolate has 128 MB of memory and no disk. Set `max_memory` on each workspace so a large write fails with `Disk quota exceeded` before the isolate runs out.
* The server API's workspaces live in the isolate's memory. A request routed to another isolate does not see them, and they are gone when the isolate is evicted. `store.type` must be `ram`. A workspace built in code keeps its files and sessions in SQL, as [above](#keep-a-workspaces-files).
* Network rules judge each request by host name: the isolate never sees the address a name reaches, so a `denied_domains` entry naming an address or a range is refused, and jwt mode's block on cloud metadata addresses covers only a request made to the address itself.
* SSH, FUSE, policy and script files read by path, and runtimes or CLIs loaded by a `source:Class` reference are the Node server's alone. Codex's exec-server runs here over its WebSocket.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.