Inside a box, you get what looks like a fresh, privileged Linux machine: you are root, 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/*, then chroot /volumes and starts tini as PID 1.
  • You run as root by default. There is no systemd and no other init — tini reaps PID 1, nothing else.
  • The chrooted root filesystem is a copy-on-write btrfs subvolume. It survives stop/resume but is destroyed on delete.
PID 1  = /usr/bin/tini  (started via chroot /volumes)
user   = root  (HOME=/root)
init   = none  (no systemd; tini only)

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:
if [ -n "${BOX_NAME:-}" ]; then
  echo "Running inside box $BOX_NAME (project $ZOMG_PROJECT)"
fi
These variables are written both to the container environment and to /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

VariableValue
ZOMG_PROJECTThe project name
BOX_NAMEThe box name
ZOMG_API_URLhttps://boxes.<domain>
ZOMG_API_HOSTboxes.<domain>
ZOMG_API_IPThe node IP (Kubernetes status.hostIP)
ZOMG_NODE_IPThe node IP — same value as ZOMG_API_IP, second name
ZOMG_INSECURE"true", only when the API is configured insecure
TERMxterm-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:
ZOMG_PROJECT  BOX_NAME  ZOMG_API_URL  ZOMG_API_HOST  TERM
Your own -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

PathBacking volumeScope
/box/binbox-binGlobal
/box/allbox-allGlobal
/box/projbox-projPer project
/homebox-homePer project
/box/* and /home are separate volumes from the box’s root filesystem, so they survive box delete.

Kernel and scratch surfaces

Recreated on every pod start:
  • /proc, /sys, /dev — procfs, sysfs, and a recursive bind of the host /dev.
  • /tmp and /run — bind-mounted from an ephemeral host path.
  • /etc/resolv.conf and /etc/hosts — bind-mounted from the pod’s files.
/tmp and /run are ephemeral. They are wiped on every pod restart, including stop/resume. Do not store anything you need to keep there.

What survives what

StateStop / resumeDelete
Root filesystem (/, btrfs CoW)SurvivesDestroyed
/box/* system volumesSurvivesSurvives
/home (project volume)SurvivesSurvives
Normal data volumesSurvivesSurvives
Box-owned clone data volumesSurvivesDeleted after owning-box cleanup completes
/tmp, /runWipedWiped
For how the CoW root relates to bases and checkpoints, see Snapshots and bases. For attaching shared persistent disks, see Data volumes.

PATH and shell defaults

The base image sets the PATH in /etc/profile.d/boxes-path.sh and /etc/bash.bashrc:
export PATH="$HOME/.local/bin:/box/proj/bin:/box/bin:$PATH"
Defaults for the root user:
  • HOME=/root
  • TERM=xterm-256color
  • WORKDIR is /work, and /root/.bashrc runs cd /work when it is sourced.

The bash-login gotcha

/box/bin and /box/proj/bin are added to the PATH only because /etc/profile runs. The base 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:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
If such a process needs a binary from /box/bin or /box/proj/bin, reference it by absolute path or set the PATH explicitly.
The cd /work line lives in /root/.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 sets LD_PRELOAD to libeatmydata.so in both /etc/profile.d/eatmydata.sh (login shells) and /etc/environment:
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libeatmydata.so
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.
If a specific command relies on real fsync durability, you can opt out for that command by clearing the preload for it:
LD_PRELOAD= mycommand

Preinstalled packages

The box-base image includes a standard toolbox:
bash build-essential ca-certificates curl wget ripgrep fd-find git less
vim nano openssh-client openssh-sftp-server jq procps psmisc htop lsof
iproute2 iputils-ping net-tools dnsutils netcat-openbsd ncurses-bin
util-linux tini python3 python3-pip python3-venv python-is-python3
tmux unzip zip eatmydata
The dev-base image layers on top of box-base and adds:
  • gnupg, gh (GitHub CLI)
  • Node.js 22 (from NodeSource)
  • @anthropic-ai/claude-code and @openai/codex (global npm)
  • bun and bunx
  • the VS Code CLI (code), for code serve-web and code tunnel — see Development
For building your own templates, see Snapshots and bases.

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 like archive.ubuntu.com resolve absolutely first.
The box’s Service is headless (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.
free -m and nproc report the host’s values, not your box’s allocation — this is standard Linux behavior, not a Zomg feature. To see the actual memory cap, read the cgroup file:
cat /sys/fs/cgroup/memory.max
Because no CPU limit is set, cat /sys/fs/cgroup/cpu.max may report max (uncapped).

Project users

By default everything runs as root. You can create project-scoped users:
zomg user create alice
  • UIDs start at 20000 and are assigned sequentially per project (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>, default shell /bin/bash.
When you exec as a user, the runner writes that user’s /etc/passwd and /etc/group entries on the fly: it removes any prior line for that same user and appends a fresh one. Entries for other users accumulate across execs and are not removed.

Lifecycle from inside

1

Stop

The deployment scales to zero. Every process dies, and /tmp and /run are lost. The root filesystem is preserved.
2

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.
3

Delete

The root subvolume is destroyed. System volumes (/box/*, /home) and normal persistent data volumes survive. Box-owned clone data volumes are the exception: Zomg deletes them with the owning box after cleanup completes.
Changing environment variables on an existing box does not work. zomg update only applies PORT, CPU, memory, termination grace period, and tags — arbitrary KEY=VALUE env changes are silently ignored. To change real environment variables, recreate the box with -e.

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.