This repo runs Codex CLI inside a Linux container and gives each session its own git worktree, so you can run multiple Codex sessions against the same repo without sharing a checkout.
cxhereand./scripts/build-local.shsupport Applecontainer, Docker, and explicit local mode.autoprefers a ready Applecontainerruntime on Apple silicon running macOS 26+, otherwise a ready Docker daemon.- Docker and Apple
containeruse separate image stores. Build both withCX_BUILD_RUNTIME=allif you want instant fallback between them. - Apple
containersessions default to a lighter1280x720x24headed display for more reliable Playwright + ffmpeg recording. Docker keeps1920x1080x24.
git- Docker Desktop or Apple
container codexinPATHif you wantCXHERE_RUNTIME=local- For Apple
container: Apple silicon, macOS 26+,containerinstalled, andcontainer system start - Optional host config reused by sessions:
gh,~/.ssh,SSH_AUTH_SOCK,ngrok
The image tag is codex-cli:local.
./scripts/build-local.shTypical build flows:
container system start
CX_BUILD_RUNTIME=container ./scripts/build-local.sh
CX_BUILD_RUNTIME=docker ./scripts/build-local.sh
CX_BUILD_RUNTIME=all ./scripts/build-local.sh| Variable | Default | Applies to | Effect |
|---|---|---|---|
CX_BUILD_RUNTIME |
auto |
./scripts/build-local.sh |
Selects container, docker, or all. auto prefers a ready Apple runtime, then a ready Docker daemon. |
CX_BUILD_CPUS |
unset | Apple container builds |
Passed through as container build --cpus. |
CX_BUILD_MEMORY |
unset | Apple container builds |
Passed through as container build --memory. |
Source the helper script from your shell config:
source /path/to/sandbox-docker/scripts/codex-worktrees.zshReload the shell config you actually use:
source ~/.zshrc
# or
source ~/.bashrcOptional setup:
mkdir -p ~/.codex/rules
cp /path/to/sandbox-docker/default.example.rules ~/.codex/rules/default.rulesmkdir -p ~/.codex
cat /path/to/sandbox-docker/config.example.toml >> ~/.codex/config.toml| Command | What it does |
|---|---|
cxhere <worktree-name> |
Create or reuse a dedicated git worktree and launch Codex there. |
cxhere <worktree-name> <session-id> |
Reuse the worktree and resume an existing Codex chat. |
cxclose <worktree-name> |
Remove a clean worktree and delete its branch. |
cxkill <worktree-name> |
Stop running container session(s) for that worktree without removing the worktree. |
cxlist |
List managed Codex worktrees and show whether each one is ok, locked, or prunable. |
Typical usage:
cxhere mpm/my-feature
CXHERE_RUNTIME=container cxhere mpm/my-feature
CXHERE_RUNTIME=docker cxhere mpm/my-feature
CXHERE_RUNTIME=local cxhere mpm/my-feature
CXHERE_NO_DOCKER=1 cxhere mpm/my-featureWith shell completion enabled, cxhere, cxclose, and cxkill autocomplete known Codex worktree branch names.
| Variable | Default | Applies to | Effect |
|---|---|---|---|
CXHERE_RUNTIME |
auto |
cxhere, cxkill |
Selects container, docker, or local. auto prefers a ready Apple runtime, then a ready Docker daemon. |
CXHERE_NO_DOCKER |
unset | cxhere, cxkill |
Legacy alias for local-mode runtime selection. |
CXHERE_GH |
1 |
Containerized sessions | Reuses host GitHub auth. Docker mounts ~/.config/gh; Apple container copies it into a writable session-local config and bootstraps it from GH_TOKEN, GITHUB_TOKEN, or gh auth token when available. |
GH_TOKEN / GITHUB_TOKEN |
unset | Containerized sessions | Preferred GitHub token source forwarded into the session before falling back to gh auth token. |
CXHERE_SSH |
1 |
Containerized sessions | Mounts host ~/.ssh read-only into the session. |
CXHERE_SSH_AGENT |
1 |
Containerized sessions | Forwards the host ssh-agent when the host exposes a socket. Docker uses a bind-mounted socket; Apple container uses native --ssh forwarding. |
SSH_AUTH_SOCK |
host-dependent | Containerized sessions | Used as the host-side source socket for agent forwarding when CXHERE_SSH_AGENT=1. |
CXHERE_NGROK |
1 |
Containerized sessions | Mounts the detected host ngrok config into the session. |
CXHERE_NGROK_CONFIG_DIR |
auto-detect | Containerized sessions | Overrides ngrok config discovery. Checked paths are ~/.config/ngrok, ~/Library/Application Support/ngrok, and ~/.ngrok2. |
| Variable | Default | Applies to | Effect |
|---|---|---|---|
CXHERE_CONTAINER_CPUS |
4 |
Apple container sessions |
Sets the VM CPU allocation. |
CXHERE_CONTAINER_MEMORY |
4G |
Apple container sessions |
Sets the VM memory allocation. |
CXHERE_CONTAINER_XVFB_SCREEN |
1280x720x24 |
Apple container sessions |
Default headed display size when XVFB_SCREEN is not set. |
CXHERE_CONTAINER_REPO_ROOT_MODE |
rw |
Apple container sessions |
Mount mode for the host repo root at its absolute path. Leave this at rw for Git worktree compatibility. |
XVFB_SCREEN |
runtime-specific | Containerized sessions | Overrides the display size directly for one run. Docker defaults to 1920x1080x24; Apple container defaults to 1280x720x24. |
CXHERE_PIDS_LIMIT |
2048 |
Docker sessions | Passed as Docker --pids-limit. |
CXHERE_TMPFS_TMP_SIZE |
2g |
Docker sessions | Size of the Docker /tmp tmpfs mount. |
CXHERE_TMPFS_HOME_SIZE |
2g |
Docker sessions | Size of the Docker /home/codex tmpfs mount. |
CXHERE_SHM_SIZE |
1g |
Docker sessions | Passed as Docker --shm-size. |
CXHERE_ULIMIT_NPROC |
8192 |
Docker sessions | Passed as Docker --ulimit nproc. |
CXHERE_ULIMIT_NOFILE |
1048576 |
Docker sessions | Passed as Docker --ulimit nofile. |
| Variable | Default | Applies to | Effect |
|---|---|---|---|
HARNESS_CAPTURE_WITH_FFMPEG |
1 |
Containerized sessions | Keeps ffmpeg capture enabled by default. |
HARNESS_CAPTURE_AUDIO_FORMAT |
pulse |
Containerized sessions | Keeps PulseAudio as the default audio capture source. |
CODEX_DISABLE_PULSEAUDIO |
unset | Session entrypoint | Skips internal PulseAudio startup. |
CODEX_DISABLE_XVFB |
unset | Session entrypoint | Skips internal Xvfb startup. |
- Worktrees live next to the repo in a sibling directory named
<repo-name>-worktrees/<worktree-name>, with/replaced by__in the directory name. cxherereuses an existing branch if the branch already exists and no worktree exists for it.- If a worktree already exists,
cxherelooks for a running session for that worktree in the selected runtime. New sessions are discovered by stable labels; Docker also falls back to bind-mount discovery so older unlabeled sessions are still found. - If exactly one session already exists on the current
codex-cli:localimage,cxhereexits cleanly instead of starting a duplicate session. - If exactly one session exists on an older
codex-cli:localimage,cxherereplaces it so the worktree picks up the rebuilt image. - If the same worktree is already running in the other runtime,
cxherestops and tells you to either reuse that runtime or runcxkill. - If you explicitly request a runtime and it is unavailable, the command exits with that runtime's error instead of silently switching.
cxkillstops sessions for one worktree without deleting the worktree. WithCXHERE_RUNTIME=auto, it checks all ready runtimes.cxcloserefuses to remove a locked worktree, a dirty worktree, or a worktree with Git lock files still present.
- Containerized sessions mount the worktree at
/workspace. - Docker sessions also mount the main repo path read-only and
<repo>/.gitread-write so git worktree metadata still works. - Apple
containersessions instead mount the full main repo path read-write at its host absolute path so Git worktree metadata resolves without relying on nested bind mounts. - Docker sessions forward the host ssh-agent by bind-mounting the socket. Apple
containersessions use the runtime's native--sshforwarding instead. ~/.gitconfigand~/.codexare mounted into containerized sessions by default.- Root-level
.env*files from the main repo are copied into the worktree when it is created. If present,.env.cx.localis passed to the session as an env file. cxhereoffers to createdocs/PLANS.mdfrom the project template when it is missing.cxhereoffers to create$CODEX_HOME/AGENTS.mdfrom the global template when it is missing.cxhereensures your Codex config has a trusted/workspaceproject entry and prompts before writing it.- If
.pw-browsersis under/workspace,cxhereoffers to add it to the repo.gitignore. - On Docker runs,
cxheremay also offer to copyseccomp_profile.example.jsontoseccomp_profile.jsonand add it to the repo.gitignore.
- Base image:
ubuntu:25.10 - Node.js:
25.8.1 - Included tooling: Playwright Linux dependencies,
xvfb, PulseAudio,ffmpeg,ngrok, and R - Browser cache path:
PLAYWRIGHT_BROWSERS_PATH=/workspace/.pw-browsers - Entry point behavior: starts PulseAudio with a null sink, exposes the monitor source for ffmpeg audio capture, starts Xvfb, then execs
codex
Install browsers per project when needed:
npx playwright install chromiumIf you want the migration research and validation notes behind the current runtime behavior, see docs/apple-container-migration.execplan.md.