# Images: bases and templates Every sandbox starts from a **base**: a container image turned into a read-only root disk for the sandbox's microVM. A **template** is a base plus a size and a policy, and a **warm template** also keeps a booted, warmed-up memory snapshot, so sandboxes made from it resume instead of booting. This page covers how to get your own images in, when to reach for a template, and the guest kernel every sandbox runs. The same actions are on the **Images** page of the web console. ## Base or template | | Base | Template | |---|---|---| | What it is | an image: files, a command, environment variables, a working directory | a base plus CPUs, memory, disk and a policy, optionally warm | | Sandbox size | whatever you pass to `zbox new`, otherwise 1 vCPU and 512 MiB | the template's size, unless you override it | | Start | cold boot | cold boot, or resume from memory if warm | | Use it for | one-off sandboxes, trying an image | many sandboxes of one kind, with fixed sizes, default ports and limits, or things loaded in advance | ```bash # boot straight from a base zbox new scratch --base alpine # or from a template zbox new agent1 --template dev ``` Both `--base` and `--template` take a name, a digest or a digest prefix. `--base` also takes the image reference the base was added from. ## The default base A new workspace gets the default sandbox image as its first base (see [create a workspace](/zbox/getting-started#create-a-workspace)). It's Ubuntu 26.04 with Node.js 24, Python 3, build tools (gcc, make), git, curl, the GitHub CLI, Claude Code and Codex. Its working directory is `/workspace` and its command is `sleep infinity`, so the sandbox just waits for your commands. The default image is published as a tarball. To add it again, for example after you removed it or to pick up a [new agent](#keep-bases-current): ```bash zbox base add https://storage.cdn.zerops.app/4ga3k-boxstorage/zbox/zbox-base.tar.gz --name default ``` ## Add a base from a registry or a URL `zbox base add` pulls an image and turns it into a base: ```bash # Docker Hub short names work like in docker zbox base add python:3.12-slim --name python # any OCI registry, pinned to a digest if you like zbox base add ghcr.io/acme/agent@sha256: --name agent # a `docker save` archive (.tar, .tar.gz or .tar.zst) served over http(s) zbox base add https://example.com/images/agent.tar.gz --name agent ``` The workspace downloads the `linux/amd64` image, applies its layers into one filesystem and stores it as a compressed read-only disk image. The pull runs as an [operation](/zbox/access#operations); the CLI waits for it unless you pass `--no-wait`. A pull may take up to 15 minutes. **Private registries.** Put the registry login as `username:password` (or `username:access-token`) into an [environment variable of the workspace service](/zbox/workspace-service#environment-variables), then name the variable, not the value: ```bash zbox base add ghcr.io/acme/private-agent:1.4 --name agent --registry-credential-env GHCR_LOGIN ``` The workspace reads the variable when it pulls, so a new or changed variable needs no restart. **Names and digests.** A base is identified by its image digest, and the name is an optional, unique label on top. Adding an image that is already a base doesn't create a second one: the existing base takes the new reference, and the new `--name` if you pass one. `--name` takes the name over from any base that holds it, which leaves that base unnamed but otherwise untouched. Rename a base with `zbox base rename `. Unlike `--name` on add, a rename is refused with `nameConflict` while another base has the name. `zbox base set --name ''` drops the name. :::caution Adding an image that's already registered under a different name renames that base. For example, `zbox base add alpine:3.20 --name test` on a workspace that has `alpine:3.20` as `alpine` leaves you with one base named `test`. ::: ## Build a base from a Dockerfile `zbox base build` uploads a build context and builds it on the workspace, with no Docker needed on your machine: ```bash # pack the current directory and build its Dockerfile zbox base build --name app # a directory, or a ready .tar, .tar.gz, .tar.zst or .zip zbox base build ./image --name app # show what would be uploaded without building zbox base build ./image --dry-run ``` The `Dockerfile` must be at the root of the context, or in its only top directory when an archive holds one whole folder. The CLI leaves out what `docker build` would: the patterns of a root `.dockerignore`, otherwise the `.gitignore` files, and `.git` always. - **Limits**: the context can be up to 512 MiB and a build up to 30 minutes. A workspace runs one build at a time, and a second build waits for the first. When the CLI talks to the workspace's public API URL directly with the workspace API key (instead of through your Zerops login or inside the project), uploads over 50 MiB are refused ([request limits](/zbox/access#request-limits)). - **Memory**: a build has at most 512 MiB of memory, BuildKit and all `RUN` steps together, and it reserves that much of the workspace's capacity while it runs ([what a sandbox reserves](/zbox/workspace-settings#what-a-sandbox-reserves)). A build that needs more fails with `the build ran out of memory, 512 MiB` and the end of the build output. Sandboxes keep running. - **Network**: `RUN` steps have internet access, so package installs and downloads work. They run on the workspace VM, not in a sandbox, so they also reach your project's private network ([base builds](/zbox/isolation#base-builds)). - **Failures**: a failed build creates no base, and the error shows the end of the build output. - **Rebuilds**: build again with the same `--name` and the name moves to the new base. The previous build stays as an unnamed base, still used by any template or sandbox made from it, until you [remove it](#delete-bases-and-templates). - **Cache**: layers are cached on the workspace disk with no size limit, so rebuilds are fast. `zbox base prune` removes the whole cache, and the next build of every Dockerfile starts from scratch. ## Image requirements Most Linux images work as they are, both glibc-based (Debian, Ubuntu) and musl-based (Alpine). - The image must be `linux/amd64`. - No init system is needed. The Zerops Box agent becomes the sandbox's init process (it replaces any `/init` in the image), runs your command, and serves commands, files and [SSH](/zbox/networking#ssh). You don't need an SSH server in the image. - The root of the image must contain the directories `/dev`, `/run` and `/etc`. Distribution images have them, but an image built `FROM scratch` with a single binary won't boot without them. - Warm-up commands and other commands that run through a shell need `/bin/sh`. - The sandbox manages `/etc/resolv.conf` (DNS). From the image configuration, the sandbox uses the command (`ENTRYPOINT` and `CMD`), the environment variables (`ENV`), the working directory (`WORKDIR`) and the exposed ports (`EXPOSE`). Everything else is ignored, `USER` included: everything in a sandbox runs as root. When a sandbox boots, the image's command starts as its main process, with its output in `/var/log/zbox-workload.log`. If the command exits, the sandbox keeps running and you can still run commands in it. You can replace the command, the environment and the working directory per sandbox: ```bash zbox new web --base app -e PORT=3000 -w /srv -- node server.js ``` The base is the sandbox's read-only lower layer. Whatever the sandbox writes lands in its own writable layer (see [what uses the disk](/zbox/workspace-settings#what-uses-the-disk)). The `EXPOSES` column of `zbox base list` shows the image's `EXPOSE` ports. A sandbox created straight from the base opens no ports. A template made from the base opens them by default with `members` access (see [ports](/zbox/networking#ports)). ## Templates A template fixes a base, a size and a policy for the sandboxes created from it: ```bash zbox template create --base app --name dev --cpus 2 --memory 2g zbox new agent1 --template dev ``` The size, including the disk set with `--disk`, is the default for every sandbox made from the template. Without `--cpus`, `--memory` and `--disk`, a template gets the same defaults as a [new sandbox](/zbox/lifecycle#create-a-sandbox), and its size must fit the workspace policy ceilings like a sandbox's. The **template policy**, passed as a JSON file with `-f`, sets defaults and limits for those sandboxes, within the [workspace policy](/zbox/workspace-settings#workspace-policy): | Field | Effect | |---|---| | `defaultPorts` | ports a sandbox opens when its create request names none; the base's `EXPOSE` ports with `members` access when empty | | `defaultEgress` | egress rules a sandbox gets when its create request names none (see [egress](/zbox/networking#egress)) | | `allowedNetworkModes` | network modes a sandbox may use | | `maxCpus`, `maxMemoryBytes`, `maxPorts` | upper limits for a sandbox's size and its number of ports | | `maxIdleTtlSeconds`, `maxAbsoluteTtlSeconds` | upper limits for a sandbox's [TTLs](/zbox/lifecycle#idle-and-absolute-ttl) | ```json title="policy.json" { "maxCpus": 2, "allowedNetworkModes": ["PROJECT_INTERNET"], "defaultPorts": [{ "port": 3000, "protocol": "HTTP", "access": "MEMBERS" }] } ``` A create request that goes over a limit fails with `422 policyViolation`, like one over the [workspace policy](/zbox/workspace-settings#workspace-policy). ### Warm templates A **warm template** is built by booting its base once at the template's size, running an optional **warm-up command**, and saving the sandbox's memory and disk. A sandbox created from it resumes from that snapshot instead of booting: ```bash zbox template create --base default --name dev --warm --cpus 2 --memory 2g --disk 20g --warmup 'claude --version && sync' ``` A sandbox made from a warm template inherits everything from the moment of the snapshot: files the warm-up wrote, memory contents (including `/dev/shm` and other in-memory filesystems), and processes the warm-up left running in the background. Use the warm-up to load what every sandbox needs anyway, such as a language runtime, a tool's first start or caches. After resuming, the sandbox gets its own identity: hostname, network, environment variables, secrets and SSH host key, and then the image's command starts. The warm-up runs with `/bin/sh -c`, the image's environment variables and its working directory. It has no network, so anything it needs must already be in the image. It may run up to 10 minutes. If it exits non-zero, the template ends up `FAILED` with the end of the warm-up output as the reason. A workspace restart during the build fails the template too. Remove a failed template and create it again. A sandbox resumes from the snapshot only when it has exactly the template's size: CPUs, memory and disk. With any other size it cold boots from the template's saved disk, resized: the warm-up's files are there, its memory and processes aren't ([resize a sandbox](/zbox/lifecycle#resize-a-sandbox)). A sandbox with the `airgapped` network mode can't resume a snapshot either and cold boots the same way. A cold boot finds only the warm-up's files that were flushed to disk, so end the warm-up with `sync`. A warm template's snapshot takes space on the workspace disk. A template without `--warm` stores only its settings and takes none. ### Change or replace a template You can rename a template or replace its policy. Sandboxes created earlier keep the policy they were created with. ```bash zbox template rename dev dev-old # replace the policy, the name stays zbox template set dev -f policy.json ``` `zbox template set` changes only what you pass. In the API, `PUT /api/rest/template/{digest}` sets the name on every call, so send the current name to keep it. The base, the size and the warm-up can't change. To change them, create a new template. Template names are unique and, unlike base names, aren't taken over: a name another template holds is refused with `nameConflict`. Rename the old template first, create the new one under the name, then remove the old one. ## Keep bases current Every base contains the Zerops Box agent from the time the base was added. When Zerops updates the agent with the workspace software, existing bases keep their old copy. Nothing re-adds them automatically, not even a restart of the workspace. To move a base to the current agent, add it again, or rebuild a built base: ```bash zbox base add python:3.12-slim --name python zbox base build ./image --name app ``` Sandboxes created afterwards from the base, or from a template without `--warm`, get the new agent. Sandboxes that already exist stay on the image they started with. A warm template keeps the agent in its memory snapshot, so replace it with a new template (see [above](#change-or-replace-a-template)). ## The guest kernel Every sandbox runs the same Linux kernel, 6.18.53, which Zerops ships with the workspace software. You can't pick or replace it per base or sandbox. `uname -r` in a sandbox shows it. The kernel has everything built in and can't load kernel modules. It provides cgroup v2, namespaces including user namespaces, overlayfs, FUSE and TUN. It doesn't provide KVM, so you can't run virtual machines inside a sandbox, and there's no swap. For running Docker, see [Docker in sandboxes](/zbox/docker). ## Delete bases and templates ```bash zbox template remove dev-old zbox base remove app ``` A template can't be removed while a sandbox that isn't `TERMINATED` or `ARCHIVED` uses it, or while it's still building. A base can't be removed while a template or such a sandbox uses it. Both cases fail with `inUse`, and the error says what holds it. Parked sandboxes count, so remove them or the templates first ([sandbox statuses](/zbox/lifecycle#sandbox-statuses)). A removed base or template disappears from every list, gives its name up, and its files leave the workspace disk. Adding the same image again brings the base back. Bases, warm template snapshots and the build cache all live on the workspace disk. `zbox workspace` shows the disk in use; see [capacity](/zbox/workspace-settings#capacity) for how much is available.