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 install things at boot time with a startup script. Here’s how to choose:
Most teams start with a startup script 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.
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 withmurmur set:
- Make it idempotent. Bake retries from scratch on a fresh VM, so a flaky
apt-getor network blip can fail one attempt and succeed the next.set -euo pipefailand 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
If your script needs a token for a private registry (Artifactory, npm Enterprise, a private GitHub repo), add the secret name to the Recipe’ssecret_allowlist. The secret is injected as an environment variable during the bake only. It’s never written into the final Image, so anyone in your tenant can safely use the resulting Image without seeing your credentials.
2. Bake the Image
Kick off the build withmurmur bake:
murmur bakes ls:
3. Use your new Image
Edit your Workspace to reference the baked Image by name:Things to watch for when writing your script
The murmur user contract
Provisioning runs as root. Agents run as themurmur user (UID 1000, home directory /home/murmur), which has passwordless sudo by default. That’s deliberate: the VM is untrusted, ephemeral, and single-tenant, and all real isolation is enforced server-side — so agents get root on their own machine and can self-provision system dependencies at runtime (sudo apt-get install just works, and once your Image adds Node.js, so does npx playwright install --with-deps chromium). If your org wants a non-root agent, strip the grant in your Recipe: rm /etc/sudoers.d/murmur. See the security model for why root on the VM isn’t a boundary.
Sudo is the fallback, not the plan: a runtime install costs minutes on every spawn, a bake pays that cost once. Install so everything your agents need is readable and executable by murmur without sudo — otherwise agents burn time rediscovering your toolchain on every boot.
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 intomurmur’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 redundantapt installs and save bake time.