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:
workspace.yaml
ready. Replace RAM using a VFS setup guide.
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’sconfig is the same document as workspace.yaml:
Workspace settings
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.
backend takes one of these values:
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: that program connects from the
machine under the machine’s own network settings, and network,
pre_network and routes do not apply to it.
*, 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.
: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 can hold its sessions to a narrower
network. routessends a connection for onehost:portto another. See Route Network.- Policies can also check each connection the lists let through, and cap how
much it carries, at
pre_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
Ahost 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:
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,
WASI,
Pyodide and
QuickJS 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 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
and the machine’s clock without it.
CLI entries
Eachclis key is the command name the CLI installs under. An entry takes
exactly one of cli or script, and always a machine:
script entry’s config cannot hold a credential pointer. Read the
credential from an env pointer 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 pages for each one in YAML, Python and TypeScript.
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 byK, M or G and an
optional B, in any case. The suffixes are binary: K is 1024 bytes.
Anything else is refused with an error naming the key, such as
1.5GB,
100 MB, 100MiB or 1TB.
These keys take a size:
workspace.yaml
[daemon] section of ~/.mirage/config.toml
(max_memory = "64MB")
or as environment variables (MIRAGE_MAX_MEMORY=64MB).