Docker runners for terminal coding agents: Codex, Gemini, and future tools.
The goal is simple: run powerful agent CLIs without installing their runtimes, npm packages, caches, and auth files directly on your laptop.
Coding agents can run commands and edit files. A bad prompt, tool bug, or compromised package can damage files on the host.
Installing agent CLIs directly on a laptop also spreads npm packages, caches, auth state, shell history, and temporary files across the user environment. This project keeps those runtimes disposable and per-agent.
This project reduces the blast radius:
- only the per-agent
workspace/directory is mounted; - Dockerfile, compose.yml, and wrapper scripts stay outside
/workspace; - auth/state is stored locally in
.codexor.gemini; - containers run as non-root users;
- Codex keeps its native approval/sandbox flow inside the container;
- Linux capabilities are dropped;
- Docker logs are disabled;
- no Docker socket is mounted;
- agent packages are installed inside images, not on the host.
This is not a perfect sandbox. The agent can still change files inside the
mounted workspace/ directory.
Each agent has its own runner directory:
agent/
workspace/ mounted read-write as /workspace
.codex/ or .gemini/ auth and local state
.ssh/ per-agent deploy key, mounted read-only
.secrets/ optional credentials, mounted read-only
The container boundary is intentionally narrow: non-root user, dropped Linux
capabilities, no privileged mode, no Docker socket, no persistent Docker logs,
and resource limits for CPU, memory, and process count. Codex additionally uses
its native bubblewrap sandbox with a custom seccomp profile so write/delete
operations still go through Codex approval.
This reduces host risk from accidental commands, malicious npm/Go/Rust packages, bad prompts, and tool bugs. The agent and code it runs execute inside a container without privileged mode, without a Docker socket, without access to the host home directory, and with a dedicated workspace mount.
The isolation limits blast radius to directories intentionally mounted into the
container: workspace/, agent state, read-only .ssh, and read-only
.secrets.
It does not protect against malicious code reading mounted files, secrets
committed into workspace/, code abusing credentials available to the agent, or
escape vulnerabilities in Docker, the Linux kernel, or Docker Desktop.
codex/- OpenAI Codex CLIgemini/- Google Gemini CLIclaude/- planned
Agent images include the practical local toolchain by default:
- git, bash, curl, SSH, rsync
- Python, pip, Node.js, npm
- Terraform from the latest HashiCorp release at build time
- Ansible from Alpine packages
- Kubernetes clients: kubectl, Helm, Kustomize, kubectx/kubens, Stern
- make, SOPS, age, and crane
- OpenSSL CLI for certificate and TLS diagnostics
- lint and hygiene tools: shfmt, ShellCheck, yamllint, ansible-lint, pre-commit
- gitleaks for local secret scans
- jq and yq
Cloud provider CLIs are intentionally not included. For Terraform and Ansible, keep projects in workspace/ and credentials outside it in .secrets/.
- Docker with Docker Compose plugin
- Bash on the host
Containers are Alpine-based and include Bash because agent shell tools commonly spawn bash.
cd codex
./codex.shOn first run Codex asks how to sign in:
1. Sign in with ChatGPT
2. Sign in with Device Code
3. Provide your own API key
Choose ChatGPT if your plan includes Codex, Device Code for login from another device, or API key for usage-based billing.
By default, Codex can edit only files inside:
codex/workspace/
The runner files stay outside that mount.
Useful commands:
./codex.sh --device-auth
OPENAI_API_KEY=... ./codex.sh --api
./codex.sh --resume lastcd gemini
./gemini.shThe default model is gemini-3.1-flash-lite. Override it when needed:
GEMINI_MODEL=other-model ./gemini.shOn first run Gemini starts the Google login flow. If a browser does not open automatically, copy the printed URL into your host browser and finish auth there.
The container sets GEMINI_FORCE_FILE_STORAGE=true, so OAuth tokens are written to .gemini/gemini-credentials.json instead of a desktop keychain.
If Gemini asks:
Do you trust the files in this folder?
Choose:
1. Trust folder (workspace)
Do not trust the parent folder unless you intentionally want a broader trust scope.
By default, Gemini can edit only files inside:
gemini/workspace/
The runner files stay outside that mount.
Do not put cloud credentials, Terraform backend secrets, Ansible vault passwords, or private keys into workspace/. Store them in per-agent .secrets/ directories instead:
codex/.secrets/
gemini/.secrets/
They are ignored by git and mounted read-only at /run/agent-secrets.
Examples:
.secrets/ansible/vault-password.txt
.secrets/aws/credentials
.secrets/gcp/service-account.json
.secrets/yc/authorized_key.json
Do not mount your personal ~/.ssh directory into an agent container. Create a separate key for each agent and add only the public key to the repository as a deploy key:
cd codex
./codex.sh --init-ssh-key
cd ../gemini
./gemini.sh --init-ssh-keyThe command prints the public key. Add it in GitHub/GitLab repository settings as a deploy key:
- read-only for clone/pull;
- write access only when the agent must push;
- separate key per agent/project when possible.
Private keys stay in codex/.ssh/ or gemini/.ssh/, are ignored by git, and are mounted read-only into the container.
alpine:3.23- non-root users
no-new-privilegeswhere compatiblecap_drop: ALL- CPU, memory, and process limits
- explicit DNS servers and empty DNS search domains
logging.driver: none- OTEL exporters disabled
- no privileged mode
- no Docker socket mount
- Codex uses
bubblewrapwith a local seccomp profile for its inner sandbox
Codex:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v trivy-cache:/root/.cache/ \
aquasec/trivy:latest image \
--scanners vuln \
--timeout 10m \
--severity HIGH,CRITICAL \
--ignore-unfixed \
local/codex-rust:latestGemini:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v trivy-cache:/root/.cache/ \
aquasec/trivy:latest image \
--scanners vuln \
--timeout 10m \
--severity HIGH,CRITICAL \
--ignore-unfixed \
local/gemini-cli:latest