Modal Provider
Read this when you:
- choose
provider: modal; - configure the Modal app, sandbox image, working directory, or Python binary;
- change
internal/providers/modal.
Modal is a delegated-run provider. Crabbox shells out to the local Modal Python client to manage Sandbox lifecycle, upload files, query status, and run commands. Modal owns the sandbox state and process transport; Crabbox owns local config, repo claims, sync manifests and guardrails, slugs, timing summaries, and the normalized list/status output. There is no SSH target.
#When to use
Use Modal when commands should run in Modal Sandboxes and you do not need a Crabbox SSH box. Reach for a provisioned SSH provider (AWS, Hetzner, Azure, GCP, static SSH) or another delegated provider instead when you need crabbox ssh, VNC, code-server, or Actions hydration β none of those surfaces exist on Modal.
#Prerequisites
- Install the Modal Python client:
pip install modal. - Authenticate Modal locally:
python3 -m modal setup, or exportMODAL_TOKEN_ID/MODAL_TOKEN_SECRET. The Python client runs with the current process environment, so any standard Modal auth state is picked up.
#Auth
Crabbox does not take Modal token flags and never passes tokens on the command line. The local Python client reads normal Modal auth state β from python3 -m modal setup or from the environment:
export MODAL_TOKEN_ID=...
export MODAL_TOKEN_SECRET=...
Rotate these values if they were ever pasted into a chat, shell history, issue, or log.
#Commands
crabbox warmup --provider modal --modal-app my-app --modal-image python:3.13-slim
crabbox run --provider modal -- pnpm test
crabbox run --provider modal --id swift-crab --shell 'pnpm install && pnpm test'
crabbox status --provider modal --id swift-crab
crabbox stop --provider modal swift-crab
#Live Smoke
Run the shared smoke only after the Modal Python client is installed and authenticated:
CRABBOX_LIVE=1 CRABBOX_LIVE_PROVIDERS=modal CRABBOX_LIVE_COORDINATOR=0 CRABBOX_LIVE_REPO=/path/to/my-app scripts/live-smoke.sh
The smoke exits before any Modal warmup, status, run, list, or stop call when the configured Python binary cannot import the Modal client. With a working client and auth state, it creates one sandbox, waits for status, runs one no-sync command, lists normalized inventory, and stops the lease.
#Config
provider: modal
target: linux
modal:
app: my-app
image: python:3.13-slim
workdir: /workspace/crabbox/work
python: python3
Provider flags:
--modal-app
--modal-image
--modal-workdir
--modal-python
Environment overrides:
CRABBOX_MODAL_APP
CRABBOX_MODAL_IMAGE
CRABBOX_MODAL_WORKDIR
CRABBOX_MODAL_PYTHON
Defaults: app crabbox, image python:3.13-slim, workdir /workspace/crabbox, Python binary python3.
#Lifecycle
warmup/runwithout--idcreates a Modal Sandbox in the configuredmodal.appfrommodal.image, with the sandbox timeout and Crabbox ownership tags. Crabbox stores a local claim with a normalcbx_...lease ID and a friendly slug.- The sandbox timeout is derived from
--ttl: it defaults to 5 minutes when no TTL is set and is capped at 24 hours. - By default
runarchive-syncs the working tree: Git manifest β localtar -czfβ upload to/tmp/crabbox-modal-sync-*.tgzin the sandbox β in-sandboxtar -xzfintomodal.workdir. - The user command runs through
Sandbox.execin abash -lcwrapper that firstcds intomodal.workdir, streaming stdout/stderr back through Crabbox. - One-shot sandboxes are terminated after a
runthat did not pass--keep.--keepand--keep-on-failureretain the sandbox untilcrabbox stop.
Note: warmup always keeps the sandbox until an explicit crabbox stop. If you pass --keep=false to warmup, Crabbox prints a warning and still keeps it.
#Capabilities
- SSH: no.
- Crabbox sync: yes β archive sync through Modal Sandbox upload + exec.
- Provider sync: no separate Modal sync step.
- Desktop / browser / code: no.
- Actions hydration: no.
- Coordinator (broker): no β Modal always runs direct from the CLI.
- URL bridge / pond: no advertised URL bridge. Modal does not expose a per-sandbox ingress URL through Crabbox today.
#Gotchas
- IDs can be a Crabbox slug, a
cbx_...lease ID, or a raw Modal sandbox ID (when that sandbox carries Crabbox tags). --classand--typeare rejected. The configured Modal image owns the runtime contents and resources.modal.workdirmust resolve to an absolute, dedicated directory. Broad roots such as/,/tmp,/home, and/workspace(and other system roots) are rejected before any sync or command runs.--checksumis rejected because Modal has no SSH/rsync target. Large-sync guardrails still apply;--force-sync-largeis honored for intentional large archive syncs.- Use
--sync-onlyto pre-upload the archive into a kept sandbox before a later command. - Delegated run/sync options that need an SSH target are rejected:
--script/--script-stdin,--fresh-pr,--full-resync,--env-helper,--capture-stdout/--capture-stderr,--capture-on-fail,--download,--artifact-glob,--require-artifact,--emit-proof, and--stop-after. - Forwarded environment values are written to a temporary shell profile, uploaded into
/tmp, sourced (set -a) for the command, and removed best-effort afterward. They are never placed on the local Python process argv.