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.

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

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.

docker build -t codex-cli .

Signing in: one interactive run

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

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.

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:

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:

# 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. 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; 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.

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 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.

Frequently asked questions

How do I run Codex CLI in Docker?

Base the image on node:22, install the CLI with npm install -g @openai/codex, and keep ~/.codex in a named volume so the login survives container restarts. One interactive run with codex login --device-auth signs the volume in; after that, codex exec runs unattended.

How do I authenticate Codex inside a container?

Run the container once interactively with the auth volume mounted and codex login --device-auth as the command. The CLI prints a code, you approve it at chatgpt.com from any device, and the tokens persist in the volume for every later run.

Why run --sandbox danger-full-access inside Docker?

Codex's built-in sandbox uses kernel features such as Landlock and seccomp that minimal containers often lack, so sandboxed commands can fail in odd ways. Inside an unprivileged container, the container itself is the isolation boundary; give Codex full access within those walls and keep the mounts minimal.

Should I bake auth.json into the Docker image?

No. auth.json is a live session for your ChatGPT account, and images get pushed to registries, cached on shared hosts, and copied freely. Keep credentials in a volume or inject them at runtime, never in an image layer.

More on Codex CLI
Codex Hosted · the main feature

Run your AI workloads on your ChatGPT subscription.

ProxyLLM runs OpenAI's Codex for you, signed in with your own ChatGPT account. Your apps call one OpenAI-compatible endpoint and the work bills to your flat plan instead of per-token API pricing.