Containerize a Project#
How to build and run the container image that osprey build generates for
every project.
What You’ll Learn
What the generated
Dockerfile/.dockerignoreare and who owns themBuilding and running the image (ports, secrets, volumes)
The three build-arg extension points for site-specific installs
Path relocation with
osprey claude regen --runtime-rootAir-gapped images, the non-root requirement, and Kubernetes notes
Prerequisites: Docker (or Podman) installed; a project built with
osprey build.
Overview#
Every project built by osprey build includes a reference container
recipe at the project root:
Dockerfile— a complete, self-documenting image definition that installs Claude Code and OSPREY, copies the project in, relocates its recorded paths, and serves the web terminal..dockerignore— keeps secrets (.env) and host-specific state (.venv,.git,_agent_data/) out of the image.
Both files are generated once and then yours: edit them freely or delete
them. osprey claude regen never touches them. To get a fresh copy,
rebuild the project with osprey build.
Note
This page covers the project image — one container that runs the
assistant and its web terminal. It is unrelated to osprey deploy,
which manages the project’s service containers (databases, MCP
servers); see Container Deployment for those.
Quickstart#
cd my-project # the directory osprey build created
docker build -t my-project .
docker run --rm -p 8087:8087 --env-file .env my-project
Then open http://localhost:8087. Secrets are passed at runtime via
--env-file — the .dockerignore guarantees .env itself never
enters the image.
Build Arguments#
The image exposes exactly three knobs for site-specific builds:
ARG |
Default |
Purpose |
|---|---|---|
|
|
pip requirement for OSPREY. Override with a |
|
|
Hosts exempted from any proxy during |
|
|
|
Example — install OSPREY from an internal mirror behind a proxy, with vendored assets for an air-gapped host:
docker build -t my-project \
--build-arg OSPREY_PIP_SPEC="git+https://git.example.gov/tools/osprey.git@main" \
--build-arg PIP_NO_PROXY="git.example.gov" \
--build-arg OSPREY_OFFLINE=1 .
Warning
Build-arg values persist in the image history (docker history).
Never put credentials in OSPREY_PIP_SPEC URLs for images you
distribute — prefer Docker build secrets or a credential-free
internal mirror.
Path Relocation#
A project built on a host records host paths in config.yml
(project_root, execution.python_env_path). The generated Dockerfile
fixes both during the image build:
RUN osprey claude regen --project /app/my-project --runtime-root /app/my-project
--runtime-root rewrites project_root in config.yml
(comment-preserving), replaces a recorded python_env_path that doesn’t
exist in the container with the image’s interpreter, and re-renders the
Claude Code artifacts (.mcp.json, CLAUDE.md, .claude/) against
the new root. This works for projects built with or without
osprey build --runtime-root.
Why Non-Root#
The image creates and switches to an unprivileged osprey user because
Claude Code refuses to run in bypassPermissions mode as root. The
native Claude Code installer lives under /root/.local; the Dockerfile
makes that chain world-traversable so the runtime user can execute it.
Keep both pieces if you customize the recipe.
Runtime State and Volumes#
Two kinds of state are worth persisting across container restarts:
docker run --rm -p 8087:8087 --env-file .env \
-v my-project-agent-data:/app/my-project/_agent_data \
-v my-project-home:/home/osprey \
my-project
_agent_data/— executed scripts, user memory, API call logs./home/osprey— Claude Code’s per-user state (sessions, credentials); setCLAUDE_CONFIG_DIRif you want it somewhere more explicit.
Kubernetes notes#
Give each user/instance a PVC for
/home/osprey(orCLAUDE_CONFIG_DIR) and one for_agent_data/— session state does not survive pod rescheduling otherwise.The container already runs as a non-root user, so a restricted
securityContext(runAsNonRoot: true) works out of the box.Expose port
8087(or override theCMDwith--port).
Troubleshooting#
pip fails building accelerator-toolbox on Apple Silicon — OSPREY’s
dependency chain ships linux/amd64 wheels only, and the slim base image
has no compiler. Build (and run) the amd64 image under Docker Desktop’s
emulation instead; it matches the usual amd64 deployment target:
docker build --platform linux/amd64 -t my-project .
docker run --platform linux/amd64 --rm -p 8087:8087 --env-file .env my-project
Customizing#
The file is yours — common edits:
Layer a site image on top: build the generated image as a base, then
FROMit in a small site Dockerfile that adds credentials helpers, enterprisemanaged-settings.json, or extra processes.Change the entrypoint: the default
CMDrunsosprey web --host 0.0.0.0 --port 8087; override it to run a process supervisor if you add sidecars.Template-level override: a build profile’s app bundle can ship its own
apps/<bundle>/Dockerfile.j2, which takes precedence over the framework template at build time — use this when every project built from a bundle needs the same customization.
See also
- Container Deployment
Service containers (databases, MCP servers) via
osprey deploy— the complement to the project image on this page.- CLI Reference
osprey claude regen --runtime-rootandosprey vendorreference.