# Codex CLI in Docker: A Working Container Setup

A Dockerfile for OpenAI's Codex CLI, a named volume for ~/.codex auth, device login inside the container, and why the sandbox flag changes when Docker is the boundary.

*Published 2026-06-12 · https://proxyllm.ai/blog/codex-cli-docker-guide*

The Codex CLI runs well in Docker: a Node 22 base image, one global npm install, a named volume for `~/.codex` so the login survives restarts, and a single interactive `--device-auth` run to sign the volume in. The one genuine difference from a bare-metal install is sandboxing. Inside a container, Docker is the isolation boundary, and Codex's own sandbox usually steps aside in its favor.

## The Dockerfile

```dockerfile
FROM node:22-bookworm-slim

RUN apt-get update \
 && apt-get install -y --no-install-recommends git ca-certificates ripgrep \
 && rm -rf /var/lib/apt/lists/*

RUN npm install -g @openai/codex

RUN useradd -m codex
USER codex
WORKDIR /work

ENTRYPOINT ["codex"]
```

Why each piece is there: git, because Codex works best inside repositories and checks for one; ripgrep, because the agent leans on fast search; a non-root user, so files it writes into a mounted repo are not owned by root on your host. The `ENTRYPOINT` makes the container behave like the binary, so arguments pass straight through.

```bash
docker build -t codex-cli .
```

## Signing in: one interactive run

Create a volume for the auth state, then run the device-auth flow once:

```bash
docker volume create codex-auth

docker run -it \
  -v codex-auth:/home/codex/.codex \
  codex-cli login --device-auth
```

The CLI prints a code, you approve it at chatgpt.com from any device, and the tokens land in the volume. Every container that mounts `codex-auth` afterward is signed in. The flow itself, and the handling rules for the file it produces, are covered in the [device auth walkthrough](/blog/codex-login-without-browser).

Bake the CLI into the image; never bake the login. An image layer with `auth.json` in it will eventually end up in a registry, a cache, or a teammate's laptop, and that file is a live session for your ChatGPT account.

On your own workstation there is a shortcut: bind-mount the login you already have with `-v "$HOME/.codex:/home/codex/.codex"`. Same account, same machine, same user, so the account stays where it belongs.

## Running jobs

Mount the repo you want Codex to work on and call `exec`:

```bash
docker run --rm \
  -v codex-auth:/home/codex/.codex \
  -v "$PWD:/work" \
  codex-cli exec --sandbox danger-full-access \
  "find every TODO comment and output a JSON array of {file, line, text}"
```

Output goes to stdout, so pipes and redirects work exactly as they do outside the container. If the mounted directory is not a git repository, `exec` will refuse by default; pass `--skip-git-repo-check` when that is intentional.

## The sandbox question

On bare metal, Codex sandboxes the shell commands it runs using OS primitives: Seatbelt on macOS, Landlock and seccomp on Linux. Slim container images and hardened container runtimes often lack those features or block them, which surfaces as sandbox errors on commands that would work fine on a laptop.

The working pattern is to make the container the sandbox:

- run it unprivileged: non-root user, no `--privileged`, no Docker socket mounted,
- mount only the repository the job is allowed to touch,
- give Codex `--sandbox danger-full-access` inside.

The flag name is alarming on purpose, and in this layout the alarm is handled: full access means full access to a throwaway container that can see one directory. Inside Docker, the container is the sandbox; keep it unprivileged, mount only what the job may touch, and let Codex run free within those walls.

To stop repeating flags, drop a config file into the volume next to `auth.json`:

```toml
# config.toml, stored in the codex-auth volume
approval_policy = "never"
sandbox_mode = "danger-full-access"
```

`approval_policy = "never"` matters for the same reason: there is no human attached to a container's stdin to approve anything.

## CI notes

On shared runners (GitHub-hosted, shared GitLab executors), skip the auth volume entirely and pass `OPENAI_API_KEY` as a masked secret instead. A key is revocable and auditable; a personal session file is neither, and OpenAI's account rules expect one account, one user. The full pipeline patterns, gates included, are in [Codex in CI/CD](/blog/codex-cli-ci-cd-guide). On a runner you own and use yourself, the volume pattern above carries over unchanged.

## What Docker solves, and what it leaves

Docker pins the environment. It does not keep anything running or signed in. The container still needs a host that stays up, which in practice means [a small VPS](/blog/codex-cli-on-vps); the volume still holds a session that can lapse; usage windows still close on their own schedule; and nothing here queues or logs your jobs. The rest of the headless story lives in the [headless server guide](/blog/codex-cli-headless-server-setup).

Running this container yourself is free and a good fit for personal pipelines. When the jobs need to behave like a service, the [honest comparison with the hosted lane](/blog/codex-hosted-vs-running-codex-yourself) shows what changes: [Codex Hosted](/) is the same official CLI in a container we keep signed in, queued, and logged, for a flat $129/month.
