Skip to content

Sandboxes

A sandbox is the Docker container a run executes in, and you choose what goes into it and what it can reach. You set it up in the job’s Environment tab and in the Sandbox and Secrets cards of its Settings tab.

Umpteenth starts a fresh container for each run and executes the agent’s commands and the playbook’s scripts in it as the user agent (uid 1000), with all Linux capabilities dropped. The job’s stdio MCP servers run there too.

Once the run ends, Umpteenth collects the files in /ump/outputs as the run’s artifacts and removes the container, along with anything else the run left in it, such as a package the agent installed or a file in /workspace. Each sandbox gets 1 CPU and 1024 MB of memory by default, which you change in the job’s Sandbox card or under Settings → General → Sandbox defaults (Managing jobs).

Runs use ghcr.io/stonith404/umpteenth-sandbox:latest, a Debian trixie-slim image with bash, coreutils, curl, git, jq, ripgrep, python3 with pip and requests, uv, node and npm.

To start from another image, set Default image under Settings → General → Sandbox defaults for the workspace, or Base image in a job’s Sandbox card for that job alone. A custom image needs bash and an amd64 or arm64 build, because Umpteenth runs commands with bash -lc and ships its ump CLI for those two architectures. Umpteenth pulls an image only if the Docker host lacks it, so after you push a new version under the same tag, run docker pull on the host.

If a job needs a Python package such as pandas and has no Dockerfile, the agent installs it on every run, and you pay for those turns each time. Put it in the job’s Dockerfile instead: Umpteenth installs it once, when it builds the image, and the install drops off your model bill. System packages such as ffmpeg belong there too, since the agent can’t install them without root. Open the job’s Environment tab, click Add a Dockerfile, which fills in a template, and add what the job needs:

Dockerfile
FROM ghcr.io/stonith404/umpteenth-sandbox:latest
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
RUN uv pip install --system pandas

Click Save & build, and Umpteenth saves the Dockerfile as a new playbook version and builds the image. A run that starts before the image is ready waits for it, and the wait counts against the run’s Timeout. From then on the Dockerfile’s FROM line sets the job’s image, and the Base image field no longer applies.

Umpteenth builds the image under these rules:

  • The build gets the Dockerfile alone, with no build context, so COPY of local files fails.
  • Umpteenth uses Docker’s classic builder, which rejects BuildKit syntax such as RUN --mount and heredocs.
  • Registry validation, base-image resolution and the build share a 15-minute deadline, and the completed image can be up to 5 GiB.
  • Docker build steps get 1 CPU and 1 GiB of memory, including swap. These limits apply to builds separately from the job’s run limits. Docker’s classic build API provides no process-count or writable-storage limit, so use a dedicated builder host with bounded storage for untrusted builds.
  • Build steps reach the internet through the egress proxy, whatever the job’s network setting.
  • ADD, custom Dockerfile frontends, RUN mounts with a from source and deferred ONBUILD copies or mounts with source flags fail validation. Download files with curl in a RUN step instead.
  • While network.allow_private_targets is false, images on a registry at a private address fail the build too, unless they come from sandbox.registry.repository. On the configured registry host, a job can reference only its own images or the operator-selected sandbox.image; this rule also applies to the job’s Base image. Docker downloads other images through Umpteenth’s outbound guard, including registry redirects and authentication endpoints.

The compile step and reflection write Dockerfiles as well. The Sandbox card on the New job review screen has a Custom environment switch, which the compile step turns on if it proposes a Dockerfile, and Umpteenth builds that image before the first run. With Self-improve on, reflection moves packages the agent installed by hand into the Dockerfile after a run (How jobs learn).

Umpteenth builds each Dockerfile once and reuses the image for every run until the Dockerfile changes. Log in the Builds list below the editor opens a build’s output, live while the build runs.

Each build pulls the FROM image, so click Rebuild after a new version of the base image comes out, for example after you upgrade Umpteenth. Runs keep using the last good build of the Dockerfile while a rebuild runs, and if it fails.

If a Dockerfile has no successful build and its latest build failed, every run of the job fails with Environment build failed: <error>. Read the build’s Log, fix the Dockerfile and click Save & build. Click Rebuild instead if the cause lay outside the Dockerfile, such as a package mirror that was down.

To run the job on its base image again, click Use the base image, which removes the Dockerfile and keeps it in the playbook’s history for a rollback.

Run as root in the Sandbox card runs the agent’s commands and the playbook’s scripts as root with Docker’s default capabilities, so the agent can install system packages during a run. Those packages disappear with the sandbox, and the agent installs them again next time, so a package the job always needs belongs in its Dockerfile. A root sandbox skips stdio MCP servers that have environment variables, since root could read them (MCP servers).

The Network select in the Sandbox card has four options: Internet access, Allowed domains only, No network and, if the instance offers it, Unrestricted network. The New job page offers only Internet access and No network, so switch to another option in the job’s settings after you save it. ump calls and HTTP MCP servers work with every option, because Umpteenth makes those connections on the sandbox’s behalf.

Network settingin the job’s Sandbox cardBrokerump CLI callsInternetpublic addressesPrivate networkLAN, Docker hostCloud metadata169.254.169.254Internet accessdefault for new jobsInternet access+ Allow private networkAllowed domains onlythe job’s list of namesAllowed domains only+ Allow private networkNo networkthe broker, nothing elseUnrestricted networkno proxy, no checksproxyproxyproxylisted onlyproxyproxylisted onlylisted onlyproxyno routeno routeno routereaches itonly names on the job’s listblocked, labeled with what blocks it
Every setting keeps the broker reachable. Internet access and Allowed domains only go out through the egress proxy, which refuses the blocked addresses on any engine, and Unrestricted network bypasses it.

Internet access is the default. The sandbox reaches the internet only through Umpteenth’s egress proxy, which connects to any public address and refuses private networks, the Docker host and cloud metadata (Security).

Umpteenth sets HTTP_PROXY, HTTPS_PROXY and ALL_PROXY in the sandbox, and most tools pick them up, from curl and git to pip, uv and npm. SSH, and with it git over SSH, goes through the proxy with ump connect. The sandbox can’t resolve public names itself, so tools that ignore the proxy variables can’t connect: ping, anything over UDP, TCP clients without SOCKS support and Java without proxy flags. Give a job that needs one of those the Unrestricted network.

The run’s timeline shows the first connection to each host and every refusal as a ump proxy <host> step.

Pick Allowed domains only for a job that needs a few known hosts and nothing else. The egress proxy then connects only to the hosts you list under Allowed domains, one per line:

api.github.com
*.githubusercontent.com

*.example.com matches every name below example.com but not example.com itself, so list both if a job needs both. Any port on an allowed host works, so github.com on the list lets git reach it over SSH too. A connection to a host off the list fails with <host> is not on this job's allow-list and leaves a red ump proxy <host> step on the timeline, where you read the name to add. Job settings lists the rules for the field.

The sandbox is offline, but ump commands and HTTP MCP servers keep working. A job that talks to other services through HTTP MCP servers alone runs fine this way. A stdio MCP server started with npx -y can’t download its package here, so install it in the job’s Dockerfile (MCP servers).

Unrestricted network turns the network sandboxing off. The sandbox gets a direct route out and reaches everything the host can, your LAN, the Docker host and cloud metadata included. It suits a job whose tools ignore proxy settings, as long as you trust its instruction and the content it reads. The option appears while sandbox.allow_unrestricted_network is on, which it is by default (Security).

Turn on Allow private network for a job that needs your LAN or the Docker host, such as a Gitea instance on your home network. The switch appears for Internet access and Allowed domains only. It lets the egress proxy connect to private addresses, while loopback addresses and cloud metadata stay blocked (Security).

Create a secret for each credential under Settings → Secrets with Create secret. Umpteenth encrypts the values at rest, and neither the UI nor the API shows a value again.

A secret reaches a run only after you map it in the job’s Secrets card: click Add secret, pick the secret and name the environment variable. Umpteenth reads the values when each run starts, so an updated secret applies from the next run. The variables exist for every command in the sandbox, from the agent’s shell to the playbook’s scripts. On the New job review screen, the Secrets card lists the variables the compile step found in your instruction, with the secret that holds each one already picked. A variable without a match shows Pick a secret. Create the secret in another tab, and the card picks it once you come back if its name matches the variable, as github-token matches GITHUB_TOKEN. Saving the job maps the picked secrets.

The agent gets no list of the variables, so name them in the instruction:

Fetch the open pull requests of acme/api from the GitHub API with curl, using $GITHUB_TOKEN as the bearer token.

Deleting a secret removes it from every job that maps it. MCP servers take secrets as {{secret:NAME}} in their headers and environment instead, as MCP servers shows.

Path Contents
/workspace The working directory of the agent’s commands and the playbook’s scripts
/ump/input.json The run’s input: a webhook body, the Input of Run now or the API, or {}
/ump/outputs/ Files to keep, which Umpteenth collects as the run’s artifacts, up to 50 MiB, even from a failed run
/ump/PLAYBOOK.md The job’s current playbook as Markdown
/ump/skills/<name>/ The job’s skills, owned by root

You download artifacts from the run’s Outputs tab (Reading a run), and the ump CLI reference lists the other paths.