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

# Workspace

> Configure a workspace with workspace.yaml.

A workspace config describes one virtual terminal. Python and TypeScript use
`snake_case` YAML keys. TypeScript constructor options use camelCase, such as
`commandLimits`; provider and runtime guides show their SDK equivalents.

## Start with RAM

`mounts` is the only required top-level key. This config creates a writable,
in-memory mount without credentials:

```yaml workspace.yaml theme={null}
mounts:
  /data:
    vfs: ram
```

Create a workspace, check it, and remove it:

```bash theme={null}
mirage workspace create workspace.yaml --id config-demo &&
mirage shell -w config-demo -c 'echo ready > /data/status && cat /data/status' &&
mirage workspace delete config-demo
```

The shell command prints `ready`. Replace RAM using a [VFS setup guide](https://docs.mirage.strukto.ai/home/setup/vfs.md).
YAML defaults `mode` to `write`; direct Python and TypeScript `Workspace`
constructors default to `read`.

### From a template

A config saved on the server as a template creates workspaces without the
file. The template's `config` is the same document as `workspace.yaml`:

```bash theme={null}
mirage api /v1/templates --name ram-only --config '{"mounts": {"/data": {"vfs": "ram"}}}'
mirage workspace create --template ram-only -w config-demo
```

Each workspace gets a copy of the config, so editing the template later does
not change it. See [From a template](https://docs.mirage.strukto.ai/home/access/cli.md#from-a-template).

## Workspace settings

| Key | Purpose |
| - | - |
| `mounts` | Map virtual prefixes to VFS configurations, described below. |
| `mode` | Default mount access: `read`, `write` or `exec`. |
| `read`, `write` | Defaults for every mount, described in [read and write behavior](https://docs.mirage.strukto.ai/home/setup/vfs.md#read-and-write-behavior). Reads default to `bounded` with a 600-second bound; writes to `unconditional`. |
| `clis` | Install [virtual CLIs](https://docs.mirage.strukto.ai/home/setup/clis.md) by command name, described [below](#cli-entries). |
| `route_policy` | Choose [machines](https://docs.mirage.strukto.ai/home/policy/route-machine.md) with a routing policy. |
| `profiles`, `profile` | Define [session profiles](https://docs.mirage.strukto.ai/home/configuration/session.md) and select the default. |
| `processes` | Whose processes a session can list and stop, and how many it can run at once: 128 by default. See [profiles](https://docs.mirage.strukto.ai/home/configuration/session.md). |
| `command_limits` | Set [output and execution limits](https://docs.mirage.strukto.ai/home/core/output-limits.md). |
| `max_body_bytes` | Largest API reply Mirage reads whole, as a [size](#sizes): `64MB` by default, also when `null`. See [limits](https://docs.mirage.strukto.ai/home/limits.md). |
| `max_memory` | Cap on what the workspace holds in memory, as a [size](#sizes): `96MB` by default, `null` for no cap. See [memory](https://docs.mirage.strukto.ai/home/core/output-limits.md#limit-a-workspaces-memory). |
| `max_env` | Cap on each session's variables, functions and aliases, as a [size](#sizes): `2MB` by default, `null` for no cap. See [limits](https://docs.mirage.strukto.ai/home/limits.md). |
| `network` | Choose where the workspace's connections may go, described [below](#network). |
| `system_time` | Start the workspace's clock at a set time, described [below](#system-time). |
| `env` | Environment variables each session starts with, literal or fetched from a credential source. See [Service Credentials](https://docs.mirage.strukto.ai/home/service-credentials.md#set-environment-variables). |
| `secrets` | Declare named [credential sources](https://docs.mirage.strukto.ai/home/service-credentials.md). |
| `machines` | Name the [machines](https://docs.mirage.strukto.ai/home/setup/machine/overview.md) programs run on, in capture order: `in_process` with a [runtime](https://docs.mirage.strukto.ai/home/setup/runtime/overview.md), `host`, or a provider you reach. |
| `cache`, `index` | Configure cached file content and directory/stat metadata, described [below](#workspace-state-and-caches). |
| `store` | Configure workspace state, background jobs included, described [below](#workspace-state-and-caches). |
| `io` | Set byte-queue capacity with `buffer_bytes`. |
| `default_session_id`, `workspace_id` | Set explicit identifiers instead of generated ones. |

Unknown keys are errors. Provider fields belong under `mounts.<prefix>.config`,
CLI fields under `clis.<name>.config`, and a machine's fields beside its `provider` under `machines.<name>`.

## Mount settings

Each mount key is an absolute virtual prefix, such as `/data`.

| Key | Purpose |
| - | - |
| `vfs` | Required registered VFS name, such as `ram` or `s3`. |
| `config` | Provider-specific fields from its [setup guide](https://docs.mirage.strukto.ai/home/setup/vfs.md), including [`capacity`](https://docs.mirage.strukto.ai/home/setup/vfs#mount-capacity) on RAM, disk, Redis, OPFS and SQL. |
| `mode` | Override the workspace's access mode. |
| `read`, `ttl` | Override the read policy. An explicit `read: bounded` requires `ttl` in positive whole seconds. |
| `write` | Override the write policy with one the backend supports. |
| `command_limits` | Override limits for commands using this mount. |
| `index` | Replace the workspace's index configuration for this mount. |
| `backend`, `mountpoint` | Expose a local folder using [FUSE or FSKit](https://docs.mirage.strukto.ai/home/setup/fuse.md), or `nfs`. The default backend is `workspace`. |

`backend` takes one of these values:

| Value | Exposes the mount |
| - | - |
| `workspace` | Inside Mirage only, through the shell and file APIs. |
| `fuse` | As a local folder through FUSE. |
| `fskit` | As a local folder through FSKit, on macOS 15.4 or later. |
| `nfs` | As a local folder through the operating system's NFS client. Mirage serves NFSv3 on a local port and runs the system `mount`, so nothing extra is installed. Linux and macOS; Linux needs root. Read-only. A VFS that cannot size a file without reading it still mounts, with a warning, but those files read as empty; use `fuse` for it. |

## Network

`network` decides where the workspace can connect. It covers every
connection a line or a file call opens, from a command, a CLI, a runtime or
a mount. It does not cover a program on a
[machine](https://docs.mirage.strukto.ai/home/setup/machine/overview.md): that program connects from the
machine under the machine's own network settings, and `network`,
`pre_network` and `routes` do not apply to it.

| Value | Behavior |
| - | - |
| `true` | Anywhere. The default. |
| `false` | Nowhere: every connection is refused. |
| `allowed_domains: [...]` | Only what the list names. |
| `denied_domains: [...]` | Anywhere but what the list names. |

```yaml theme={null}
network:
  allowed_domains:
    - api.github.com:443
    - "*.slack.com"
    - 10.0.0.0/8
  denied_domains:
    - "*:22"
```

Each list entry is one of:

* `*`, for every host.
* A host name, such as `api.github.com`.
* `*.` and a name, such as `*.slack.com`, for every host under that name.
* An address, or a range in CIDR form, such as `10.0.0.0/8`.

Any entry except a range can end in `:port` to match only that port. An
IPv6 address puts its port after brackets: `[::1]:8080`.

A name matches the host the connection was opened for. An address or a
range matches the address the connection reaches. A denied entry wins over
an allowed one, and an empty `allowed_domains` lets nothing through.

Write `false`, not `off`: only some YAML readers take `off` as false.

Related settings:

* Mounts connect under the same rules, so the lists must let each mount's
  endpoint through.
* A [profile](https://docs.mirage.strukto.ai/home/configuration/session.md) can hold its sessions to a narrower `network`.
* `routes` sends a connection for one `host:port` to another. See
  [Route Network](https://docs.mirage.strukto.ai/home/policy/route-network.md).
* Policies can also check each connection the lists let through, and cap how
  much it carries, at
  [`pre_network`](https://docs.mirage.strukto.ai/home/policy/policies#pre_network-and-post_network).

### When a connection is refused

A refused connection fails the way a closed port does: `curl` exits 7 with
`Could not connect to server`.

### In a browser or on an edge runtime

`fetch` and `WebSocket` are checked by host name only. Mirage never sees the
address a name resolves to, so a workspace whose `denied_domains` names an
address or a range is refused.

On an edge runtime, a limited workspace checks each redirect under the same
rules. In a browser, it follows no redirects, because a page cannot see
where a redirect goes.

### Host machines

A [`host`](https://docs.mirage.strukto.ai/home/setup/machine/host) machine runs real programs on the host,
the computer or server where Mirage runs, sandboxed or not. Those programs
open their own connections, so Mirage cannot see or block them. While
`network` limits anything, or a policy checks connections, a host machine
refuses to run them: `Permission denied`, exit 126.

## System time

`system_time` sets the time the workspace starts at, as an ISO 8601 time
with a zone offset, from 1970 through 2261. From there it moves forward in
real time:

```yaml theme={null}
system_time: 2026-03-02T09:00:00Z
env:
  TZ: America/New_York
```

Right after it starts, `date -u` prints `Mon Mar  2 09:00:00 UTC 2026`, a
file written then is stamped 09:00 UTC, and Python's
`datetime.date.today()` is `2026-03-02`.

`date`, file times, `ls -l`, `find -mtime`, `jq`'s `now` and git commits
all see this time, and so do the
[Monty](https://docs.mirage.strukto.ai/home/setup/runtime/monty.md#time-and-datetime),
[WASI](https://docs.mirage.strukto.ai/home/setup/runtime/wasi.md#time-and-datetime),
[Pyodide](https://docs.mirage.strukto.ai/home/setup/runtime/pyodide.md#time-and-datetime) and
[QuickJS](https://docs.mirage.strukto.ai/home/setup/runtime/quickjs.md#date) runtimes. `TZ` in `env` picks
the zone `date` shows it in. With no `TZ` a workspace runs on UTC, whatever
zone the server is in. Without `system_time`, the workspace uses the server's
clock.

Each workspace keeps its own time, so workspaces on one server can each
start at a different date. Snapshots keep the setting, and `GET
/v1/workspaces/{id}` returns it.

Programs a `host` machine runs, sandboxed or not, see the time through
[libfaketime](https://github.com/wolfcw/libfaketime) when it is installed,
on Linux (`apt install libfaketime`). Without it, or on macOS, they run on
the host's clock. A statically linked program, as many Go tools are,
also reads the host's clock. Programs on a machine work the same way,
with libfaketime in the [machine image](https://docs.mirage.strukto.ai/home/setup/machine/overview.md#the-machine-image)
and the machine's clock without it.

## CLI entries

Each `clis` key is the command name the CLI installs under. An entry takes
exactly one of `cli` or `script`, and always a `machine`:

| Key | Purpose |
| - | - |
| `cli` | A registered CLI, such as `slack`. See [CLIs](https://docs.mirage.strukto.ai/home/setup/clis.md) for the twelve bundled ones. |
| `script` | A `.py`, `.js` or `.mjs` program file, read when the config loads. See custom CLIs in [Python](https://docs.mirage.strukto.ai/python/cli/custom.md) and [TypeScript](https://docs.mirage.strukto.ai/typescript/cli/custom.md). |
| `machine` | Required. The machine type it runs on: `in_process` for a `cli` entry; `in_process` or `host` for a `script`. The workspace needs a `machines` entry of that type that runs the script's language. |
| `runtime` | The runtime that runs `script` on `in_process`, such as `monty` or `quickjs`. Unset uses the first one for the script's language. A `host` takes none. Only valid with `script`. |
| `config` | Fields the CLI validates at install time. |

```yaml theme={null}
clis:
  slack:
    cli: slack
    machine: in_process
    config:
      token: {from: env, key: SLACK_TOKEN}
  pager:
    script: pager.py
    machine: in_process
```

A `script` entry's `config` cannot hold a credential pointer. Read the
credential from an [`env` pointer](https://docs.mirage.strukto.ai/home/service-credentials#set-environment-variables) instead.

## Workspace state and caches

These keys set where a workspace keeps its own data and its copies of what it
reads. See the [Infrastructure](https://docs.mirage.strukto.ai/home/infrastructure/overview.md) pages for each
one in YAML, Python and TypeScript.

| Key | Holds | Details |
| - | - | - |
| `store` | Symlinks and file attributes, sessions, command logs and background jobs | [Namespace](https://docs.mirage.strukto.ai/home/infrastructure/namespace.md), [Sessions](https://docs.mirage.strukto.ai/home/infrastructure/sessions.md), [Command Logs](https://docs.mirage.strukto.ai/home/infrastructure/command-logs.md), [Jobs](https://docs.mirage.strukto.ai/home/infrastructure/jobs.md) |
| `cache` | File content | [File Cache](https://docs.mirage.strukto.ai/home/infrastructure/file-cache.md) |
| `index` | Directory listings and file metadata | [File Index](https://docs.mirage.strukto.ai/home/infrastructure/file-index.md) |

A `cache` or `index` block needs a `type`. A `store` block without one keeps
in memory every part that its `namespace`, `sessions`, `logs` or `execution`
key does not move elsewhere. A `redis` type defaults `url` to
`redis://localhost:6379/0`.

## Values and paths

`${NAME}` placeholders are substituted from the host process environment when
the config loads. `NAME` is uppercase letters, digits and underscores, not
starting with a digit; any other `${...}` is left as written. A missing
variable is an error. Use a credential pointer when the value should be
fetched through a source instead. A config posted to a server is taken as
written: `mirage workspace create` substitutes from your shell first, and the
server never substitutes from its own environment.

Relative script paths and file-based code references resolve from the config
file's directory. Relative dotenv paths resolve from the Mirage process's
working directory.

## Sizes

A size is a number of bytes, optionally followed by `K`, `M` or `G` and an
optional `B`, in any case. The suffixes are binary: `K` is 1024 bytes.

| Write | Bytes |
| - | - |
| `4096` | 4,096 |
| `512K` | 524,288 |
| `100MB` | 104,857,600 |
| `1g` | 1,073,741,824 |

Anything else is refused with an error naming the key, such as `1.5GB`,
`100 MB`, `100MiB` or `1TB`.

These keys take a size:

```yaml workspace.yaml theme={null}
max_memory: 96MB
max_env: 2MB
max_body_bytes: 64MB
cache:
  type: ram
  limit: 32MB
mounts:
  /scratch:
    vfs: ram
    config:
      capacity: 100MB
```

So do the server's ceilings, in the `[daemon]` section of `~/.mirage/config.toml`
(`max_memory = "64MB"`)
or as environment variables (`MIRAGE_MAX_MEMORY=64MB`).


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