--root away), PID 1 is tini, and there is no init system or Kubernetes identity to trip over. This page covers what the filesystem, environment, network, and lifecycle look like from a process running in the box.
To drive the box from outside (creating, exec, console, ssh), see Boxes.
Mental model
- A box is a single privileged container running a plain Ubuntu rootfs.
- The runner bind-mounts kernel and system paths under
/volumes/*, thenchroot /volumesand startstinias PID 1. - Sessions run as you — when you are signed in, exec, console, ssh, and dashboard sessions land as your deployment user. Root is explicit escalation (
--root), and the anonymous deployment API token acts as root because it carries no user identity. - Sessions also land in the box’s PID, UTS, and IPC namespaces —
ps,pgrep, andpkillin an exec, console, or ssh session see and signal box processes, andhostnameis the box’s. - There is no systemd and no other init —
tinireaps PID 1, nothing else. - The chrooted root filesystem is a copy-on-write btrfs subvolume. It survives stop/resume but is destroyed on delete.
Detecting that you are inside a box
A startup script appends a source line for/etc/boxes-env to /etc/bash.bashrc, and the runner injects a set of environment variables. The most reliable inside-the-box check is whether BOX_NAME or ZOMG_PROJECT is set:
/etc/boxes-env, so they are visible to any process — sh -c, cron jobs, Python subprocesses — not only to bash login shells.
Injected environment variables
| Variable | Value |
|---|---|
ZOMG_PROJECT | The project name |
BOX_NAME | The box name |
ZOMG_API_URL | https://api.zomg.ai |
ZOMG_API_HOST | api.zomg.ai |
ZOMG_API_IP | The node IP (Kubernetes status.hostIP) |
ZOMG_NODE_IP | The node IP — same value as ZOMG_API_IP, second name |
ZOMG_INSECURE | "true", only when the API is configured insecure |
TERM | xterm-256color |
ZOMG_API_IP and ZOMG_NODE_IP always resolve to the same value (the node IP), exposed under two names.
Reserved variables
These keys are reserved and cannot be set with-e at create time. Attempting to set one is rejected with environment variable is reserved:
-e KEY=VALUE pairs are written to /etc/boxes-env as export KEY="value" and added to the container environment, so they are visible to every process in the box.
Filesystem layout
The runner bind-mounts host paths under/volumes/* and chroots into /volumes. System volumes and persistent data are mounted at fixed paths; kernel and scratch surfaces are recreated on every pod start.
System volumes
| Path | Backing volume | Scope |
|---|---|---|
/box/bin | box-bin | Global |
/box/all | box-all | Global |
/box/proj | box-proj | Per project |
/home | box-home | Global |
/box/* and /home are separate volumes from the box’s root filesystem, so they survive box delete. /home is one deployment-global volume shared by every box in every project: the same user gets the same uid and the same /home/<user> everywhere.
Kernel and scratch surfaces
Recreated on every pod start:/proc,/sys,/dev— procfs, sysfs, and a recursive bind of the host/dev./tmpand/run— bind-mounted from an ephemeral host path./etc/resolv.confand/etc/hosts— bind-mounted from the pod’s files.
What survives what
| State | Stop / resume | Delete |
|---|---|---|
Root filesystem (/, btrfs CoW) | Survives | Destroyed |
/box/* system volumes | Survives | Survives |
/home (global volume) | Survives | Survives |
| Normal data volumes | Survives | Survives |
| Box-owned clone data volumes | Survives | Deleted after owning-box cleanup completes |
/tmp, /run | Wiped | Wiped |
PATH and shell defaults
The base image sets the PATH in/etc/profile.d/boxes-path.sh and /etc/bash.bashrc:
HOME=/home/<name>for a user session,HOME=/rootfor a root sessionTERM=xterm-256colorWORKDIRis/work, and/etc/bash.bashrcrunscd /workwhen an interactive shell sources it.
The bash-login gotcha
/box/bin and /box/proj/bin are added to the PATH only because the base image’s bash startup files run — /etc/profile.d/boxes-path.sh for login shells and /etc/bash.bashrc for interactive shells. The bare system PATH does not include them.
zomg exec runs commands through a login shell (bash -lc), so /etc/profile.d/boxes-path.sh runs and the /box directories appear on the PATH. But a non-bash child process — a sh -c spawned by Python, a cron job, anything that bypasses bash startup files — sees only the bare system PATH:
/box/bin or /box/proj/bin, reference it by absolute path or set the PATH explicitly.
The
cd /work line lives in /etc/bash.bashrc, which bash sources for interactive shells. Interactive sessions (zomg console, zomg ssh) land in /work. A non-interactive zomg exec login shell does not necessarily start in /work — pass --wd/--cwd if you need a specific working directory.eatmydata and fsync
The base setsLD_PRELOAD to libeatmydata.so in both /etc/profile.d/eatmydata.sh (an export for login shells) and /etc/environment (a bare assignment PAM applies to every session):
eatmydata turns fsync() and related calls into no-ops, which speeds up package installs, database setup, and similar work in a disposable box. The tradeoff is that data is not flushed to disk as durably as usual.
Preinstalled packages
Thebox-base image includes a standard toolbox:
sudo is configured NOPASSWD:ALL, so a non-root deployment-user session can escalate to root without a password. Inside a box, the boundary is the box itself, not the user account.dev-base image layers on top of box-base and adds:
gnupg,gh(GitHub CLI)- Node.js 22 (from NodeSource)
@anthropic-ai/claude-codeand@openai/codex(global npm)bunandbunx- the VS Code CLI (
code), forcode serve-webandcode tunnel— see Development
Networking and DNS
The pod’s DNS is configured for short, project-scoped names while keeping external lookups fast:- Search domains:
<project>.boxes,boxes,<project> ndots:1, so external FQDNs likearchive.ubuntu.comresolve absolutely first.
clusterIP: None), so DNS returns the pod IP directly and all ports are reachable between boxes — there is no port allowlist for box-to-box traffic. To expose a box to the public web on a URL, see Services.
Resource limits
Kubernetes sets a memory request and a memory limit, plus a CPU request:- Memory is capped by the limit.
- CPU has a request only — there is no hard CPU limit, so CPU is not capped by a quota.
Users
Users are a single deployment-level registry — the lab-computer model. One user means one uid and one/home/<user> in every box in every project:
- UIDs start at
20000and are assigned sequentially across the deployment (max(existing) + 1), then persisted. UID equals GID. - Names must be 1–32 characters of
[a-z0-9_-]and not a reserved system name. - Home is
/home/<name>on the deployment-global home volume, default shell/bin/bash. - A user can be bound to an email. On first allowlisted Google sign-in, Zomg auto-provisions a user named after the email’s localpart (lowercased; characters outside
[a-z0-9_-]become-). A pre-created email-less user with that name is claimed by the sign-in; a name already bound to a different email is a conflict the operator resolves by updating user emails. zomg user deletekeeps/home/<name>by default; pass--purge-hometo remove it.
--root for an explicit root session, or --user <name> for another user. The anonymous deployment API token has no user identity and acts as root.
When a session runs as a user, the runner writes that user’s /etc/passwd, /etc/group, and /etc/shadow entries on the fly: it removes any prior line for that same user, appends a fresh one, and creates /home/<name> if needed. (The /etc/shadow entry is what lets sudo’s PAM account phase succeed.) Entries for other users accumulate across execs and are not removed.
Lifecycle from inside
Stop
The deployment scales to zero. Every process dies, and
/tmp and /run are lost. The root filesystem is preserved.Resume
A new pod starts with a fresh
tini as PID 1. Environment variables are re-injected and system mounts are re-applied. Your processes do not survive — only the filesystem does.Next steps
Boxes
Create, exec, console, and ssh into boxes from outside.
Data volumes
Attach shared persistent disks across boxes.
Services
Expose a box to a public URL.
API reference
Manage boxes programmatically over HTTP.