5bc8e447da
Use a dedicated development image so Cargo builds have the Rust toolchain, rustfmt, pkg-config, and DBus headers instead of inheriting the minimal release runtime.\n\nReplace the host HOME bind mount with named volumes for CodeWhale state and Cargo artifacts. This avoids invalid Windows HOME expansion, preserves non-root write access, and keeps Rust build churn off the workspace bind mount.\n\nDocument the Dev Container storage and rebuild behavior.\n\nVerification:\n- Built the Dev Container image with Docker Desktop\n- Verified non-root Cargo, Rust, Git, pkg-config, DBus discovery, and writable state/target paths\n- cargo build --locked\n- cargo test -p codewhale-config --locked (cherry picked from commit 8b1ceea7f03394246fa86b9a23b7c75fe661dd46) Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com>
299 lines
10 KiB
Markdown
299 lines
10 KiB
Markdown
# Docker
|
|
|
|
Codewhale publishes a multi-arch Linux image to GitHub Container Registry
|
|
for each release.
|
|
|
|
```bash
|
|
docker pull ghcr.io/hmbown/codewhale:latest
|
|
```
|
|
|
|
## Quick start
|
|
|
|
Run the published image with a Docker-managed data volume:
|
|
|
|
```bash
|
|
docker volume create codewhale-home
|
|
|
|
docker run --rm -it \
|
|
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
|
|
-v codewhale-home:/home/codewhale/.codewhale \
|
|
-v "$PWD:/workspace" \
|
|
-w /workspace \
|
|
ghcr.io/hmbown/codewhale:latest
|
|
```
|
|
|
|
Use a pinned release tag for reproducible installs:
|
|
|
|
```bash
|
|
docker run --rm -it \
|
|
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
|
|
-v codewhale-home:/home/codewhale/.codewhale \
|
|
-v "$PWD:/workspace" \
|
|
-w /workspace \
|
|
ghcr.io/hmbown/codewhale:vX.Y.Z
|
|
```
|
|
|
|
Replace `vX.Y.Z` with a tag from
|
|
[GitHub Releases](https://github.com/Hmbown/CodeWhale/releases).
|
|
|
|
## Default image contract
|
|
|
|
`ghcr.io/hmbown/codewhale:latest` and the semver tags are conservative runtime
|
|
images:
|
|
|
|
- the container runs as the non-root `codewhale` user with UID/GID `1000:1000`
|
|
- the image does not grant passwordless `sudo`
|
|
- the image is meant to run Codewhale against mounted workspaces, not to mutate
|
|
the base operating system at runtime
|
|
- user state belongs in a volume mounted at `/home/codewhale/.codewhale`
|
|
|
|
That default is intentional. Keep using it for the smallest trust boundary. If a
|
|
project needs `apt-get`, compiler toolchains, Node/Python package managers,
|
|
custom CA certificates, or other host-like setup inside Docker, build an
|
|
explicit toolbox image instead of changing the default image contract.
|
|
|
|
## Opt-in toolbox/custom image
|
|
|
|
The repository includes an example
|
|
[`docs/examples/Dockerfile.toolbox`](examples/Dockerfile.toolbox) that extends
|
|
the official image with passwordless `sudo` and common development packages.
|
|
Build it with a pinned Codewhale tag when you want repeatable project
|
|
environments:
|
|
|
|
```bash
|
|
docker build -f docs/examples/Dockerfile.toolbox \
|
|
--build-arg CODEWHALE_IMAGE=ghcr.io/hmbown/codewhale:vX.Y.Z \
|
|
--build-arg TOOLBOX_PACKAGES="git openssh-client curl build-essential pkg-config python3 python3-pip nodejs npm" \
|
|
-t codewhale-toolbox:my-project .
|
|
```
|
|
|
|
Use `latest` only for throwaway testing. For shared projects, keep the
|
|
`CODEWHALE_IMAGE` value pinned and review package additions like any other
|
|
development-environment change.
|
|
|
|
Run the toolbox image with the same workspace and state mounts:
|
|
|
|
```bash
|
|
docker volume create codewhale-my-project-home
|
|
|
|
docker run --rm -it \
|
|
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
|
|
-v codewhale-my-project-home:/home/codewhale/.codewhale \
|
|
-v "$PWD:/workspace" \
|
|
-w /workspace \
|
|
codewhale-toolbox:my-project
|
|
```
|
|
|
|
Inside this opt-in image, Codewhale can use commands such as
|
|
`sudo apt-get update` and `sudo apt-get install -y <package>`. For repeatable
|
|
containers, prefer baking those packages into the toolbox Dockerfile instead of
|
|
letting a long-lived container drift.
|
|
|
|
Do not bake API keys, SSH private keys, or other secrets into custom images.
|
|
Pass API keys at runtime and mount any SSH material deliberately, preferably
|
|
read-only and only for projects that need it.
|
|
|
|
### Compose toolbox template
|
|
|
|
If you prefer a repeatable `docker compose` entry point, use
|
|
[`docs/examples/compose.toolbox.yml`](examples/compose.toolbox.yml). It builds
|
|
the toolbox image from [`docs/examples/Dockerfile.toolbox`](examples/Dockerfile.toolbox)
|
|
and keeps the project state volume explicit:
|
|
|
|
```bash
|
|
CODEWHALE_IMAGE=ghcr.io/hmbown/codewhale:vX.Y.Z \
|
|
CODEWHALE_TOOLBOX_IMAGE=codewhale-toolbox:my-project \
|
|
CODEWHALE_HOME_VOLUME=codewhale-my-project-home \
|
|
CODEWHALE_WORKSPACE="$PWD" \
|
|
docker compose -f docs/examples/compose.toolbox.yml run --rm codewhale
|
|
```
|
|
|
|
Use a different `CODEWHALE_TOOLBOX_IMAGE` and `CODEWHALE_HOME_VOLUME` for each
|
|
project that needs an independent toolchain or independent `.codewhale` state.
|
|
The Compose file also shows opt-in, read-only mounts for SSH material and local
|
|
CA certificates; keep those commented out unless the project needs them.
|
|
|
|
## Multiple independent projects
|
|
|
|
Use one named state volume per project so sessions, config, skills, memory, and
|
|
the offline queue do not bleed across workspaces:
|
|
|
|
```bash
|
|
project="$(basename "$PWD")"
|
|
image="codewhale-toolbox:${project}"
|
|
docker volume create "codewhale-${project}-home"
|
|
|
|
docker run --rm -it \
|
|
--name "codewhale-${project}" \
|
|
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
|
|
-v "codewhale-${project}-home:/home/codewhale/.codewhale" \
|
|
-v "$PWD:/workspace" \
|
|
-w /workspace \
|
|
"$image"
|
|
```
|
|
|
|
For projects with different toolchains, build different toolbox tags, for
|
|
example `codewhale-toolbox:frontend` and `codewhale-toolbox:backend`. The
|
|
separate launcher idea discussed in issue #2217 can build on this contract, but
|
|
it is intentionally outside the core Docker image.
|
|
|
|
## Project bootstrap scripts
|
|
|
|
Codewhale does not automatically execute `.codewhale/setup.sh` or legacy
|
|
`.deepseek/setup.sh`. If you keep one of those files as a local project recipe,
|
|
run it explicitly. For shared team setup, prefer a committed project script or
|
|
the toolbox Dockerfile so the environment can be reviewed and rebuilt.
|
|
|
|
For example, to run a committed bootstrap script before starting Codewhale:
|
|
|
|
```bash
|
|
docker run --rm -it \
|
|
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
|
|
-v codewhale-my-project-home:/home/codewhale/.codewhale \
|
|
-v "$PWD:/workspace" \
|
|
-w /workspace \
|
|
--entrypoint bash \
|
|
codewhale-toolbox:my-project \
|
|
-lc './scripts/bootstrap-dev.sh && exec codewhale'
|
|
```
|
|
|
|
Use the toolbox image for bootstrap scripts that need `sudo`. The default image
|
|
will not elevate privileges.
|
|
|
|
## Custom CA certificates and proxies
|
|
|
|
For corporate proxies, dev-sidecar, or self-signed internal services, prefer
|
|
baking trusted CA certificates into a custom toolbox image:
|
|
|
|
```dockerfile
|
|
USER root
|
|
COPY docker/certs/*.crt /usr/local/share/ca-certificates/
|
|
RUN update-ca-certificates
|
|
USER codewhale
|
|
```
|
|
|
|
All files copied into `/usr/local/share/ca-certificates/` must use the `.crt`
|
|
extension. Keep private CA material out of public images.
|
|
|
|
For a local-only run, mount certificates read-only and update the trust store at
|
|
container start:
|
|
|
|
```bash
|
|
docker run --rm -it \
|
|
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
|
|
-v codewhale-my-project-home:/home/codewhale/.codewhale \
|
|
-v "$PWD:/workspace" \
|
|
-v "$PWD/docker/certs:/usr/local/share/ca-certificates/local:ro" \
|
|
-w /workspace \
|
|
--entrypoint bash \
|
|
codewhale-toolbox:my-project \
|
|
-lc 'sudo update-ca-certificates && exec codewhale'
|
|
```
|
|
|
|
This CA workflow requires the opt-in toolbox image because the default image
|
|
does not include passwordless `sudo`.
|
|
|
|
## Local build
|
|
|
|
Build the image locally from a checkout:
|
|
|
|
```bash
|
|
docker build -t codewhale .
|
|
```
|
|
|
|
Then run it with the same Docker-managed data volume:
|
|
|
|
```bash
|
|
docker run --rm -it \
|
|
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
|
|
-v codewhale-home:/home/codewhale/.codewhale \
|
|
-v "$PWD:/workspace" \
|
|
-w /workspace \
|
|
codewhale
|
|
```
|
|
|
|
Docker Hub publishing is not configured; GHCR is the supported prebuilt image
|
|
registry.
|
|
|
|
## Environment variables
|
|
|
|
| Variable | Required | Description |
|
|
|-----------------------|----------|--------------------------------------------------|
|
|
| `DEEPSEEK_API_KEY` | yes | DeepSeek API key |
|
|
| `DEEPSEEK_BASE_URL` | no | Custom API base URL (e.g. `https://api.deepseek.com`) |
|
|
| `DEEPSEEK_NO_COLOR` | no | Set to `1` to disable terminal colour output |
|
|
|
|
## Volumes
|
|
|
|
Mount `/home/codewhale/.codewhale` to persist sessions, config, skills, memory,
|
|
and the offline queue across container restarts. The image also keeps
|
|
`/home/codewhale/.deepseek` available for legacy compatibility. A
|
|
Docker-managed named volume is the safest default because Docker creates it with
|
|
ownership the container can write:
|
|
|
|
```bash
|
|
-v codewhale-home:/home/codewhale/.codewhale
|
|
```
|
|
|
|
Without this mount the container starts fresh each time.
|
|
|
|
If you bind-mount an existing host directory instead, the image runs as the
|
|
non-root `codewhale` user with UID/GID `1000:1000`. The mounted directory must be
|
|
writable by that user, or startup can fail while creating runtime directories
|
|
under `.codewhale/tasks`. On Linux hosts, either use the named volume above or
|
|
prepare the bind mount explicitly:
|
|
|
|
```bash
|
|
mkdir -p ~/.codewhale
|
|
sudo chown -R 1000:1000 ~/.codewhale
|
|
|
|
docker run --rm -it \
|
|
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \
|
|
-v ~/.codewhale:/home/codewhale/.codewhale \
|
|
ghcr.io/hmbown/codewhale:latest
|
|
```
|
|
|
|
That `chown` changes ownership of the host `~/.codewhale` directory. Skip it if
|
|
you do not want the container UID to own your local config, and use a named
|
|
volume instead.
|
|
|
|
## Non-interactive / pipeline usage
|
|
|
|
When stdin is not a TTY, `codewhale` drops to the dispatcher's one-shot mode
|
|
(`codewhale -c "…"`). Pipe a prompt on stdin:
|
|
|
|
```bash
|
|
echo "Explain the Cargo.toml in structured English." | \
|
|
docker run --rm -i -e DEEPSEEK_API_KEY ghcr.io/hmbown/codewhale:latest
|
|
```
|
|
|
|
## Building locally
|
|
|
|
```bash
|
|
# Single platform (your host architecture)
|
|
docker build -t codewhale .
|
|
|
|
# Multi-platform (requires a builder with emulation)
|
|
docker buildx create --use
|
|
docker buildx build --platform linux/amd64,linux/arm64 -t codewhale .
|
|
```
|
|
|
|
## Devcontainer
|
|
|
|
The repository includes a [`.devcontainer/devcontainer.json`](../.devcontainer/devcontainer.json)
|
|
configuration for VS Code / GitHub Codespaces. It builds a dedicated development
|
|
image with the Rust toolchain, Git, `pkg-config`, and the DBus development headers
|
|
required by the workspace. The first open runs `cargo build --locked` and installs
|
|
rust-analyzer and the other editor extensions.
|
|
|
|
The source checkout remains mounted from the host. CodeWhale state and Cargo build
|
|
artifacts use Docker named volumes instead, so the configuration works when VS Code
|
|
cannot provide a POSIX-style `HOME` variable (notably on Windows), and builds do not
|
|
write thousands of small files through a Windows bind mount. Rebuild the container
|
|
after changing the Dev Container configuration.
|
|
|
|
## Release status
|
|
|
|
Docker image publishing is part of the release gate. The image is published to
|
|
GHCR for `linux/amd64` and `linux/arm64` with semver tags plus `latest`.
|