Ctrl K

Claude Code Docker Setup

Run Claude Code inside a reusable Docker container, scoped to one mounted project with persistent subscription auth.

Run Claude Code inside Docker on the Arch host with a reusable image and per-project mounts. The container is created fresh for each run, can read and edit only the mounted project directory, and stores Claude subscription auth in ~/.config/claude-box/state.

Setup model

  • Use one generic image named claude-box for every project.
  • Run ephemeral containers with --rm.
  • Launch from a specific repo root so only that repo is mounted into /workspace.
  • Persist Claude Code auth and local CLI state under ~/.config/claude-box/state.
  • Do not build one image per project.

Create config directory

Keep the Dockerfile and Claude auth state under ~/.config/claude-box. Create both the state directory and .claude.json before the first run so Docker mounts them as the expected directory and file.

mkdir -p ~/.config/claude-box/state
touch ~/.config/claude-box/state/.claude.json
nano ~/.config/claude-box/Dockerfile

Create Dockerfile

FROM archlinux:latest

RUN pacman -Syu --noconfirm nodejs npm git ca-certificates && \
    pacman -Scc --noconfirm

RUN npm install -g @anthropic-ai/claude-code

ARG UID=1000
ARG GID=1000
RUN groupadd -g ${GID} claude && \
    useradd -m -u ${UID} -g ${GID} claude

USER claude
WORKDIR /workspace

ENTRYPOINT ["claude"]

Check Docker

Confirm the Docker daemon is running before building the image. If Docker group membership was just changed, log out and back in before continuing.

docker ps

sudo systemctl enable --now docker
sudo usermod -aG docker $USER

Build the image

Build the image with the host UID and GID so files written inside the mounted repo have clean ownership on the host.

docker build \
  --build-arg UID=$(id -u) \
  --build-arg GID=$(id -g) \
  -t claude-box \
  ~/.config/claude-box

docker images | grep claude-box

Run from a project

Start Claude Code from the target repo root. The current directory becomes /workspace inside the container.

cd ~/projects/mlnotebooks

docker run -it --rm \
  --cap-drop=ALL \
  --security-opt no-new-privileges \
  --user "$(id -u):$(id -g)" \
  -v "$(pwd)":/workspace \
  -v ~/.config/claude-box/state:/home/claude/.claude \
  -v ~/.config/claude-box/state/.claude.json:/home/claude/.claude.json \
  claude-box
  • On first run, choose the Claude account with subscription login option.
  • Open the printed URL in the browser, authorize, then return to the terminal.
  • Later runs reuse the token and local Claude state from ~/.config/claude-box/state.

Keep billing on subscription

  • Use the Claude account subscription login flow for Pro or Max.
  • Do not set ANTHROPIC_API_KEY for this workflow.
  • Decline API-credit prompts if the goal is to stay within plan allocation.
  • Use /status inside Claude Code to inspect current usage mode.
echo "$ANTHROPIC_API_KEY"

Security boundary

  • The mount is the workspace boundary. Only launch from the repo Claude should access.
  • Do not launch from ~/projects because that exposes every project under it.
  • The container runs as the host UID and GID, not as root.
  • --cap-drop=ALL and --security-opt no-new-privileges reduce container privileges.
  • Anything mounted is writable by Claude Code, including .env files and development secrets in the repo.
  • Container isolation is not the same as a VM. Use a VM if the threat model requires a separate kernel.

Add project context

Put project-specific instructions in CLAUDE.md at the repo root. Claude Code reads it automatically, and it can be version-controlled with the project.

cd ~/projects/mlnotebooks
nano CLAUDE.md
  • Use CLAUDE.md for conventions, project commands, boundaries, and recurring lessons.
  • A per-project .claude directory may be created by Claude Code and can stay with the repo if the settings are meant to travel with it.
  • The same claude-box image stays generic. Project focus comes from the mount.

Know the limits

This standalone container is for reading and editing code. It does not control the host Docker daemon, join the project Compose network, or reach services by Compose DNS names.

  • It cannot run docker compose unless Docker access is deliberately added.
  • It cannot reach services such as backend, mysql, redis, or qdrant by Compose service name.
  • To operate the stack later, add a claude service to the project docker-compose.yml and join the existing network.
  • Do not mount /var/run/docker.sock into this container unless host-level Docker control is explicitly intended.

Troubleshoot

IssueFix
Blank screen or silent hangOmit --read-only, or add writable tmpfs mounts for /tmp, /home/claude/.cache, and /home/claude/.npm
Host files get wrong ownershipRebuild with --build-arg UID=$(id -u) and --build-arg GID=$(id -g)
Docker permission deniedAdd the user to the docker group, then log out and back in
Claude asks about API creditsConfirm ANTHROPIC_API_KEY is empty and use the subscription login flow
Claude asks to log in every runConfirm both /home/claude/.claude and /home/claude/.claude.json are mounted from ~/.config/claude-box/state

Update later

Rebuild the generic image when Claude Code updates or when setting up a new machine.

docker build \
  --build-arg UID=$(id -u) \
  --build-arg GID=$(id -g) \
  -t claude-box \
  ~/.config/claude-box

File locations

  • Generic Dockerfile: ~/.config/claude-box/Dockerfile
  • Claude auth and CLI state directory: ~/.config/claude-box/state
  • Claude local state file: ~/.config/claude-box/state/.claude.json
  • Mounted project inside container: /workspace
  • Project context file: CLAUDE.md
  • Optional project settings: .claude/
  • Docker image name: claude-box