Create, run, and manage boxes — the core sandbox lifecycle object in Forkr.
A box is a single-replica Kubernetes Deployment with a headless Service and an Ingress, backed by a copy-on-write btrfs root filesystem. It boots from a base image, gives you a full Linux environment you can exec into, and persists its filesystem across stop and resume. You manage boxes through the 4kr CLI.Every command on this page targets a box by its ref. Read reference resolution first if a command can’t find your box.
The CLI needs a base URL and a token to reach the API. The URL resolves in this priority:
The --url flag.
The selected profile (the --profile flag or FORKR_PROFILE, plus FORKR_PROFILE_<NAME>_URL).
FORKR_URL.
FORKR_API_URL.
Most of the time you don’t pass any of these. 4kr config set profile <name> stores a default profile and every command targets it. Use the global --profile <name> flag to target a different profile for a single command without changing the default. --profile works on any command and is equivalent to setting FORKR_PROFILE for that invocation.A profile is selected by name. Profile variables are named FORKR_PROFILE_<UPPERCASED_NAME>_<SUFFIX>, where any non-alphanumeric character in the name becomes _. The suffixes include URL, TOKEN, SSH_HOST, INSECURE, and IP.
export FORKR_PROFILE=stagingexport FORKR_PROFILE_STAGING_URL=https://api.forkr.example.comexport FORKR_PROFILE_STAGING_TOKEN=...4kr list
If a profile is selected but its _URL is unset, the CLI errors:
Error: profile "staging" is selected but FORKR_PROFILE_STAGING_URL is not set
Local setup config lives in .forkr/config.yaml (or $FORKR_SETUP_DIR/config.yaml) and is read and written by 4kr config set/get/list/unset/path/profiles. When FORKR_PROFILE matches the config’s profile/env key, the CLI auto-derives api_url (or https://api.<forkr_domain>), ssh_host (or ssh.<forkr_domain>), and the token.
Pass --base to start from a template image, or --fork to clone an existing box. You can’t use both:
Error: --base cannot be used with --fork
A --fork clone snapshots the source root and each non-system data volume into new box-<name>-<vol> volumes, and inherits the source’s base, config, tags, env (merged with your overrides), port, and termination grace. You can’t clone a box from itself.Tagged base builds resolve through the alias:tag form:
--data attaches a data volume at a mount path. The mount path must be absolute and cannot sit under /proc, /sys, /dev, /run, /etc, or /volumes. The data volume must be in the same project as the box, and each mount name must be unique.
4kr show <box> prints details: name, project, status, base, url, port, timestamps, published hosts, tags, data mounts, and (from config) cpus, ram_mb, and storage_gb.
4kr update <box> [SETTINGS...] takes positional KEY=VALUE settings plus --env, --tag, -p, and -j. The CLI merges all settings into the environment map and sends them.
The server acts only on a fixed allowlist. Everything else you pass is silently dropped.
The server applies, from the settings you send:
PORT — updates the Service/Ingress port (1–65535).
CPU — updates the CPU resource.
MEMORY — updates the memory resource.
TERMINATION_GRACE_PERIOD_SECONDS or TERMINATION_GRACE_SECONDS — up to 86400.
Tags.
Every other KEY=VALUE is parsed but never applied — there is no path to mutate container environment variables after create. If nothing in the allowlist is present, the server returns 400 no_updates ("no updates provided").
To change a box’s container environment after creation, recreate the box — optionally with --fork to keep the existing root filesystem.
4kr delete <box...> (alias rm) deletes one or more boxes. Per box it prints <name>: deleted or <name>: error: <msg>; any error exits non-zero.
Flag
Purpose
--force, -f
Delete even if the box is running.
--project, -p / --json, -j
Project / JSON output.
Without --force, deleting a running box returns 409 fork_running ("box is running"). Delete is idempotent; partial cleanup returns 202 with a background retry.
4kr publish <box> [subdomain] exposes the box on a custom hostname with TLS. unpublish removes it.
Flag
Purpose
subdomain
Required unless --host. @ means the apex (bare domain).
--host <HOST>
Explicit full hostname (conflicts with subdomain).
--domain <DOMAIN>
Override the default publish domain (conflicts with --host).
--force, -f
Take over a subdomain already published elsewhere.
--project, -p / --json, -j
Project / JSON output.
The host resolves as: --host wins; otherwise {subdomain}.{domain} where the domain defaults to the server’s publish domain; @ resolves to the bare domain. On success the CLI prints OK (or {"host": ...}).TLS uses a wildcard secret if the cluster has one configured, otherwise cert-manager issues a per-host certificate via the cluster issuer. Publishing a host already in use returns 409 unless you pass --force. Unpublishing a host not owned by the box returns 403.
Box-level 4kr publish has no --port flag. Port-bound publishing for long-running services is covered on the Services page.
Boxes reach each other by name. The Service is headless (clusterIP: None): it exposes all ports and resolves DNS straight to the pod IP, so traffic is not load-balanced.From inside a box in project P:
You request
Resolves to
<box>
A box in the same project P.
<box>.<other-project>
A box in another project.
<box>.default
A box in the default project.
any dotted FQDN
External DNS.
# from inside a box, reach a sibling box named "db" on port 5432psql -h db -p 5432 -U postgres
Because a box is single-replica, its pod IP changes on stop/resume. Reconnect after the target box resumes.
More on the in-box runtime, PATH, and environment variables is on the Environment page.
4kr exec <box> [flags] -- <cmd...> runs a command in a box.
Flag
Purpose
--user <NAME>
Run as a project user.
--env KEY=VALUE
Set an environment variable (repeatable).
--wd <DIR> (alias --cwd)
Working directory.
--sh (alias --bash)
Treat the args as a shell string.
--bg
Run detached in the background; returns a PID.
--project, -p / --json, -j
Project / JSON output.
Foreground (default) runs an interactive exec session and preserves the command’s exit code. If you give no command and stdin isn’t a TTY (and you didn’t pass --bg), it defaults to bash. --json is rejected in foreground mode:
Error: --json is not supported in foreground mode
Background (--bg) starts a detached process and prints pid=<n>. Use - as the box name to run in an ephemeral box.
4kr ps <box> runs ps inside the box (plus any extra args after --).4kr logs <box> --pid <PID> (alias tail) reads a background process’s log. --pid is required:
4kr cp ./app.tar web-1:/srv/app.tar4kr get web-1:/var/log/app.log ./app.log4kr put ./config.yaml web-1:/etc/app/config.yaml
4kr cp <src> <dest> transfers files. 4kr get and 4kr put are aliases. Exactly one side must be a box path written as box:/path (the :/ is required) — both-remote or neither-remote errors.Qualified refs work: project:box:/path, .:/path (current box), and -:/path (ephemeral). File permissions are preserved in both directions.
Directories aren’t supported yet:
Error: only files are supported (directories require -r, not yet supported)
Data-volume gotcha:4kr cp writes into the box root filesystem and the system volumes only. A path under a custom data-volume mount is not redirected — it lands in the root subvolume, where the live data mount shadows it at runtime. So 4kr cp does not write into attached data volumes. To put files into a data volume, run 4kr exec inside the box (for example, pipe through a shell command).
The in-box environment snapshot file is /etc/boxes-env. Injected variables include FORKR_PROJECT, BOX_NAME, FORKR_API_URL, FORKR_API_HOST, plus node/host IPs and TERM. See Environment for the full runtime layout.
Pass - as the box name to exec, console, or cp/get/put to auto-create a box, run, and auto-delete it.
4kr exec - -- python3 --version4kr console -
The generated name is ephemeral-<pid>-<millis>, and the CLI prints ephemeral fork: <name> to stderr. Use project:- to target a specific project. --json isn’t supported for ephemeral exec, and ephemeral deletes always force.
4kr inject <box> copies local credentials into the box.
Flag
Default
--codex-auth <PATH>
~/.codex/auth.json
--codex-toml <PATH>
~/.codex/config.toml
--ssh-key <PATH>
First of ~/.ssh/id_ed25519, id_rsa, id_ecdsa
--project, -p
Target project
Destinations inside the box: /root/.codex/auth.json (chmod 600), /root/.codex/codex.toml (renamed from config.toml), /root/.ssh/<keyname> (chmod 600) plus .pub (644), and /root/.gitconfig. Git config is auto-discovered from GIT_CONFIG_GLOBAL, $XDG_CONFIG_HOME/git/config, ~/.config/git/config, or ~/.gitconfig. It seeds /root/.ssh/known_hosts with GitHub’s keys and tests ssh -T git@github.com. The final line reads:
OK: injected codex auth/config, ssh keypair[, git config] into <project>:<box>
--json / -j is supported on create, list, show, update, publish, unpublish, delete, stop, resume, background exec, non-follow logs, port list, port wait, and the user list/create/update commands.It is not supported on foreground exec, ephemeral exec, logs --follow, console, ssh, cp/get/put, inject, or user delete.