Menü schliessen
Created: September 16th 2025
Last updated: September 4th 2026
Categories: Artificial intelligence (AI)
Author: Marcus Fleuti

[solved] Complete Guide: Install OpenAI Codex CLI and Claude Code in Docker on Ubuntu/Linux Mint with Security Configuration

Install Claude Code and OpenAI Codex CLI in a Minimal Ubuntu Docker Container (2026 Edition)

Running Claude Code and the OpenAI Codex CLI inside one lightweight Ubuntu Docker container gives you two powerful AI coding agents in a clean, reproducible and isolated workspace. This guide walks through every step on Ubuntu or Linux Mint: installing Docker, starting the container, creating a user that matches your host account, installing both CLIs, writing sane configuration files, logging in for the first time, and wiring up host aliases so claude and codex feel like native commands while everything they touch stays inside the container.

Updated September 2026. Both CLIs changed a lot since the first version of this guide. The main differences you will notice below:

  • Claude Code ships as a native binary that updates itself. Node.js is no longer required for it, and claude install is gone.
  • Codex CLI is a Rust binary with its own standalone installer. npm still works, but is optional.
  • The Claude Code settings.json was rewritten. Several keys from the old version never existed, defaultMode belongs under permissions, and bypassPermissions only works from the user settings file.
  • The Codex config.toml was rewritten. approval_policy = "on-failure" is deprecated, the gpt-5-codex model is retired, web_search is now a string, and network_access lives in the [sandbox_workspace_write] table.
  • The host aliases are now shell functions that pass arguments safely and always map your host directory to the right path inside the container.
  • New sections: first login, verification, updates, resetting the container, and optional git identity for the agents.

Why Isolate AI Coding Agents in Docker?

Both agents can run fully autonomously: Claude Code in bypassPermissions mode and Codex with --yolo. Anthropic's own docs state that bypassPermissions belongs in "isolated containers and VMs only". A container gives the agents a full Linux toolchain and unrestricted internet access while the only thing they can permanently touch on your machine is the one folder you mount into it. Rebuilding the container is a two-minute job, and because the container user's home directory lives on that mounted folder, your logins, settings and even the installed CLIs survive a rebuild.

Prerequisites

  • Ubuntu 22.04 or newer, Linux Mint 21 or newer (other distributions work the same way, only the Docker install differs)
  • Docker Engine 24.x or newer
  • A non-root host user with sudo rights
  • Around 4 GB free disk space for the image and the developer tooling
  • A Claude Pro, Max, Team or Enterprise subscription (or a Claude Console API key) for Claude Code
  • A ChatGPT Plus, Pro, Business, Edu or Enterprise subscription (or an OpenAI API key) for Codex CLI

Install Docker from the standard repositories if it is not already available:

sudo apt update
sudo apt install docker.io -y
sudo systemctl enable --now docker

To run docker without sudo, add your user to the docker group, then log out and back in so the new group membership takes effect:

sudo usermod -aG docker "$USER"

Verify that it works before you continue:

docker run --rm hello-world

Prepare the Shared Directories

Two host directories are mounted into the container. ~/ai-agent becomes the home directory of the container user and holds your projects, both CLI installations and their configuration (.claude, .codex). /ai-agent-tmp is mounted as /tmp inside the container so temporary files do not bloat the container layer.

mkdir -p ~/ai-agent
sudo mkdir -p /ai-agent-tmp
sudo chown "$(id -u):$(id -g)" /ai-agent-tmp
sudo chmod 1777 /ai-agent-tmp

Take note of your user and group ID now, you will need them inside the container:

id

On most desktop systems the first user has uid=1000 and gid=1000. If yours differ, replace 1000 in the user creation step below.

Launch the Ubuntu Container

Start a long-lived container named ai-agent. It runs sleep infinity so it stays online, and you attach interactive shells with docker exec. The image is pinned to ubuntu:26.04 (the current LTS) rather than latest, so a future major release cannot silently change your environment.

docker run -d \
  --init \
  --pids-limit=4096 \
  --restart=unless-stopped \
  --name ai-agent \
  --hostname ai-agent \
  --network host \
  -e TZ=Europe/Zurich \
  -e LANG=C.UTF-8 \
  -v "$HOME/ai-agent:/ai-agent" \
  -v "/ai-agent-tmp:/tmp" \
  ubuntu:26.04 \
  sleep infinity
  • -d: run detached in the background.
  • --init: a tiny init process as PID 1 that reaps zombie processes and forwards signals.
  • --pids-limit=4096: protection against fork bombs and runaway subprocesses.
  • --restart=unless-stopped: the container comes back after a crash or a host reboot unless you stopped it yourself.
  • --name / --hostname ai-agent: the name you use with docker exec, and the hostname shown in the container prompt.
  • --network host: the container shares the host network stack. This is what makes the browser-based logins of both CLIs work: their local OAuth callback servers (Codex uses localhost:1455) are reachable from your host browser without any port mapping. The trade-off is that there is no network isolation between host and container. If you prefer bridge networking, drop this flag and use the terminal login fallbacks described in the login section.
  • -e TZ / -e LANG: timezone and UTF-8 locale for the container. Adjust the timezone to yours.
  • -v "$HOME/ai-agent:/ai-agent": the shared workspace and container home. Survives container removal.
  • -v "/ai-agent-tmp:/tmp": /tmp on the host instead of inside the container layer.
  • ubuntu:26.04: pinned base image.
  • sleep infinity: keeps the container alive.

Enter the Container and Install the Developer Toolkit

Open a root shell inside the running container:

docker exec -it ai-agent bash -l

If Docker complains about permissions, you have not logged out and back in since joining the docker group. Once inside, update the base image. DEBIAN_FRONTEND=noninteractive prevents packages such as tzdata from blocking the install with interactive prompts.

export DEBIAN_FRONTEND=noninteractive
apt update && apt upgrade -y

Neither CLI needs Node.js anymore, but AI agents do need build tools, version control, search utilities, language runtimes and database clients to be useful. The following package set gives them an environment comparable to a full-stack workstation. Node.js and npm are included because many MCP servers are started with npx, and Python because agents write a lot of glue scripts.

apt install -y \
  build-essential \
  git \
  curl \
  wget \
  jq \
  ripgrep \
  fd-find \
  tree \
  htop \
  tmux \
  screen \
  vim \
  nano \
  less \
  file \
  zip \
  unzip \
  tar \
  gzip \
  bzip2 \
  xz-utils \
  whois \
  python3 \
  python3-pip \
  python3-venv \
  python3-dev \
  nodejs \
  npm \
  sqlite3 \
  libsqlite3-dev \
  postgresql-client \
  libpq-dev \
  redis-tools \
  ca-certificates \
  openssh-client \
  gnupg \
  lsb-release \
  tzdata \
  locales \
  sed \
  gawk \
  grep \
  iputils-ping \
  iproute2 \
  imagemagick \
  ffmpeg \
  libxml2-dev \
  libxslt1-dev \
  libyaml-dev \
  libffi-dev \
  libssl-dev \
  libreadline-dev \
  zlib1g-dev \
  libcurl4-openssl-dev \
  sudo

Optional extras worth considering:

  • bubblewrap socat: required if you want to use Claude Code's built-in Bash sandbox inside the container as an additional layer (see the security section).
  • gh (GitHub CLI): lets the agents open pull requests. It needs GitHub's own apt repository, see cli.github.com.
  • The Ubuntu Node.js package is fine for npx-based MCP servers. If a project needs the newest Node LTS, install it with nvm as the container user later.

The mail packages from the older version of this guide were removed: sendmail pulls in a long interactive configuration and is not something an AI agent should have anyway.

Create a Container User That Matches Your Host User

This is the most important step for a painless setup. Files written by the agents must be owned by your host user, and Claude Code refuses to start in bypassPermissions mode as root. So the agents run as a non-root user inside the container whose UID and GID match your host account, with /ai-agent as home directory. That way ~/.claude and ~/.codex resolve to /ai-agent/.claude and /ai-agent/.codex, which is ~/ai-agent/.claude and ~/ai-agent/.codex on your host.

Still inside the container as root, check whether UID 1000 is already taken. The Ubuntu image ships a default ubuntu user with that ID:

id 1000

Warning: run the following commands only inside the container. Check that your prompt shows root@ai-agent before you continue.

Remove the default user and group, then create yours. Replace yourUserName with your host username and adjust the IDs if id on the host printed something other than 1000:

userdel -r ubuntu 2>/dev/null || true
groupdel ubuntu 2>/dev/null || true
groupadd -g 1000 yourUserName
useradd -u 1000 -g 1000 -d /ai-agent -M -s /bin/bash yourUserName
cp -n /etc/skel/.bashrc /etc/skel/.profile /ai-agent/
chown -R yourUserName:yourUserName /ai-agent

What this does:

  • -u 1000 -g 1000: match the host IDs so ownership on the shared folder is identical on both sides.
  • -d /ai-agent -M: use the existing mount as home without trying to create it.
  • -s /bin/bash: a proper login shell.
  • cp -n /etc/skel/...: since the home already exists, useradd does not copy the skeleton files. Ubuntu's default .profile adds ~/.local/bin to the PATH, which is exactly where both CLI installers place their binaries.

Optionally give that user passwordless sudo inside the container. This lets the agents install missing packages themselves. It is harmless for the host because sudo only affects the container, but it also means an agent can change anything in the container, so leave it out if you want a stricter setup:

echo 'yourUserName ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/yourUserName
chmod 0440 /etc/sudoers.d/yourUserName

Leave the container:

exit

Install Claude Code and Codex CLI as the Container User

Both vendors now provide standalone installers that drop a self-updating binary into ~/.local/bin. Run them as your user, not as root. The bash -lc login shell makes sure ~/.profile (and therefore the PATH) is loaded.

docker exec -it -u yourUserName -w /ai-agent ai-agent bash -lc \
  'curl -fsSL https://claude.ai/install.sh | bash'

docker exec -it -u yourUserName -w /ai-agent ai-agent bash -lc \
  'curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh'

Verify both installations:

docker exec -it -u yourUserName -w /ai-agent ai-agent bash -lc 'claude --version && codex --version'

If claude or codex is not found, ~/.local/bin is missing from the PATH. Check that /ai-agent/.profile exists and contains the .local/bin block from /etc/skel/.profile.

Prefer npm? npm install -g @anthropic-ai/claude-code and npm install -g @openai/codex still work and install the same binaries, but then you have to run those commands yourself to update. The standalone installs update on their own (Claude Code) or with a rerun of the installer (Codex), which the ai-agent-update helper below takes care of.

Claude Code Configuration

Because the shared folder is your container home, you can edit the configuration directly from the host. Create ~/ai-agent/.claude/settings.json with your favourite editor:

mkdir -p ~/ai-agent/.claude
nano ~/ai-agent/.claude/settings.json

Paste the following. It is strict JSON, so no comments and no trailing commas:

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "model": "opus",
  "effortLevel": "high",
  "autoUpdatesChannel": "latest",
  "skipDangerousModePermissionPrompt": true,
  "permissions": {
    "defaultMode": "bypassPermissions",
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Read(./config/credentials.json)",
      "Read(./*.key)",
      "Read(./*.pem)",
      "Read(./*.p12)",
      "Read(./*.pfx)",
      "Read(./*.tfvars)",
      "Read(./terraform.tfstate)",
      "Read(./terraform.tfstate.backup)",
      "Read(./.terraform/**)",
      "Read(./backup/**)",
      "Read(./*.bak)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)",
      "Read(~/.gcp/**)",
      "Read(~/.azure/**)",
      "Read(~/.kube/config)",
      "Read(~/.docker/config.json)",
      "Read(~/.netrc)",
      "Read(~/.npmrc)",
      "Read(~/.pypirc)",
      "Read(~/.bash_history)",
      "Read(~/.claude.json)",
      "Read(~/.claude/.credentials.json)",
      "Read(~/.codex/auth.json)"
    ]
  },
  "env": {
    "BASH_DEFAULT_TIMEOUT_MS": "600000",
    "BASH_MAX_TIMEOUT_MS": "1800000",
    "BASH_MAX_OUTPUT_LENGTH": "150000",
    "CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR": "1",
    "MAX_MCP_OUTPUT_TOKENS": "50000",
    "DISABLE_TELEMETRY": "1",
    "DISABLE_COST_WARNINGS": "1"
  }
}

What each part does, and why the file looks different from the old version:

  • $schema: gives you autocomplete and validation in VS Code or any editor with JSON schema support.
  • model / effortLevel: the opus alias always resolves to the current default Opus model (Opus 5 at the time of writing), so the file does not go stale when a new model ships. Use sonnet for cheaper everyday work, or a full ID such as claude-opus-5 to pin a version.
  • autoUpdatesChannel: latest gets every release, stable lags about a week and skips releases with known regressions.
  • permissions.defaultMode: the old guide put defaultMode at the top level, where Claude Code ignores it. It belongs under permissions. Note that bypassPermissions and auto only take effect from the user file ~/.claude/settings.json, never from a project's .claude/settings.json. Since the container user's home is /ai-agent, this file is the user file.
  • skipDangerousModePermissionPrompt: suppresses the one-time "you accept responsibility" dialog that bypassPermissions shows on first start. Claude Code writes this key itself once you accept the dialog, setting it up front just saves the click.
  • No allow list: allow rules have no effect in bypassPermissions mode, and blanket rules like Bash are dropped automatically in auto mode. The long allow list from the old version was dead weight.
  • deny list: deny rules apply in every mode, including bypassPermissions, so this is your real guard rail. The list keeps the agent away from secrets, cloud credentials, key material and, new in this version, from the other agent's login tokens (~/.codex/auth.json) and its own credential store. Entries like .git/**, node_modules/**, build and dist were removed because they contain no secrets and blocking them only hampers the agent. Review this list whenever you add sensitive files to a project.
  • env: the old bashSettings, outputSettings and privacy objects never existed. The same behaviour is configured through environment variables: Bash commands may run 10 minutes by default and up to 30 minutes when the model asks for it, command output is capped at the maximum of 150,000 characters (the old value of 200,000 exceeded the limit), the working directory is restored after every command, and telemetry and cost warnings are off.

Auto Mode Instead of bypassPermissions

Auto mode is the built-in default on Pro, Max and Team plans since August 2026. A separate classifier model reviews every risky action (curl-pipe-to-bash, force pushes, destructive git commands, sending secrets to external endpoints, and so on) and blocks it, while everything else runs without prompts. It is the recommended hands-off mode when the container is not your only line of defense, for example when you mount additional directories or forward credentials. To use it as the default, set "defaultMode": "auto" in the file above. You can also start a single session with claude --permission-mode auto and keep bypassPermissions as the file default.

Codex CLI Configuration

Create ~/ai-agent/.codex/config.toml from the host:

mkdir -p ~/ai-agent/.codex
nano ~/ai-agent/.codex/config.toml

Standard Configuration (Sandbox Enabled)

Codex separates two controls: sandbox_mode decides what a command can technically reach (filesystem, network), approval_policy decides when Codex stops and asks you first. This profile keeps the OS-level sandbox on, allows writes to the current workspace, enables network access inside the sandbox, and asks before escalating.

#:schema https://developers.openai.com/codex/config-schema.json

# Model: pin one or delete the line to use the current recommended default.
model = "gpt-5.6-sol"
model_reasoning_effort = "high"
model_reasoning_summary = "detailed"

# Approvals and sandbox (two independent controls)
approval_policy = "on-request"
sandbox_mode = "workspace-write"

# Web search: "cached" (default), "live", or "disabled"
web_search = "live"

# Show reasoning in the TUI
hide_agent_reasoning = false
show_raw_agent_reasoning = true

check_for_update_on_startup = true

[sandbox_workspace_write]
network_access = true
exclude_tmpdir_env_var = false
exclude_slash_tmp = false

[projects."/ai-agent"]
trust_level = "trusted"

Changes compared to the old version of this file:

  • model: gpt-5-codex and its successors up to gpt-5.4 are retired for ChatGPT sign-in. The current family is GPT-5.6 with gpt-5.6-sol (most capable, coding and research), gpt-5.6-terra (everyday work) and gpt-5.6-luna (fast and cheap), with GPT-6 Astra rolling out on top. Check the models page when you set this up, or simply delete the line and let Codex pick its recommended default.
  • approval_policy: on-failure is deprecated. Use on-request for interactive sessions and never for non-interactive runs.
  • web_search: the boolean form is deprecated. cached serves results from an OpenAI-maintained index (safer against prompt injection), live fetches current pages.
  • network_access: the top-level key from the old file was never valid. It belongs in the [sandbox_workspace_write] table. Keep in mind that TOML tables must come after all top-level keys, which is why the table sections are at the end.
  • disable_response_storage was dropped because it is no longer part of the configuration reference.
  • projects."/ai-agent".trust_level: marks the whole workspace as trusted, so Codex loads project-level .codex/config.toml files and AGENTS.md without asking every time.
  • The #:schema line enables validation and autocompletion with the "Even Better TOML" extension in VS Code.

YOLO Profile (Sandbox Disabled)

Codex still offers --dangerously-bypass-approvals-and-sandbox, alias --yolo, for the moments when the sandbox gets in the way. Instead of editing your main config back and forth, Codex now supports profile files: a file named ~/.codex/<name>.config.toml is layered on top of config.toml when you pass --profile <name>. Create ~/ai-agent/.codex/yolo.config.toml:

approval_policy = "never"
sandbox_mode = "danger-full-access"
web_search = "live"

Then start Codex with codex --profile yolo. Everything not set in the profile is inherited from config.toml. This removes the filesystem guard rails and every approval prompt, so use it only in this disposable container and switch back to the default profile when you are done. An admin-enforced requirements.toml can forbid this combination on managed machines.

Create Host Aliases for Claude Code and Codex CLI

Add the following block to ~/.bashrc or ~/.zshrc on the host and replace yourUserName. Unlike the old single-line aliases, these are shell functions. They pass arguments with spaces correctly, they always translate your current host directory into the matching container path, and they fall back to /ai-agent when you call them from outside the shared folder instead of failing with a confusing Docker error.

# --- AI agent container helpers -------------------------------------------
AI_AGENT_USER="yourUserName"        # container user (same name and UID as on the host)
AI_AGENT_HOST_DIR="$HOME/ai-agent"  # shared workspace on the host

# Map the current host directory to its path inside the container.
_ai_workdir() {
  local root real
  root="$(cd "$AI_AGENT_HOST_DIR" && pwd -P)"
  real="$(pwd -P)"
  case "$real" in
    "$root"|"$root"/*) printf '/ai-agent%s\n' "${real#"$root"}" ;;
    *) printf '/ai-agent\n' ;;
  esac
}

# Run any command inside the container as your user, in the mapped directory.
ai() {
  docker exec -it -u "$AI_AGENT_USER" -w "$(_ai_workdir)" ai-agent \
    bash -lc 'exec "$@"' _ "$@"
}

# The two agents. Permission modes come from their config files.
claude() { ai claude "$@"; }
codex()  { ai codex "$@"; }

# Root shell in the container for apt and debugging
alias ai-agent='docker exec -it -w /ai-agent ai-agent bash -l'

# Update OS packages and both CLIs
ai-agent-update() {
  docker exec -it ai-agent bash -lc \
    'export DEBIAN_FRONTEND=noninteractive; apt update && apt upgrade -y && apt autoremove -y'
  ai claude update
  ai bash -c 'curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh'
}

Reload your shell:

source ~/.bashrc

A few notes:

  • bash -lc 'exec "$@"' _ "$@" starts a login shell (so ~/.local/bin is on the PATH) and hands your arguments over untouched. The old $* inside a quoted string broke prompts containing spaces or quotes.
  • The old claude alias passed --permission-mode bypassPermissions --dangerously-skip-permissions on every start. The two flags are equivalent, and with defaultMode in the settings file neither is needed. If you keep auto as the file default and want an occasional YOLO session, define an extra function: claude-yolo() { ai claude --dangerously-skip-permissions "$@"; }
  • The old aliases ran claude install and npm install -g @openai/codex@latest on every start. Claude Code now updates itself in the background, and Codex checks for updates on startup, so the update logic moved into ai-agent-update.
  • The ai helper is handy on its own: ai npm test, ai python3 script.py or ai bash for a user shell in the right directory.

First Login

Both CLIs authenticate through your browser. With --network host the callback servers they start inside the container are reachable on your host, so the flow feels native.

Claude Code: start it from any project folder inside ~/ai-agent and follow the prompts:

cd ~/ai-agent
claude

On first start you pick a theme, then a login URL is printed. Open it in your host browser, sign in with your Claude account and the CLI picks up the token automatically. If the browser cannot reach the callback (bridge networking, remote host), run claude auth login instead, which lets you paste the authorization code into the terminal. Credentials are stored in ~/.claude/.credentials.json and session state in ~/.claude.json, both on the shared folder, so you stay logged in across container rebuilds.

Codex CLI:

codex login

Codex prints a URL and listens on localhost:1455. Open the URL on the host, sign in with your ChatGPT account, done. Without host networking use codex login --device-auth, which shows a code to enter on the OpenAI website instead. To use an API key rather than a subscription, export OPENAI_API_KEY inside the container before running codex. Tokens land in ~/.codex/auth.json. Treat that file like a password.

Finally, run a health check for Claude Code. It validates the settings file and reports anything it does not understand:

claude doctor

Optional: Git Identity and SSH for the Agents

Both agents commit code. Give the container user a git identity once:

ai git config --global user.name "Your Name"
ai git config --global user.email "you@example.com"

For pushing to remotes you have two options. The simple one is gh auth login inside the container (if you installed the GitHub CLI), which stores a token in the shared home. The more careful one is SSH agent forwarding: add -v "$SSH_AUTH_SOCK:/ssh-agent" -e SSH_AUTH_SOCK=/ssh-agent to the docker run command so the container can use your host keys without ever seeing the private key files. Whatever you choose, remember that in bypassPermissions or YOLO mode the agent can use those credentials without asking, so consider a deploy key or a fine-grained token with limited scope.

Daily Workflow

Stay inside ~/ai-agent (or any nested project folder) and call the agents directly:

cd ~/ai-agent/my-project
claude "add input validation to the signup form and write tests"
codex "explain the caching layer in this repo"

Both CLIs use the directory you were in as their working directory, so they read and edit exactly the project you meant. Keep VS Code or another editor pointed at ~/ai-agent on the host to watch changes as they happen. Put project instructions in CLAUDE.md (Claude Code) and AGENTS.md (Codex) at the project root; both agents read them automatically.

Keep the Container Updated

Run the helper from time to time. It upgrades the Ubuntu packages, tells Claude Code to update immediately instead of waiting for its background check, and reruns the Codex installer, which is the official way to upgrade a standalone Codex install:

ai-agent-update

Reset or Rebuild the Container

Because the container user's home is the shared folder, a rebuild does not cost you anything but the apt packages:

docker rm -f ai-agent
# run the docker run command from above again, then:
docker exec -it ai-agent bash -l
# ... repeat "Install the Developer Toolkit" and "Create a Container User"

The CLIs in /ai-agent/.local, both logins, all settings and your projects are still there. If you want a truly clean slate for the agents, delete ~/ai-agent/.claude, ~/ai-agent/.claude.json and ~/ai-agent/.codex on the host.

Security Notes

  • The container is the isolation boundary. Only mount what the agents need. Everything under ~/ai-agent is fair game for an agent in bypassPermissions or YOLO mode, including deleting it, so keep backups or commit often.
  • Never mount your host home directory, ~/.ssh, ~/.aws or the Docker socket into the container. The deny list in settings.json is a second line of defense, not a replacement for not mounting secrets in the first place.
  • --network host gives the container the host's network identity. Services listening on your host's localhost are reachable from the container. If that matters to you, use bridge networking and the terminal login fallbacks.
  • Prefer auto mode over bypassPermissions when in doubt. It costs a little latency per shell command and stops the classic prompt-injection outcomes (exfiltrating data, downloading and executing code) that bypassPermissions happily executes.
  • For an extra layer inside the container, install bubblewrap and socat and enable Claude Code's Bash sandbox with /sandbox. In an unprivileged container you also need "sandbox": { "enabled": true, "enableWeakerNestedSandbox": true } in settings.json.
  • Review the deny list whenever you add secrets, infrastructure state or credential files to a project. Add project-specific rules in the project's own .claude/settings.json, which you can commit for your team.
  • The plaintext token files ~/.codex/auth.json and ~/.claude/.credentials.json live on your host disk under ~/ai-agent. Do not sync that folder to a cloud drive or commit it anywhere.

Conclusion

With this setup, Claude Code and the OpenAI Codex CLI run side by side in a pinned Ubuntu 26.04 container as a non-root user that mirrors your host account. The configuration files match the current documentation of both vendors, the agents can work autonomously without touching anything outside one folder, logins and settings survive rebuilds, and a single ai-agent-update keeps everything current. From here you can layer on MCP servers, project-level CLAUDE.md and AGENTS.md instructions, or reuse the same container image in CI to keep your AI coding workflows consistent across machines.