Development environment bootstrap files and the command contract for the
prebuilt devenv CLI.
The intended macOS user experience is that a machine with Docker Desktop, but
without Go, can use the prebuilt devenv binary from this checkout and run one
setup command:
devenv setupAfter setup, a new zsh session should have these commands:
dev
devenv downNormal setup can use a prebuilt macOS devenv binary. To build it from this
checkout while developing:
mkdir -p bin
go build -o bin/devenv ./cmd/devenvThen run setup from the checkout:
./bin/devenv setup --source "$PWD"devenv setup copies the running binary to ~/.devenv/bin/devenv and adds that
directory to the generated zsh block, so future shells use the installed copy.
It cannot update the already-running parent shell. Existing terminals need:
source ~/.zshrcAfter getting the new revision of the codebase:
scripts/update-devenv-from-source.shCodex is pinned in dev.Dockerfile with checksums for both Linux image
architectures. To update it to the latest upstream release:
scripts/update-codex.shTo pin a specific release instead:
scripts/update-codex.sh 0.141.0Grok Build is pinned in dev.Dockerfile with checksums for both Linux image
architectures. To update it to the latest NPM latest release:
scripts/update-grok.shTo pin a specific release instead:
scripts/update-grok.sh 0.2.82Cursor CLI is pinned in dev.Dockerfile with checksums for both Linux image
architectures. To update it to the latest release exposed by Cursor's official
installer:
scripts/update-cursor.shTo pin a specific release instead:
scripts/update-cursor.sh 2026.07.16-899851bdevenv setup [flags]
devenv run [flags] [--] [command [args...]]
devenv down [flags]devenv setup builds the local dev image, prepares ~/.devenv, starts the safe
Docker proxy, verifies it, and installs shell commands in zshrc. After updating
zshrc it prints:
✅ dev env is ready
⚠️ existing terminals need: source ~/.zshrc
Useful setup flags:
--source path: directory containingdev.Dockerfile.--image tag: local dev image tag, defaultlocal-dev:latest.--root path: state directory, default~/.devenv.--zshrc path: zshrc file to update, default~/.zshrc.--skip-build: skip rebuilding the Docker image.
devenv run starts the dev container in the current directory. With no command,
it opens the image default shell. With a command, it runs that command in the dev
container. The generated zsh commands call devenv run --quiet, so normal
preflight checks stay silent unless something fails.
devenv down removes managed runtime containers, proxy bridge, and proxy
network. It keeps ~/.devenv state unless --delete-data is passed.
devenv setup is the single idempotent bootstrap command. It should be safe to
run repeatedly on the same machine.
The command must:
- Build the dev Docker image locally from
dev.Dockerfile. Users should not need Go, but they do need this repository checkout so Docker can build the image in place. - Create the host state directory at
~/.devenvif it does not exist. - Install the current
devenvbinary at~/.devenv/bin/devenvso generated shell functions have a stable command to call. - Initialize env-owned Git config and SSH identity files only when they are missing.
- Install or update the generated zshrc block that defines
dev. - Start or repair the safe Docker socket proxy, then verify the proxy container is running and the proxied socket is usable.
setup must not overwrite existing identity or persisted tool state. Existing
~/.devenv/gitconfig, SSH keys, and ~/.devenv/home contents are user state.
The command may update generated shell functions inside its own marker block,
but it must preserve user edits outside that block.
devenv setup should install one generated block in the user's zshrc. Re-running
setup should replace this block in place, not append duplicates.
The user-facing command contract is:
# >>> devenv >>>
export DEVENV_HOME="${DEVENV_HOME:-$HOME/.devenv}"
export DEVENV_IMAGE="${DEVENV_IMAGE:-local-dev:latest}"
export DEVENV_SOURCE="${DEVENV_SOURCE:-/path/to/env}"
export DEVENV_PROXY_PORT="${DEVENV_PROXY_PORT:-23750}"
case ":$PATH:" in
*":$DEVENV_HOME/bin:"*) ;;
*) export PATH="$DEVENV_HOME/bin:$PATH" ;;
esac
dev() {
devenv run --quiet -- "$@"
}
# <<< devenv <<<dev opens an interactive shell in the dev container with the current directory
mounted at /workspace. Arguments passed to dev are forwarded to devenv run
so callers can run a specific command in the same container environment.
The CLI should use an actual host folder, not an anonymous or named Docker home volume. The default layout is:
~/.devenv/
bin/
devenv
gitconfig
home/
.codex/
ssh/
id_ed25519
id_ed25519.pub
run/
docker.sock
proxy-bridge.pid
Mount contract:
~/.devenv/homeis mounted as/home/devso tool state such as~/.codexpersists across containers.~/.devenvis mounted read-only at/devenv, and containers receiveGIT_CONFIG_GLOBAL=/devenv/gitconfigso env-owned Git config is used without overlaying files inside the persisted home bind mount.~/.devenv/sshcontains the env-specific SSH keypair and is mounted as/home/dev/.ssh.~/.devenv/run/docker.sockis the safe proxied Docker socket used for host health checks and host-side Docker CLI access.- Dev containers join the managed proxy Docker network and receive
DOCKER_HOST=tcp://devenv-dockerproxy:2375, avoiding direct host Docker socket mounts. - The current working directory is mounted at
/workspace.
~/.devenv/gitconfig should include:
[worktree]
useRelativePaths = trueThe SSH private key must be created with restrictive permissions, for example
0600, and setup must never replace it after creation. Users can add the public
key at ~/.devenv/ssh/id_ed25519.pub to GitHub or another Git host.
Run GitHub CLI commands through the dev launcher from the host. To authenticate
with GitHub:
dev gh auth login --hostname github.com --git-protocol ssh --webFollow the instructions printed by gh. If your env SSH key is already uploaded
on GitHub, skip the SSH key upload step.
Dev containers should not bind mount the host Docker socket directly. The CLI
runs a Docker socket proxy container that binds the real host socket read-only
and exposes a filtered Docker API on a managed Docker network. A small
devenv bridge process exposes the same filtered API as a host Unix socket at
~/.devenv/run/docker.sock so setup can verify it and host-side tools can use
it with DOCKER_HOST=unix://$HOME/.devenv/run/docker.sock.
devenv setup should create or reuse the proxy container and bridge, recreate
them when configuration is stale or unhealthy, and wait until the proxied socket
and proxy network are usable before reporting success.
The safe socket proxy is runtime infrastructure. Repairing or recreating it must
not remove ~/.devenv/home, ~/.devenv/gitconfig, or SSH keys.
devenv down tears down running containers managed by the CLI, including the
safe Docker socket proxy. It is also idempotent: running it when no managed
containers exist should succeed.
devenv down must not delete identity or durable state:
- Do not delete
~/.devenv/gitconfig. - Do not delete
~/.devenv/homeor tool state such as~/.codex. - Do not delete
~/.devenv/sshor its keypair. - Do not remove the generated zshrc block.
- Do not remove the dev image unless a separate explicit prune command or flag is added.
The next devenv setup or launcher command should be able to recreate runtime
containers without forcing the user to re-authenticate tools.
The dev image includes Go, Rust, Node 24, Python with pip/venv, Lua, Luau,
Codex CLI, Claude Code CLI, Gemini CLI, Grok Build CLI, Cursor CLI, Homebrew,
Git 2.54.0, Git LFS, OpenSSH client, GitHub CLI (gh), Modal CLI, ping, jq,
ripgrep (rg), tree, less, pkg-config, zip/unzip, pnpm, Bazelisk with
Bazel 9.2.0 pre-cached, PostgreSQL, ffmpeg, ImageMagick, pandoc, WeasyPrint,
bash, build tools, common dev headers, and the Docker CLI with the Compose
plugin.
Codex is configured to run without its own sandbox inside this image because the container is the isolation boundary. Do not mount sensitive host paths into containers where Codex runs with broad autonomy. Use the safe Docker socket proxy if Docker CLI access is needed.
Codex enhanced keyboard reporting follows Codex's default behavior. To force the
legacy Docker TTY behavior, set CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT=1
before launching the container.
PostgreSQL is installed but not started automatically. Start the packaged cluster inside the container with:
sudo pg_ctlcluster 15 main startIf you use Git worktrees from both the host and this container, use Git 2.48.0 or newer everywhere that will touch the repository. This image builds Git 2.54.0.
Enable relative worktree links before creating worktrees:
git config --global worktree.useRelativePaths trueThe container sees the checkout at /workspace, while the host usually sees the
same files at a different absolute path. Git worktrees store links between the
main checkout and linked worktrees, so absolute links written in one environment
may not exist in the other. Relative links keep the worktrees usable as long as
the repository and its worktree directories move together.
Existing absolute-path worktrees should be recreated or repaired with Git 2.48.0 or newer:
git worktree repair --relative-pathsRelative worktree links enable Git's extensions.relativeWorktrees repository
extension, so older Git versions will reject those repositories.
devenv setup performs this build automatically. To rebuild manually:
docker build -f dev.Dockerfile -t local-dev:latest .