Skip to main content
Every agent runs on a VM. The platform’s default Image gets you Debian, Node, Git, Claude Code, and gh, which is enough for most TypeScript or Node projects. If your repos need anything else (Go, Python, Docker, CUDA, a private SDK, ImageMagick, an internal CLI), you’ll want a custom Image so agents boot with your toolchain already in place. You can also run setup on every spawn with a repo hook. Here’s how to choose: Most teams start with a repo hook while figuring out their stack, then move stable installs into a Recipe and bake when things settle. See recipe for every field on the Recipe resource. This example authors a tenant-level recipe. It requires recipe.create (or recipe.edit for an existing record), bake.create, and service-profile.assume on the selected service profile. A cloud identity on a tenant-level bake additionally requires placement-sa.assume. Ordinary membership does not supply tenant-level recipe writes or bake authority; a workspace manager’s authority instead covers scoped names. See authorization.

1. Write a Recipe

A Recipe is a YAML file that names a base Image and a shell script. The shell script is what gets baked into your new Image. Apply it with murmur set:
A few things worth knowing about the script:
  • Make it idempotent. Bake retries from scratch on a fresh VM, so a flaky apt-get or network blip can fail one attempt and succeed the next. set -euo pipefail and explicit cleanup (apt-get clean) keep things tidy and surface real failures.
  • Clean up after yourself. Anything you leave on disk ends up in the final Image, which makes it bigger and slower to load. Delete tarballs, package caches, and build artifacts before the script exits.

If your install needs a private registry

To supply a private-registry token during a bake, add the secret name to the recipe’s secret_allowlist. The allowlist controls delivery to the bake VM; the provisioning script determines what is written into the resulting image.

2. Bake the Image

Kick off the build with murmur bake:
A bake takes 5 to 15 minutes. Check in any time with murmur bakes ls:
Baked Images are content-addressed with a SHA-256 hash of the base Image, script, target architecture, and secret allowlist. A no-op rebake is free because the platform reuses the existing Image when the inputs have not changed. Changing one byte of the script produces a fresh Image without manual version management.

3. Use your new Image

Each bake registers a catalog image named <recipe>-<first 16 characters of the image hash>, with -<region> appended on AWS. The baked image inherits the recipe’s resource-admission policy; this example admits the backend workspace. List the result with murmur get image, then update an existing workspace with murmur patch, using its actual generated name:
To try an Image on a single agent first, run murmur spawn --image-ref go-python-0f5e1c8a2b3d4e5f go-python-test "Test the Go and Python image". The next agent spawned in this workspace boots from that image. To select a previous build, update image_ref to an admitted image with murmur patch. To confirm which build an agent is actually on, read /etc/murmur/baked-image.json on its VM. It names the Recipe, the Recipe version, and the bake time. See identifying the image from inside a VM.

Things to watch for when writing your script

The murmur user contract

Provisioning runs as root. Agents run as the murmur user (UID 1000, home directory /home/murmur), which has passwordless sudo by default. The VM is untrusted, ephemeral, and single-tenant, while isolation is enforced server-side. Agents can therefore install system dependencies on their own VM at runtime with sudo apt-get install; after your Image adds Node.js, they can also run npx playwright install --with-deps chromium. To run agents without root access, remove the grant in your Recipe with rm /etc/sudoers.d/murmur. See the security model for why root on the VM is not an isolation boundary. Use sudo as a fallback. A runtime install costs minutes on every spawn, while a bake pays that cost once. Make everything agents need readable and executable by murmur without sudo so each boot does not repeat toolchain setup. The patterns below cover the common cases.

Install to system paths

Things in /usr/local/bin are on murmur’s $PATH by default and world-executable, so this works without any extra steps:

Tools that install into a home directory

Some installers (sdkman, pyenv, rustup, etc.) drop files into ~/. Run them as murmur, otherwise they land in /root where the agent won’t find them:

Config files dropped as root

If your script copies a config file into murmur’s home, fix the ownership or murmur will hit a permission-denied at runtime:

Non-standard $PATH locations

If you install something to /opt/... or anywhere not already on $PATH, extend it through /etc/profile.d/ (not ~/.bashrc) so every shell murmur opens picks it up:

Don’t redo what the base already has

In the dashboard, expand “What’s on <image>” under the base Image picker. It shows you the exact provisioning script that built the base, so you can skip redundant apt installs and save bake time.