- A single custom image that replaces the default sandbox image for the whole installation. Configured in the Replicated Admin Console; no cluster access needed.
- Multiple custom images running side by side,
each with its own warm pool, selectable per user. Configured through the Runtime API; requires
kubectlaccess.
Why Use a Custom Image
Custom images eliminate cold-start setup work (clone, install, transpile, and bootstrap) so agents spend their time on the actual task. They also reduce setup variance and lower sandbox memory requirements by keeping only what the agent needs. With multiple custom images, different teams get different environments: a PHP image with Composer and MySQL client for the web team, a JDK and Maven image for the Java services team, a data science image with pinned Python packages for the analytics team. Each image is kept ready in its own warm pool so conversations start in seconds regardless of which environment they use.Build Your Own Custom Image
The OpenHands agent-server sandbox guide provides full documentation on building custom sandbox images. The approach is the same for the Enterprise Replicated VM deployment.Basic Pattern
- Start from the OpenHands agent-server base image.
- Keep the normal OpenHands entrypoint intact: extend the image, do not replace the entrypoint.
- Add your repo, docs, tools, and verification wrappers.
- Pre-run the expensive setup you do not want to repeat at task time.
- Publish the image to a registry reachable from your OpenHands cluster.
Base Image
Version Compatibility
Each OpenHands Enterprise release expects a specific agent-server version. The base image tag you build from must match the release you run: theopenhands-sdk inside the sandbox and the one inside
the OpenHands application must agree on major and minor version.
To find the expected tag, enable Use a Custom Sandbox Image in the Admin Console. The
Sandbox Image Tag field defaults to the tag the current release expects.
When a conversation starts on a custom image, OpenHands checks the sandbox’s agent-server version.
If it does not match the release, the conversation fails with an error naming the expected and
actual versions. Rebuild your image from the expected tag and update the Sandbox Image Tag
field to fix it.
Rebuild your custom image before each upgrade. The agent-server base image changes with every
OHE release, and an image built for an older release will be rejected by the version check.
Example: Build and Push
--platform linux/amd64 because the Enterprise Replicated VM runs on x86-64.
What to Bake In
Good candidates for prebaking:- Pinned repository checkouts
- Package manager caches and installed dependencies (
node_modules, Python virtualenvs, etc.) - Compiled or transpiled output
- Native system packages (
xvfb,libkrb5-dev,pkg-config, etc.) - Browser or Electron artifacts
- Stable helper scripts such as
prepare-*and*-verifywrappers
What to Keep Out
If the repository or dependencies change frequently, include aprepare-* script in the image
so the agent can refresh only the parts that need updating without a full rebuild.
Configure a Single Custom Image (Admin Console)
Once your image is built and pushed to a registry, point the Replicated Admin Console at it.- Open the Admin Console at
https://admin.<your-base-domain>:30000. - Navigate to Config and find the Sandbox Configuration section.
- Set the following fields:
- Click Save config and then Deploy to apply the change.
This setting applies to the sandbox / agent-server image only (the image that runs inside each
agent’s isolated workspace). It does not replace the other OpenHands service images.
Run Multiple Custom Images with Warm Runtime Pools
To offer several sandbox images at once, configure warm runtime pools through the Runtime API. Each configuration names one image and keeps a pool of pre-started sandbox pods ready for it. The OpenHands application automatically exposes every configuration as a selectable sandbox, so users can pick their environment without any redeployment. Requirements:- OpenHands Enterprise 0.64.0 or later.
kubectlaccess to the cluster. On a Replicated VM install, get a shell withsudo /var/lib/embedded-cluster/bin/openhands shell; on a Helm install, use your normal kubeconfig.- Custom images built and pushed as described above (all on the agent-server version your release expects).
How It Works
- The installer-managed configuration remains the base configuration. Configurations saved through the Runtime API are overlaid by name: a new name adds a pool, while an existing name overrides that installer-managed entry.
- You manage database configurations with the admin REST endpoints
(
PUT/DELETE /api/admin/warm-runtime-configs/{name}). Deleting an override reveals the installer-managed entry again. - A reconciler job runs every minute and creates or removes warm sandbox pods so each
configuration has
countunclaimed pods ready. - The OpenHands application polls the configuration list (cached for 60 seconds) and exposes each configuration as a sandbox spec. Users choose their default in Settings → Application → Default Sandbox.
- When a conversation starts, the Runtime API hands it a matching warm pod in a few seconds. If no warm pod is available, the sandbox cold-starts from the image instead (20+ seconds), and the reconciler replenishes the pool.
The installer-managed
v1_current pool remains active when you add API-managed configurations. Do
not save a v1_current configuration unless you intentionally want to override the installer default.Step 1: Confirm the Admin Password
The Runtime API’s admin endpoints authenticate with an admin password. Replicated generates a durable password, stores it in theadmin-password secret, and injects it into the runtime-api pod. The helper
script in Step 2 uses that pod environment. If the value is empty, the script reports an error before a
save or delete.
To set or rotate the password:
- Open the
Admin Consoleand selectConfig. - In
Sandbox Configuration, setRuntime API Admin Password. - Select
Save config, then deploy the new configuration.
admin-password Secret before
using the admin endpoints and restart runtime-api after changing it.
Step 2: Save the Helper Script
The Runtime API is not exposed outside the cluster by default. Download the maintainedwarm-runtime-configs.sh
helper, which runs each API call inside the runtime-api pod with kubectl exec:
The helper uses
DEFAULT_API_KEY and ADMIN_PASSWORD from the runtime-api pod without printing or
copying either value. Listing authenticates with the regular API key. Saving and deleting use the
admin password via a challenge-response login that returns a 24-hour JWT.source. When v1_current is not overridden, it appears
with "source": "file".
Step 3: Start From the Installer’s Default Configuration
Do not write configurations from scratch. The environment in a warm runtime configuration is what its sandbox pods actually boot with; the default configuration contains install-specific values (webhook callback URL, CA bundles, workspace paths) that sandboxes need to function. Export the default from the installer-managed ConfigMap and use it as your template:kubectl -n openhands get configmap | grep warm-runtimes.)
The installer-managed v1_current entry remains live and follows Admin Console changes. Derive each
custom image configuration from the exported template, changing only the image and pool size:
Configuration Format
The configuration name comes from the URL path (the
save <name> argument), not the body. Saving
creates or replaces a database entry. If an installer-managed entry has the same name, the database
entry overrides it. List responses also include a read-only source field: file for installer-managed
entries and db for API-managed entries and overrides. Do not add source to a saved configuration.
The application uses the image reference as the sandbox spec ID. Give every selectable configuration a
distinct image reference; configurations that share an image reference cannot be selected independently,
even if their commands or environments differ.
Step 4: Verify the Warm Pools
The reconciler runs every minute. Watch it create the pods:runtime-<random-id> deployment per warm pod, with your configured images. To see
the reconciler’s own view (per-pool counts, pull failures, culling decisions), read the latest
reconciler job log:
kubectl -n openhands describe pod <pod-name> shows the pull
error. For private registries, either fill in the Registry Server / Username / Password fields in
the Admin Console’s Sandbox Configuration section (they render an image pull secret that runtime
pods use), or add your own secret name to the runtime-api RUNTIME_IMAGE_PULL_SECRETS setting.
Step 5: Pick an Image and Start a Conversation
Within a minute of saving configurations (the application caches the list for 60 seconds):-
Per user: each user opens Settings → Application and picks an image in the Default Sandbox
dropdown (entries are the image references). Before a user picks an image, the application uses the
configuration named
v1_current, or the first configuration if nov1_currentexists. All of the user’s new conversations use their selected image. -
Per conversation (API): start a sandbox for a specific image, then attach a conversation to it:
session_id label, and the reconciler creates a fresh warm pod to replace it within a minute.
How Warm Pods Are Claimed
A conversation claims a warm pod only when the pod exactly matches the requested image, command, working directory, environment (ignoring a fixed set of session-specific variables), andrun_as_user / run_as_group / fs_group. Because the application requests exactly what the
selected configuration declares, conversations started through OpenHands match automatically.
Cold starts still happen when:
- All warm pods for the image are already claimed (
counttoo low for the traffic). - The configuration changed in the last minute, so the old pods no longer match and replacements are still starting.
- Warm pods cannot become ready (image pull failures, insufficient node resources).
Updating and Deleting Configurations
Update by saving the same name again. To roll out a new image version:./warm-runtime-configs.sh list; its source changes
from db to file.
Keep superseded image tags available in your registry while conversations that used them can still
resume: a paused conversation resumes on its original image. Delete old tags only after the
conversations that used them are gone (by default, stopped sandboxes are cleaned up after 10 days).
After Upgrading OpenHands Enterprise
Return an Entry to Installer Management
Delete a same-named database override to restore the installer-managed entry on the next reconciler cycle. For example, ifv1_current was intentionally overridden:
Troubleshooting
API Reference
The endpoints below are served by the runtime-api service (in-cluster:http://<runtime-api-service>:5000).
Admin authentication (for save and delete):
GET /api/admin/challengereturns{challenge, salt, iterations}. Challenges are single-use and expire after 5 minutes.- Compute
PBKDF2-HMAC-SHA256(password, salt + challenge, iterations, dklen=32)and hex-encode it. POST /api/admin/loginwith{"challenge": ..., "hash": ...}returns{"token": ...}, a JWT valid for 24 hours.- Send
Authorization: Bearer <token>on admin requests.
200 with the effective configuration set:
source: "file". API-managed entries have source: "db"; a database
entry with the same name replaces the file entry in this effective list.
Create or update a configuration (admin):
200 with the saved configuration. Creates or overwrites; the name in the URL is the identity.
Delete a configuration (admin):
200 with a confirmation message, or 404 if no database configuration has that name. When
the deleted name also exists in the installer-managed file, that file entry becomes effective again.
Reference
Agent-Server Sandbox Guide
Full SDK documentation on building custom sandbox images
Custom Image Example Repo
Dockerfile, benchmark scripts, and analysis tooling for the VS Code custom image example
Conversations and Sandboxes
How conversations, sandboxes, and their lifecycle fit together
Sizing Guide
Capacity planning, including headroom for warm pools

