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.

Projects and reference resolution

Every box lives in a project. A box’s identity is the pair (project, name), so the same name can exist in different projects.

Project resolution

When you don’t pass --project, the CLI resolves the project in this order:
  1. The --project / -p flag.
  2. The FORKR_PROJECT environment variable.
  3. The git repository root directory name, if it’s a valid project name.
  4. "default".
Run 4kr project current to print the resolved project.
4kr project current

Box ref forms

A box ref accepts several forms:
FormMeaning
nameBox name in the default project.
project:nameCanonical explicit project (colon).
project.nameSame, dot sugar (colon is tried first, then dot).
.The current box, from FORKR_PROJECT + BOX_NAME (errors if unset).
-An ephemeral box (see Ephemeral boxes).

Naming rules

  • Project: 1–63 characters, a-z, 0-9, -; starts and ends alphanumeric; no --.
  • Box name: a DNS label — 1–63 characters, a-z, 0-9, -; starts and ends alphanumeric.
  • Tag keys and values: may also use _ and ..

Target selection and profiles

The CLI needs a base URL and a token to reach the API. The URL resolves in this priority:
  1. The --url flag.
  2. The selected profile (the --profile flag or FORKR_PROFILE, plus FORKR_PROFILE_<NAME>_URL).
  3. FORKR_URL.
  4. 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=staging
export FORKR_PROFILE_STAGING_URL=https://api.forkr.example.com
export 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.

Create a box

4kr create web-1 --base box-base --cpu 2 --memory 2G
4kr create <name> provisions a new box. On success it prints OK, or raw JSON with --json.
FlagPurpose
--base, -b <BASE>Base image to create from. Defaults to box-base.
--fork, -f <BOX>Clone another box’s filesystem instead. Mutually exclusive with --base.
--tag, -t key=valueAttach a tag (repeatable). Used by list -t filtering.
--data, -d NAME=/mount/path[:ro|:rw]Attach a data volume (repeatable, default :rw).
--env, -e KEY=VALUESet a container environment variable (repeatable).
--cpu <CPU>CPU request, e.g. 2 or 500m.
--memory <MEM>Memory request, e.g. 2G or 512M.
--project, -p <PROJECT>Target project.
--json, -jJSON output.

Base vs clone

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:
4kr create db-1 --base box-base-postgres-17:0123abcd
See Snapshots & bases for building and tagging bases.

Data volumes

--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 create web-1 --data assets=/srv/assets:ro --data uploads=/srv/uploads
See Data volumes for managing volumes, attachments, and backups.
Reserved environment keys are rejected at create time: FORKR_PROJECT, BOX_NAME, FORKR_API_URL, FORKR_API_HOST, and TERM.

List, show, update

list

4kr list
4kr list --all --tag team=web
4kr list (alias ls) prints a table with NAME STATUS BASE URL PUBLISHED columns. The PUBLISHED column shows https://<host> entries joined by commas.
FlagPurpose
--all, -aList across all projects (adds a PROJECT column).
--quiet, -qNames only; with --all, prints project:name.
--tag, -t key=valueFilter by tag (repeatable, AND).
--project, -p / --json, -jProject / JSON output.

show

4kr show web-1
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.

update — what it actually changes

4kr update web-1 PORT=8080 CPU=4 MEMORY=4G
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.

Stop, resume, delete

stop and resume

4kr stop web-1
4kr resume web-1
4kr stop tears down the pod but preserves the filesystem. 4kr resume recreates the pod and re-injects environment variables. Both print OK.
The pod IP changes on stop/resume because a box is single-replica. Clients reaching a box over inter-box DNS should reconnect after a resume.

delete

4kr delete web-1 web-2
4kr delete web-1 --force
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.
FlagPurpose
--force, -fDelete even if the box is running.
--project, -p / --json, -jProject / 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.

Publish a custom hostname

4kr publish web-1 app
4kr publish web-1 @
4kr publish web-1 --host app.example.com
4kr publish <box> [subdomain] exposes the box on a custom hostname with TLS. unpublish removes it.
FlagPurpose
subdomainRequired 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, -fTake over a subdomain already published elsewhere.
--project, -p / --json, -jProject / 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.

Inter-box DNS

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 requestResolves to
<box>A box in the same project P.
<box>.<other-project>A box in another project.
<box>.defaultA box in the default project.
any dotted FQDNExternal DNS.
# from inside a box, reach a sibling box named "db" on port 5432
psql -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.

Run commands with exec

4kr exec web-1 -- ls /srv
4kr exec web-1 --sh -- "cat /etc/os-release | head"
4kr exec web-1 --bg -- ./long-job.sh
4kr exec <box> [flags] -- <cmd...> runs a command in a box.
FlagPurpose
--user <NAME>Run as a project user.
--env KEY=VALUESet an environment variable (repeatable).
--wd <DIR> (alias --cwd)Working directory.
--sh (alias --bash)Treat the args as a shell string.
--bgRun detached in the background; returns a PID.
--project, -p / --json, -jProject / 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.

Inspect with ps and logs

4kr ps web-1
4kr logs web-1 --pid 4123 --follow
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:
FlagPurpose
--pid <PID>Process to read (required).
--lines, -l <N>Number of lines (default 100).
--follow, -fTail in follow mode. Cannot combine with --json.
--project, -p / --json, -jProject / JSON output.

Open a shell with console

4kr console web-1
4kr console - --command "python3"
4kr console <box> opens an interactive shell. Use - for an ephemeral box that’s deleted on exit.
FlagPurpose
--tty / --no-ttyForce or disable an interactive TTY.
--raw / --no-rawForce or disable local terminal raw mode.
--term <TERM>Terminal type (defaults to TERM or xterm-256color).
--user <NAME>Run as a project user.
--command <CMD>Run this instead of a login shell.
--wd <DIR> (alias --cwd)Working directory.
--log-console-debugPrint transport details to stderr.
--project, -pTarget project.
console has no --json.

SSH via the gateway

4kr ssh web-1
4kr ssh web-1 --info
4kr ssh web-1 -- -A
4kr ssh <box> connects through the SSH gateway. The username is <project>--<box>@<host>.
FlagPurpose
--host <HOST> (env FORKR_SSH_HOST)Gateway host. Otherwise resolved from the profile’s _SSH_HOST or _URL host.
--port <PORT> (env FORKR_SSH_PORT)Gateway port (default 2222).
--infoPrint the resolved ssh command and exit.
-- <ssh-args>Extra arguments passed to ssh.
On Unix, 4kr ssh execs into the real ssh client. It runs without API config.

Ports

4kr port list web-1
4kr port wait web-1 8080 --timeout 30
4kr port <list|wait> (alias ports) inspects listening ports.
  • port list <box> (alias ls): --tcp, --udp (default both), -p, -j. Columns: PROTOCOL ADDRESS PORT.
  • port wait <box> <port>: --tcp (default) or --udp, --timeout <s> (default 60, 0 = forever), --interval <s> (default 1), -p, -j. Passing both --tcp and --udp, or --interval 0, errors.

Copy files

4kr cp ./app.tar web-1:/srv/app.tar
4kr get web-1:/var/log/app.log ./app.log
4kr 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).

System volumes

Every box auto-mounts these volumes:
MountScope
/box/binGlobal, on PATH.
/box/allGlobal shared data.
/box/projPer-project; /box/proj/bin is on PATH.
/homePer-project user homes.
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.

Project users

4kr user create deploy --shell /bin/bash
4kr user list
4kr user <list|create|update|delete> manages users within a project. Created users get a synthesized UID/GID and a home at /home/<user>.
  • user create <name> [--shell <SHELL>] — prints name<TAB>uid<TAB>home.
  • user list — prints name uid home, tab-separated.
  • user update <name> [--shell <SHELL>] — updates the shell.
  • user delete <name> — removes the user (no --json).
All except delete accept -p and -j. Reference a user from exec and console with --user.

Ephemeral boxes

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 --version
4kr 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.

Inject credentials

4kr inject web-1
4kr inject web-1 --ssh-key ~/.ssh/id_ed25519
4kr inject <box> copies local credentials into the box.
FlagDefault
--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, -pTarget 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>
inject has no --json.

JSON output

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

CLI reference

Run 4kr docs to print the bundled “Using forkr” guide. The full HTTP surface is documented in the API reference.

Next steps

Environment

The in-box runtime: PATH, /box/* volumes, env vars, and DNS.

Data volumes

Attach, share, and back up persistent data across boxes.

Services

Expose long-running services on a public URL with a port.

Snapshots & bases

Capture filesystem state and build tagged base images.