Run Codex against a Hugging Face model on Modal. The wrapper supports Modal's managed Endpoints and a generic self-managed SGLang app for models outside the catalog. Codex is launched with an isolated, Modal-only configuration; wrapper-owned resources are cleaned up on exit.
Requires Python 3.10+, codex-cli 0.146.1, the codex executable on PATH, and a
Modal account. The launcher creates .venv and installs the pinned dependencies.
# macOS/Linux
./codex-modal.sh setup
./codex-modal.sh --modal-dry-run
./codex-modal.sh# Windows PowerShell
.\codex-modal.ps1 setup
.\codex-modal.ps1 --modal-dry-run
.\codex-modal.ps1The remaining examples use the POSIX launcher. On Windows, substitute
.\codex-modal.ps1 for ./codex-modal.sh; every option is the same.
setup logs into Modal if needed and creates a proxy token. Credentials come from
MODAL_PROXY_TOKEN (or separate ID/secret variables), the OS keyring, or an
owner-only, git-ignored local fallback. For an RBAC environment, use
setup --modal-env prod.
The default preset is deepseek-ai/DeepSeek-V4-Flash-0731.
./codex-modal.sh --modal-list
./codex-modal.sh --modal-pick
./codex-modal.sh --modal-model Qwen/Qwen3.6-27BFor a catalog-compatible fine-tune, give Modal the supported base architecture and the custom checkpoint:
./codex-modal.sh \
--modal-model Qwen/Qwen3.6-27B \
--modal-custom-hf-repo your-org/your-finetune \
--modal-custom-hf-revision <exact-commit>For a model outside Modal's catalog, deploy the generic SGLang app. GPU topology is
required because it cannot be inferred safely for arbitrary weights. Start with the
model card's known-good SGLang command and repeat --modal-sglang-arg for its engine
flags.
./codex-modal.sh \
--modal-self-managed \
--modal-model your-org/your-model \
--modal-model-revision <exact-commit> \
--modal-gpu B200:2 \
--modal-sglang-arg '--trust-remote-code' \
--modal-startup-timeout 7200Private repositories can use --modal-hf-token-env HF_PRIVATE_TOKEN. Large
fine-tunes can reuse unchanged shards from an existing Modal Volume with
--modal-base-volume and --modal-base-volume-path; run --modal-help for the full
surface. SGLang architecture support, parsers, quantization, tensor parallelism, and
GPU memory must still match the checkpoint.
Repeatable self-managed deploys live in self-managed-catalog.json (a models
array of full deploy specs). Bring one up by index or name; the served endpoint
URL is printed and the app is left warm (subject to scale-to-zero):
./modal-deploy.sh self-managed-catalog.json glm-5.3-flash-uncensored
./modal-deploy.sh self-managed-catalog.json 0 --gpu H200:4 --dry-runAdding a model is one catalog entry, not new code. Afterwards, attach by the
entry's endpoint_name with --modal-use-app <name>, or from another client such
as blue-green-red via --model <role>=modal:<endpoint_name>.
Self-managed apps default to zero warm containers, at most one serving container, and GPU scale-to-zero after 300 idle seconds. Set 15 minutes with:
./codex-modal.sh --modal-self-managed <other-options> --modal-scaledown-window 900Normally, the wrapper stops only the exact endpoint/app it created and deletes only
its owned temporary Volume. A detached watchdog and cleanup recover resources after
a local crash. --modal-keep-endpoint keeps a deployment intentionally; idle
scale-to-zero still applies.
Attach without taking ownership or changing the deployed scale-down policy:
./codex-modal.sh --modal-use-endpoint <endpoint-name-or-host>
./codex-modal.sh --modal-use-app <app-name> --modal-model your-org/your-modelUseful operator hooks:
setup / cleanup credentials / stale-resource recovery
--modal-dry-run validate without login or provisioning
--modal-startup-timeout SECONDS provisioning/readiness timeout
--modal-routing-region REGION request-proxy placement
--modal-compute-region REGION repeatable compute placement
--modal-no-history disable isolated Codex transcripts
--modal-help complete wrapper CLI
Each run uses a git-ignored CODEX_HOME containing exactly one model/provider. The
wrapper pins the main, review, and subagent models to Modal; removes OpenAI credentials
and base-URL overrides; disables analytics, feedback, OpenTelemetry exporters, prompt
logging, web search, plugins/apps, memories, and remote compaction; and rejects Codex
arguments that could override the provider. Project instructions and ordinary Codex
commands still work.
--docker runs Codex in a throwaway local container with approvals and Codex's
own sandbox bypassed: the agent is unrestricted inside the box, and the box is the
boundary. The image is a general dev/cyber box (compilers, Python/Node/Go/Rust,
network tools, and a headless Chromium the agent can drive). It has internet but
no view of the host — it sits alone on a Docker --internal network whose only
peer is an egress broker allowing public addresses on ports 80/443 only (host,
LAN, loopback, link-local, metadata, CGNAT refused; second-layer in-container
iptables default-deny). No host path is mounted (per-run volumes, read-only
rootfs, caps dropped, non-root, no Docker socket), and the broker attaches the
Modal token per request so the agent can use the model but never read it.
./codex-modal.sh --docker # Modal model, Codex in a container
./codex-modal.sh --docker --docker-shell # a shell in the same sandbox
./codex-modal.sh --docker-prune # remove leftovers after a crashEach run exports raw logs (session rollout, agent-console.log, egress.jsonl)
to .codex-modal/docker-runs/<id>/. Run --modal-help for the full option list;
--docker-upstream targets any OpenAI-compatible server with no Modal at all.
The test suite and dry run do not create paid Modal resources:
python -m unittest discover -s tests -v
./codex-modal.sh --modal-dry-runSee Modal Endpoints, Modal Servers, and Codex configuration.