Skip to content

Repository files navigation

Claude Code container

Run Claude Code in a disposable Docker container while keeping the Claude Code installation and its runtime dependencies isolated from the host. The image is intended for interactive, repository-aware use: mount a project into /work, start Claude Code there, and optionally mount the host's Claude Code configuration and Git configuration.

The published image is available from GitHub Container Registry:

ghcr.io/ophiosdev/claude-code:<version>

<version> is the installed @anthropic-ai/claude-code version. Pin a version for reproducible use; use the tag shown on the package's GitHub Container Registry page or the version you built yourself.

What this image does

  • Installs Claude Code in an oven/bun Debian-based image.
  • Starts Claude Code through a small entrypoint that supports a selected working directory and command.
  • Runs the container process as the host user's UID and GID when HOST_UID and HOST_GID are supplied, so files created in a mounted repository are not owned by root.
  • Disables Claude Code nonessential traffic by default with CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1.
  • Lets you pass Claude Code, Anthropic, Git, and other runtime settings through Docker environment variables.

This image does not provide an Anthropic account, API key, subscription, or model access. You must authenticate Claude Code using a supported method and comply with Anthropic's terms and policies.

Requirements

  • Docker Engine or Docker Desktop
  • An Anthropic account or another Claude Code-supported authentication provider
  • Access to the project you want Claude Code to work on
  • Credentials supplied securely at runtime (an API key should not be baked into an image)

The examples below use docker. Replace it with podman where the equivalent options are available.

Quick start

Create a working directory for the project, then run the published image with that directory mounted at /work:

docker run --rm -it \
  --env-file .env \
  -e HOST_UID="$(id -u)" \
  -e HOST_GID="$(id -g)" \
  -v "$PWD:/work" \
  -w /work \
  ghcr.io/ophiosdev/claude-code:<version>

The default command is claude, so this opens an interactive Claude Code session in the mounted project.

For a one-off command, pass Claude Code arguments after the image name:

docker run --rm -it \
  --env-file .env \
  -e HOST_UID="$(id -u)" \
  -e HOST_GID="$(id -g)" \
  -v "$PWD:/work" \
  -w /work \
  ghcr.io/ophiosdev/claude-code:<version> \
  --help

Configure Claude Code with an .env file

Using --env-file is the recommended way to provide runtime configuration. It keeps the command line short and avoids putting secrets directly in shell history or process arguments.

Create a local .env file next to the directory from which you run Docker:

# Required for API-key authentication. Keep this file private.
ANTHROPIC_API_KEY=sk-ant-replace-me

# Optional Claude Code settings. Add only the variables you need.
# ANTHROPIC_MODEL=claude-sonnet-4-20250514
# ANTHROPIC_SMALL_FAST_MODEL=claude-haiku-4-5-20251001
# CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1

Then load it when starting the container:

docker run --rm -it \
  --env-file .env \
  -e HOST_UID="$(id -u)" \
  -e HOST_GID="$(id -g)" \
  -v "$PWD:/work" \
  -w /work \
  ghcr.io/ophiosdev/claude-code:<version>

Docker's env-file syntax is one NAME=value assignment per line. Do not add export, and quote values when they contain whitespace or shell-sensitive characters. Variable expansion in .env files is Docker-version dependent; use literal values when portability matters.

Keep .env out of Git

If the file contains an API key, protect it and do not commit it:

chmod 600 .env
printf '\n.env\n' >> .gitignore

Treat .env as a secret. Do not publish it, include it in a container build context, paste it into issues, or use it in CI logs. For CI or production, prefer the CI provider's secret store or a secret manager and pass the value to Docker at runtime.

Precedence and overrides

If the same variable is specified more than once, the effective value depends on Docker's environment precedence rules. In normal usage, put defaults in .env and use an explicit -e NAME=value on the command line for a temporary override. Never put an API key in the Dockerfile, image build arguments, or a committed compose file.

The entrypoint intentionally forces CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 for the process it starts. This setting is therefore enabled even if it is absent from .env or set differently there.

Authentication and Claude Code configuration

Claude Code supports multiple authentication approaches. The simplest non-interactive approach is to provide an API key:

ANTHROPIC_API_KEY=sk-ant-replace-me

You can also authenticate interactively inside the container when the Claude Code version and provider support that flow. Interactive login state is ephemeral unless you persist the relevant Claude Code files.

Persisting Claude Code login state

The companion safeclaude helper mounts the host's Claude Code files into the container. You can do the same manually:

docker run --rm -it \
  --env-file .env \
  -e HOST_UID="$(id -u)" \
  -e HOST_GID="$(id -g)" \
  -v "$HOME/.claude.json:/home/hostuser/.claude.json" \
  -v "$HOME/.claude:/home/hostuser/.claude" \
  -v "$PWD:/work" \
  -w /work \
  ghcr.io/ophiosdev/claude-code:<version>

When /home/hostuser is used

/home/hostuser is the home directory created by the entrypoint only when both HOST_UID and HOST_GID are supplied and no existing account in the image already has HOST_UID. In that case, the entrypoint creates the user hostuser, creates /home/hostuser, and runs Claude Code with HOME=/home/hostuser.

The safeclaude helper always targets /home/hostuser, so its Claude state mounts are usable only under that condition. The manual example above has the same requirement. Before using those mounts, check whether the image already has an account for your host UID; an existing account such as bun causes the entrypoint to use that account instead, with HOME set to that account's home directory (for example /home/bun). In that case, mounts under /home/hostuser are not where Claude Code looks.

If HOST_UID and HOST_GID are omitted, the entrypoint does not create or select hostuser; it runs the command using the image's default user, and /home/hostuser has no special meaning. A mount can still create that path in the container, but Claude Code will not use it unless it is also running with HOME=/home/hostuser.

For reliable persistence, mount state into the effective home directory of the runtime user, or use an API key passed through a protected runtime .env file instead of sharing login state. You can inspect the active user and home directory with:

docker run --rm \
  -e HOST_UID="$(id -u)" \
  -e HOST_GID="$(id -g)" \
  ghcr.io/ophiosdev/claude-code:<version> \
  sh -c 'id -un; printf "HOME=%s\\n" "$HOME"'

Use state mounts only when you understand and trust the container. They expose local Claude Code state to the container. Ensure host source files and directories already exist before using -v; otherwise Docker may create an unintended directory for a file mount. If your host files use a different home directory or username, adjust the destination paths consistently with the UID/GID settings.

An API key in .env is often simpler and more portable than sharing login state.

Microsoft Foundry models

Claude Code can use Claude deployments hosted in Microsoft Foundry. Configure Foundry at runtime; do not put Azure credentials in the Dockerfile, an image layer, or a committed compose file.

For API-key authentication, add the following to the .env file passed with --env-file:

CLAUDE_CODE_USE_FOUNDRY=1
ANTHROPIC_FOUNDRY_RESOURCE=your-foundry-resource
ANTHROPIC_FOUNDRY_API_KEY=your-azure-api-key

# These must be the deployment names configured in Foundry.
ANTHROPIC_DEFAULT_OPUS_MODEL=your-opus-deployment
ANTHROPIC_DEFAULT_SONNET_MODEL=your-sonnet-deployment
ANTHROPIC_DEFAULT_HAIKU_MODEL=your-haiku-deployment

ANTHROPIC_FOUNDRY_RESOURCE is the Foundry resource name. If you need to specify the endpoint explicitly, use this instead:

CLAUDE_CODE_USE_FOUNDRY=1
ANTHROPIC_FOUNDRY_BASE_URL=https://your-resource.services.ai.azure.com/anthropic
ANTHROPIC_FOUNDRY_API_KEY=your-azure-api-key

Use either ANTHROPIC_FOUNDRY_RESOURCE or ANTHROPIC_FOUNDRY_BASE_URL, not both. The model values are Azure deployment names, which may be custom names and may not match the underlying Claude model IDs. Pin each model role to a deployment that exists in your Foundry resource; otherwise aliases such as opus and sonnet can resolve to defaults that are not deployed in your account.

For Microsoft Entra ID, omit the API-key variable and provide a bearer token instead:

CLAUDE_CODE_USE_FOUNDRY=1
ANTHROPIC_FOUNDRY_RESOURCE=your-foundry-resource
ANTHROPIC_FOUNDRY_AUTH_TOKEN=your-entra-access-token

The token is sent as a bearer token and must be available to the container at runtime. A token or API key supplied on the host is not automatically passed to Docker; use --env-file or explicit -e options. Claude Code can also use Azure's default credential chain when neither Foundry credential variable is set, but the required Azure credentials and SDK support must be available inside the container. Logging in with az login on the host alone does not make those credentials available in a disposable container. Foundry bearer-token support requires a recent Claude Code release; check the current Foundry setup documentation when pinning an older image.

Run the container with the file as usual:

docker run --rm -it \
  --env-file .env \
  -e HOST_UID="$(id -u)" \
  -e HOST_GID="$(id -g)" \
  -v "$PWD:/work" \
  -w /work \
  ghcr.io/ophiosdev/claude-code:<version>

Inside Claude Code, run /status to verify that the API provider is Microsoft Foundry and that the expected resource or base URL is being used. Some Claude features differ for Azure-hosted deployments; consult Claude in Microsoft Foundry for current feature availability.

Common environment variables

Claude Code and the underlying Anthropic tooling may recognize additional variables as versions evolve. Consult the Claude Code documentation for the exact variables supported by the version you run. Common examples include:

Variable Purpose
ANTHROPIC_API_KEY API key used for Anthropic API authentication. Keep it secret.
ANTHROPIC_MODEL Default model selection, when supported by the installed Claude Code version.
ANTHROPIC_SMALL_FAST_MODEL Deprecated auxiliary model setting; prefer ANTHROPIC_DEFAULT_HAIKU_MODEL for third-party providers when supported.
ANTHROPIC_DEFAULT_OPUS_MODEL Foundry deployment name to use for the Opus model role.
ANTHROPIC_DEFAULT_SONNET_MODEL Foundry deployment name to use for the Sonnet model role.
ANTHROPIC_DEFAULT_HAIKU_MODEL Foundry deployment name to use for the Haiku model role.
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC Disables nonessential traffic; this image forces it to 1.
CLAUDE_CODE_USE_FOUNDRY Enables the Microsoft Foundry provider when set to 1.
ANTHROPIC_FOUNDRY_RESOURCE Microsoft Foundry resource name; use instead of ANTHROPIC_FOUNDRY_BASE_URL.
ANTHROPIC_FOUNDRY_BASE_URL Explicit Foundry Anthropic endpoint; use instead of ANTHROPIC_FOUNDRY_RESOURCE.
ANTHROPIC_FOUNDRY_API_KEY Microsoft Foundry API key; keep it secret.
ANTHROPIC_FOUNDRY_AUTH_TOKEN Microsoft Entra bearer token for Foundry authentication.
CLAUDE_CODE_USE_BEDROCK Enables the Amazon Bedrock provider when configured and supported.
CLAUDE_CODE_USE_VERTEX Enables the Google Vertex AI provider when configured and supported.
AWS_REGION AWS region used by Bedrock-based configurations.
GOOGLE_CLOUD_PROJECT Google Cloud project used by Vertex-based configurations.

Provider-specific authentication still needs to be configured. For example, setting CLAUDE_CODE_USE_BEDROCK=1 does not create AWS credentials; pass credentials through Docker's supported mechanisms or use an appropriate mounted credential configuration.

Claude Code configuration hierarchy

Claude Code combines settings from several scopes. Use the narrowest scope that matches the intended audience:

Precedence Scope Location or mechanism Use it for
Highest Managed settings Organization-managed policy, or a system-level managed-settings.json Enforced enterprise policy that users must not override.
Command line Claude Code options such as --settings and permission-mode flags A one-session override or experiment.
Project-local settings /work/.claude/settings.local.json Personal settings for this checkout; do not commit them.
Shared project settings /work/.claude/settings.json Repository settings intended to be reviewed and shared with collaborators.
Lowest User settings ~/.claude/settings.json inside the container Your defaults across projects.

A setting at a higher-precedence scope normally overrides the same scalar setting at a lower scope. Array settings such as permission lists can be combined across scopes, and a higher-level deny cannot be undone by a lower-level allow. Managed policy remains authoritative. See the official Claude Code settings documentation for current merge rules and supported locations.

Configure settings globally for the container user

Global settings belong in the home directory of the user that runs Claude Code. With the standard commands in this README, that is the runtime user selected from HOST_UID and HOST_GID, so the effective path is usually:

/home/<runtime-user>/.claude/settings.json

For a disposable container, create or edit the file on the host and mount it at the corresponding home path, or mount the complete Claude Code state directory if you intentionally want to persist login state too. Make sure the mount destination matches the runtime user's HOME; mounting a file under /home/hostuser while Claude runs with HOME=/home/bun, for example, will not make it a global setting for that process.

A minimal global settings file might look like this:

{
  "permissions": {
    "deny": ["Bash(git push:*)"]
  }
}

Do not place API keys or other credentials in settings.json. Pass provider credentials through runtime environment variables as described above.

Configure settings for one repository

Shared repository configuration goes in the mounted project:

/work/.claude/settings.json

Commit this file only when the settings are appropriate for every contributor and every environment using the repository. Use it for reviewed project conventions, shared hooks, and narrowly scoped permissions.

Personal or machine-specific repository configuration goes in:

/work/.claude/settings.local.json

Keep this file out of Git. It is suitable for local permissions, experimental hooks, and settings that should not affect collaborators. The repository's .gitignore should ignore .claude/settings.local.json (or the entire .claude/ directory when no project settings are intended to be shared).

Inspect the active configuration

Inside Claude Code:

  • Run /status to see which settings sources are active.
  • Run /doctor to diagnose malformed or conflicting configuration.

Because this image is disposable, files written only inside the container disappear when it is removed. Mount the relevant host files or directories if global or local configuration must persist between runs. Also review mounted settings before use: hooks, permissions, and other automation are executable configuration and should be trusted like source code.

Working with files and Git

The container can only read and modify files that you mount. Mount a repository at /work and set the working directory explicitly:

docker run --rm -it \
  --env-file .env \
  -e HOST_UID="$(id -u)" \
  -e HOST_GID="$(id -g)" \
  -v "$PWD:/work" \
  -w /work \
  -v "$HOME/.gitconfig:/home/hostuser/.gitconfig:ro" \
  ghcr.io/ophiosdev/claude-code:<version>

The .gitconfig mount is optional and read-only. SSH keys, cloud credentials, package-manager credentials, and other host secrets are not mounted by default. Add only the minimum additional mounts needed for your task, and prefer read-only mounts where possible.

Because the entrypoint creates or selects a user matching HOST_UID and HOST_GID, files written to the project mount normally have your host ownership. Always verify the result when using unusual filesystems, rootless Docker, or network-mounted directories.

safeclaude shell helper

This project includes safeclaude.sh, which defines a safeclaude shell function. It automatically:

  • Resolves the current Claude Code package version from npm.
  • Uses the matching ghcr.io/ophiosdev/claude-code:<version> image.
  • Passes your host UID and GID.
  • Mounts the current directory at /work.
  • Mounts $HOME/.claude.json, $HOME/.claude, and a read-only $HOME/.gitconfig.

Source it from your shell, then run Claude Code from a repository:

source ./safeclaude.sh
safeclaude

Arguments are passed through to Claude Code:

safeclaude --help
safeclaude -p "Review the uncommitted changes and summarize them"

The helper currently does not automatically load .env or forward arbitrary host environment variables into Docker. Exporting variables before calling it is not sufficient unless the helper is changed to pass them with -e options. For Foundry, use the direct docker run --env-file .env examples above (or make run for a locally built image). The helper also depends on npm being available on the host to resolve the latest package version; pinning a version in a wrapper of your own can make automation more reproducible.

Building the image locally

The supplied Makefile builds a versioned image. Create a build .env file with the Claude Code package version:

CLAUDE_VERSION=0.0.0
IMAGE=claude-code
TAG=local

Replace 0.0.0 with the version you want to install, then run:

make build

The resulting local image is claude-code:local by default. The Makefile's run target also loads .env with --env-file, mounts the current directory, and passes the current host UID/GID:

make run

Keep build configuration and runtime secrets conceptually separate. CLAUDE_VERSION is a build argument; ANTHROPIC_API_KEY is a runtime secret and should not be used as a build argument.

Pulling and verifying the published image

Pull a specific version:

docker pull ghcr.io/ophiosdev/claude-code:<version>

Inspect the installed Claude Code version:

docker run --rm \
  ghcr.io/ophiosdev/claude-code:<version> \
  --version

Pinning an immutable image digest is preferable for automated workflows:

docker pull ghcr.io/ophiosdev/claude-code@sha256:<digest>

Security and privacy notes

  • Use --rm for disposable containers, but remember that mounted files are changes to the host and are not removed with the container.
  • Never bake ANTHROPIC_API_KEY or other credentials into an image layer.
  • Review every host path before mounting it. A mounted directory is available to Claude Code and the container process.
  • Mount sensitive configuration read-only where practical.
  • Run with the minimum credentials and repository access required for the task.
  • Do not mount the Docker socket unless you deliberately want to grant container-management access.
  • Claude Code can make changes to the mounted repository. Review diffs before committing or publishing them.
  • The image disables nonessential Claude Code traffic by default, but API calls needed for Claude Code operation still leave the container.

Troubleshooting

ANTHROPIC_API_KEY is missing or authentication fails

Confirm that the file exists and that Docker loaded it:

docker run --rm --env-file .env ghcr.io/ophiosdev/claude-code:<version> \
  env | grep '^ANTHROPIC_API_KEY='

Avoid sharing the output of this command because it prints the secret. If the variable is present but authentication still fails, check that the key is valid, has access to the selected model, and is accepted by the Claude Code version in the image.

Files are created as root

Pass both values, and make sure the mounted directory is writable by that UID/GID:

-e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)"

Some Docker, rootless-container, and network-filesystem configurations handle ownership differently.

The repository is not visible

Check that the host path is correct and that the container working directory matches the mount:

-v "$PWD:/work" -w /work

Login state is not retained

A container is disposable by design. Use the Claude Code state mounts described above, or use ANTHROPIC_API_KEY in a protected runtime .env file.

Microsoft Foundry authentication or model selection fails

Check that CLAUDE_CODE_USE_FOUNDRY=1 is present inside the container and that exactly one of ANTHROPIC_FOUNDRY_RESOURCE and ANTHROPIC_FOUNDRY_BASE_URL is configured. Confirm that the selected model variables contain the exact deployment names from Foundry, not only the public Claude model ID. For Entra ID authentication, make sure the bearer token or Azure default credentials are available inside the container; host credentials are not inherited automatically. Run /status in Claude Code to inspect the active provider and endpoint without printing secrets.

About

A containerized environment for running Claude Caude.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages