This guide sets up a box before it starts: its screens, applications, packages, fonts, network, and browser data. For what each setting means, see Boxes. For a table of each setting in each interface, see Configuration.
Image settings and runtime settings
Some settings go into the image. The first box with a new combination of them builds a new image, which takes a few minutes. Later boxes with the same combination start in seconds.
| Goes into the image | Does not |
|---|---|
| Applications | Screen size and number of screens |
| Packages | Network |
Features: wide_fonts, video, dock, accessibility, and x11_apps on Wayland by default; audio |
Memory, CPUs, lifetime |
| X11 or Wayland | Browser profile |
You cannot add a feature to a running box. To add an application to a running box, see Add an application to a running box.
To build an image before a box needs it:
holm image build docker --app vscode --package jqScreens
BOX=$(holm new --size 1920x1080 --screens 2)Each screen has its own display, browser, and viewer. Screen 0 is the primary screen.
For Wayland instead of X11:
BOX=$(holm new --wayland)A Wayland box has XWayland by default, so X11 applications also run on it. --minimal leaves it out.
Applications
The catalog has these applications:
| Name | Application |
|---|---|
xterm |
xterm |
gimp |
GIMP |
files |
Thunar |
text-editor |
Mousepad |
vscode |
Visual Studio Code |
List them with holm apps, GET /v1/catalog, or the MCP tool list_apps.
Install applications when you create the box, then open one by name:
BOX=$(holm new --app gimp,vscode)
holm app "$BOX" gimpholm app returns when the application's window has appeared and stopped changing. MCP: launch_box with apps, then open_app.
Your own applications
Define an application in the spec under apps. Give it a name, the packages to install, the command that opens it, and the window that shows it is ready:
{
"spec": {
"apps": {
"inkscape": {
"packages": ["inkscape"],
"command": ["inkscape"],
"window": { "class": "Inkscape" },
"settle_ms": 1000
}
}
}
}| Field | Effect |
|---|---|
packages |
Apt packages to install. |
command |
The command that opens the application. |
window |
{"class": "…"} or {"title": "…"}. open_app waits for this window. |
settle_ms |
Time to wait after the window appears, for applications that draw late. |
source |
An extra apt repository: {"key_url": "…", "list": "…"}. |
BOX=$(holm new --spec app.json)
holm app "$BOX" inkscapeAn application with its own source makes the image build download and trust that repository's key. The server refuses it unless the spec also sets "policy": {"custom_sources": true}. The catalog's own sources, such as the one for vscode, need no policy.
Add an application to a running box
The server can install a catalog application, or an application from the box's spec, into a running box:
holm app "$BOX" install gimp
holm app "$BOX" gimpMCP: install_app with apps: ["gimp"], then open_app. REST:
curl -X POST "$BASE/v1/boxes/$BOX/apps" \
-H 'content-type: application/json' \
-d '{"apps": ["gimp"]}'The install runs as root. Where the box's commands run as another user, as on E2B, it runs through sudo.
The box installs it with apt, so the box needs network access, and each install takes time. A fork does not keep it, because a fork builds from the spec. When you know the application in advance, put it in the spec.
Packages
Add apt packages for commands that the agent runs with run_command or holm exec:
BOX=$(holm new --package jq --package ripgrep)let computer = Computer::builder().packages(["jq", "ripgrep"]).launch().await?;Features
A box is one of two desktops. --base is the default, so the tools that need these features work without asking. --minimal is the bare desktop, for when size or build time matters more:
| Feature | Adds | Without it | --base |
--minimal |
|---|---|---|---|---|
| Wide fonts | Noto CJK and color emoji fonts | Chinese, Japanese, Korean, and emoji show as empty boxes, and the screenshot still looks correct at a glance. | Yes | No |
| Video | ffmpeg | holm record and the MCP record tool fail. |
Yes | No |
| Dock | A tint2 dock | A person has no launcher. | Yes | No |
| Accessibility | AT-SPI | Native windows cannot be driven by widget name. See Control modes. | Yes | No |
| X11 apps | XWayland | X11 applications do not open on Wayland. | On Wayland | No |
| Audio | PulseAudio | A page or application that needs a sound server does not work. | Add with --audio |
Add with --audio |
BOX=$(holm new --minimal)
BOX=$(holm new --minimal --audio)For any other set of features, list them in a --spec file.
Network
BOX=$(holm new --no-network)The box has no network access. This does not affect the viewer, which the host serves.
Browser data
There are two ways to keep a login.
| Profile | Saved state | |
|---|---|---|
| Keeps | The full Chromium profile: logins, history, extensions, storage | Cookies and storage for the origins you name |
| Stored in | A volume on the host | The server's memory, or a file |
| Moves between hosts | No | Yes |
| Runtimes | Container runtimes only | All runtimes with DevTools |
| Boxes at a time | One | Any number |
Profile
BOX=$(holm new --profile work)The volume is holm-profile-work. The next box with --profile work starts with the same browser data. The server refuses a second box that asks for a profile that another box holds, running or stopped, and names that box.
Before the server stops or removes a box with a profile, it closes the browser cleanly, so recent cookies are written. Session cookies end when the browser closes, as on any computer. A site's "remember me" cookie is the one that keeps a login. A fork does not take the profile.
Saved state
holm browser "$BOX" state save login.json --origin https://mail.example.com
NEW=$(holm new)
holm browser "$NEW" state load login.json--name NAME keeps the state on the server instead of in a file, until the server restarts. A state file contains live logins, and the CLI writes it with mode 0600.
MCP: save_state and load_state.
Start from a spec file
Put a full spec and placement in a file, and pass it to holm new:
{
"spec": {
"desktop": {
"server": "x11",
"width": 1280,
"height": 800,
"packages": ["jq"]
},
"apps": { "gimp": {} },
"policy": { "network": true }
},
"placement": {
"runtime": "docker",
"memory": "2g",
"expires_after_secs": 3600
}
}BOX=$(holm new --spec box.json)
BOX=$(holm new --spec box.json --size 1920x1080)
cat box.json | holm new --spec -A flag overrides the same value in the file. The server refuses unknown keys, so a misspelled key gives an error.
"gimp": {} names a catalog application. An entry with fields defines your own.
The same file is the body of POST /v1/boxes. In Rust, see examples/from_spec.rs. For all fields, see Create a box.