ai-dev is an immutable Ubuntu 24.04 terminal development environment for amd64 and arm64. It includes Docker CLI/Compose/Buildx, GitHub CLI, common language toolchains, Claude Code, Codex CLI, Oh My ClaudeCode (OMC), Oh My Codex (OMX), and SaladDay cc-switch-cli. Projects, credentials, tool state, logs, models, and backups live in named volumes rather than the image.
The Compose service mounts
/var/run/docker.sockread-write. Access to the raw Docker Socket is effectively host-root access: code in the container can create privileged containers, mount host filesystems, and control the Docker host.Use this image only on a trusted, single-user server and only with trusted repositories and tools. Running normal commands as the non-root
devuser, omittingprivileged: true, and limiting container capabilities do not sandbox Docker Socket access. This deployment accepts that risk by design. Read Security before starting it.
- Linux Docker host with Docker Engine, Docker Compose v2, and a working
/var/run/docker.sock - amd64 or arm64 CPU
- Permission to use Docker on the host
- Enough disk space for the image, six named volumes, and backups
The committed Compose file defaults to docker.io/jerry0510/ai-dev:latest. Override the image for a pinned release, registry mirror, or local build without editing Compose:
AI_DEV_IMAGE=docker.io/jerry0510/ai-dev:1.2.3 docker compose up -dFor the default image:
docker compose up -d
docker compose psOptional first-start identity and timezone values are PUID, PGID, and TZ; defaults are 1000, 1000, and UTC:
PUID="$(id -u)" PGID="$(id -g)" TZ=Asia/Shanghai docker compose up -dThe first successful start records the selected UID/GID. Later changes require the offline identity migration.
The image is built from a digest-pinned Ubuntu 24.04 base, supports amd64 and arm64, and uses Zsh as the default shell for the dev user. versions.env is the single source of truth for pinned versions; the primary contents of the current image are:
| Category | Software | Version / notes |
|---|---|---|
| Container development | Docker CLI, Compose v2, Buildx | Installed from Docker's Ubuntu repository and used through the mounted host Docker Socket |
| JavaScript / TypeScript | Node.js, pnpm, Bun | 24.4.1, 10.13.1, 1.2.19 |
| Python | Python 3, pip, venv, uv | Ubuntu 24.04 system Python; uv 0.8.3 |
| Go | Go | 1.24.5 |
| Rust | rustc, cargo, rustup | 1.88.0, minimal profile |
| Java | OpenJDK headless | 21 |
| GitHub | GitHub CLI (gh) |
2.97.0 |
| AI tools | Claude Code, Codex CLI | 2.1.221, 0.146.0 |
| AI orchestration | Oh My ClaudeCode, Oh My Codex | 4.15.7, 0.20.3 |
| Claude provider tool | SaladDay cc-switch-cli |
5.9.3 Linux musl CLI |
| Development tools | Git, Git LFS, OpenSSH client, tmux, Zsh, Bash, GCC, Clang, CMake, make, pkg-config | Installed from Ubuntu repositories |
| Command-line tools | curl, wget, jq, yq 4.47.1, rg, fd, fzf, sqlite3, ShellCheck |
fd is a compatibility alias for fdfind |
The AI CLI support baseline includes Node.js 24 (the OMC and OMX runtime), Python 3 plus a native build toolchain and SQLite (native Node module fallback), and Git, rg, jq, tmux, curl, and tar for the supported CLI workflows. Runtime self-updates are disabled. Caches live under /data/cache, while configuration, credentials, and projects are persisted through the named volumes below. Check a running image with scripts/exec <command> --version.
Compose creates exactly six named volumes:
| Volume | Container path | Contents |
|---|---|---|
workspace |
/workspace |
Git repositories and repository-local Claude, Codex, OMC, and OMX files |
config |
/config |
Versioned tool configuration, credentials, SSH, Git, Zsh, journals, and events |
data |
/data |
Package caches and reusable tool data |
logs |
/logs |
Runtime events and optional tool logs |
models |
/models |
Downloaded or local model artifacts |
backups |
/backups |
In-container backups and migration metadata |
The Compose project name is ai-dev, so Docker normally names these volumes ai-dev_workspace, ai-dev_config, and so on. Do not use docker compose down -v unless you intend to delete all persistent state.
Open the supported Zsh session as dev (from the Compose project directory):
scripts/shellThat is the normal entry path. It runs as dev through ai-dev-shell / ai-dev-run, so generation exports such as CLAUDE_CONFIG_DIR, CODEX_HOME, and GH_CONFIG_DIR are applied. Equivalents:
docker compose exec --user dev ai-dev ai-dev-shell
docker exec -it --user dev ai-dev ai-dev-shellRun one command through the same generation resolver:
scripts/exec git --version
scripts/exec cc-switch config path
scripts/exec ai-dev readiness
scripts/exec ai-dev doctortmux runs inside the container. Use it so host SSH disconnects do not kill your shell work:
scripts/shell
tmux new -As devOr from the host in one step: scripts/tmux.
- First time: creates a session named
devand attaches. - Later: the same command reattaches if the session still exists (
-A), or creates it again if not. - Detach without stopping work:
Ctrl-bthend, then leave the container. - Reattach later:
scripts/shell
tmux attach -t dev
# same effect:
tmux new -As devFrom the Compose project directory, open tmux in one step:
scripts/tmuxThat runs docker compose exec --user dev ai-dev ai-dev-run tmux new -As dev. Equivalents:
docker compose exec --user dev ai-dev ai-dev-run tmux new -As dev
docker exec -it --user dev ai-dev ai-dev-run tmux new -As devOptional host-wide shortcut (any directory; requires container name ai-dev):
# bash/zsh — add to ~/.bashrc or ~/.zshrc
alias ai-dev-tmux='docker exec -it --user dev ai-dev ai-dev-run tmux new -As dev'
# then:
ai-dev-tmuxUseful checks inside the container: tmux ls, tmux kill-session -t dev.
Notes:
- The image does not run an SSH daemon; SSH to the Docker host, then use
scripts/shell/scripts/exec(or thedocker execforms above). - A bare
docker compose exec ai-dev shstarts as root and is a recovery interface, not a normal development shell. - tmux session state lives in the container process.
docker compose restartor recreating the container ends the session; tool config in named volumes is separate and persists.~/.tmux.confis managed into the active generation.
Startup never invents identity, logs into an AI provider, creates SSH keys, configures a cc-switch provider, enables its proxy, or starts an updater.
Configure Git identity first:
scripts/exec git config --global user.name "Your Name"
scripts/exec git config --global user.email "you@example.com"~/.ssh is a managed route into the active configuration generation under /config, so keys survive container recreate when volumes are kept. Do not bind-mount the host ~/.ssh directory and do not put private keys in Compose or git.
scripts/shell
ssh-keygen -t ed25519 -C "you@example.com"
cat ~/.ssh/id_ed25519.pubAdd the printed public key to GitHub (or another Git host).
Copy the host private key (and public key if present) into the container as dev. Example for id_ed25519:
docker exec -u dev ai-dev ai-dev-run mkdir -p /home/dev/.ssh
docker cp ~/.ssh/id_ed25519 ai-dev:/home/dev/.ssh/id_ed25519
docker cp ~/.ssh/id_ed25519.pub ai-dev:/home/dev/.ssh/id_ed25519.pub
docker exec -u dev ai-dev ai-dev-run bash -lc 'chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_ed25519 && chmod 644 ~/.ssh/id_ed25519.pub'Optional GitHub SSH config:
docker exec -u dev -i ai-dev ai-dev-run bash -lc 'cat > ~/.ssh/config && chmod 600 ~/.ssh/config' <<'EOF'
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
EOFVerify:
docker exec -u dev ai-dev ai-dev-run ssh -T git@github.com
docker exec -u dev ai-dev ai-dev-run bash -lc 'readlink -f ~/.ssh; ls -la ~/.ssh'ssh -T git@github.com often exits non-zero even on success; look for Hi <username>! You've successfully authenticated. readlink -f ~/.ssh should resolve under /config/generations/<id>/ssh. Use id_rsa (or your real key name) instead of id_ed25519 when that is what the host uses.
Authenticate with the browser or device flow appropriate for the host:
scripts/exec gh auth login
scripts/exec gh auth statusGitHub CLI credentials persist through GH_CONFIG_DIR in the active configuration generation (see the Tool State Contract). Do not add tokens to Compose or commit them to a repository.
scripts/exec claude auth loginComplete the URL/code flow on your local browser when the server is headless. Claude credentials persist through CLAUDE_CONFIG_DIR in the active generation.
Device authentication works well on a headless server:
scripts/exec codex login --device-auth
scripts/exec codex login statusCodex stores its credentials under the generation-selected CODEX_HOME. Do not place API keys in Compose or commit them to a repository.
The pinned omc CLI is already in the image. For the official Claude Code plugin workflow, start Claude from the target project and enter these commands in Claude Code:
/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode
/plugin install oh-my-claudecode@omc
/omc-setup
Run this per project; project files remain under /workspace. The container does not automate this interactive marketplace operation.
On a fresh config volume, startup performs the packaged user-scope initialization offline and runs omx doctor before committing the first configuration generation. It does not authenticate Codex. Verify the wiring at any time:
scripts/exec omx doctorInitialize OMX for a repository explicitly:
scripts/exec bash -lc 'cd /workspace/my-project && omx setup --scope project --merge-agents'Repository-local OMX runtime state stays in /workspace/my-project/.omx.
The image contains the pinned headless TUI/scriptable CLI from SaladDay/cc-switch-cli, not the similarly named desktop GUI:
scripts/exec cc-switch --version
scripts/exec cc-switch config path
scripts/exec cc-switch config validate
scripts/exec cc-switch provider listRun scripts/exec cc-switch for the TUI. Provider credentials, OAuth material, proxy configuration, sync, and updates are manual user actions. Treat the entire cc-switch state directory as secret.
- Upgrade, migration, rollback, backup, and restore
- Troubleshooting and FAQ
- Architecture and persistence model
- Tool state contract (development source of truth for recreate-safe tool config)
- Security model
- Development and validation
- Contributing
| Source | Published tags |
|---|---|
main |
edge, sha-<short-sha> |
Stable vX.Y.Z |
vX.Y.Z, X.Y.Z, X.Y, X, YYYY.MM, latest |
Prerelease vX.Y.Z-rc.N |
vX.Y.Z-rc.N, X.Y.Z-rc.N only |
Stable publication requires native amd64 and arm64 gates against the candidate digest. QEMU smoke tests are supplemental. CI always runs static contracts, Compose/Bake configuration checks, and release-policy validation. Expensive image build, native gate, publish, and promote jobs run only when image/deploy-relevant paths change, and always for version tags. Successful image publishes still carry SBOM, provenance, and artifact attestation. Local npm run validate can skip unavailable host tools and is not evidence that Docker builds or native release gates passed.
This project is licensed under the MIT License. See LICENSE.