feat(dev): add local dockerized ci workflow (#342)

This commit is contained in:
Will Sarg 2026-02-16 09:10:39 -05:00 committed by GitHub
parent f2c73bacf8
commit b61d33aa1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 259 additions and 17 deletions

View file

@ -5,13 +5,13 @@ A fully containerized development sandbox for ZeroClaw agents. This environment
## Directory Structure
- **`agent/`**: (Merged into root Dockerfile)
- The development image is built from the root `Dockerfile` using the `dev` stage (`target: dev`).
- Based on `debian:bookworm-slim` (unlike production `distroless`).
- Includes `bash`, `curl`, and debug tools.
- The development image is built from the root `Dockerfile` using the `dev` stage (`target: dev`).
- Based on `debian:bookworm-slim` (unlike production `distroless`).
- Includes `bash`, `curl`, and debug tools.
- **`sandbox/`**: Dockerfile for the simulated user environment.
- Based on `ubuntu:22.04`.
- Pre-loaded with `git`, `python3`, `nodejs`, `npm`, `gcc`, `make`.
- Simulates a real developer machine.
- Based on `ubuntu:22.04`.
- Pre-loaded with `git`, `python3`, `nodejs`, `npm`, `gcc`, `make`.
- Simulates a real developer machine.
- **`docker-compose.yml`**: Defines the services and `dev-net` network.
- **`cli.sh`**: Helper script to manage the lifecycle.
@ -20,42 +20,53 @@ A fully containerized development sandbox for ZeroClaw agents. This environment
Run all commands from the repository root using the helper script:
### 1. Start Environment
```bash
./dev/cli.sh up
```
Builds the agent from source and starts both containers.
### 2. Enter Agent Container (`zeroclaw-dev`)
```bash
./dev/cli.sh agent
```
Use this to run `zeroclaw` CLI commands manually, debug the binary, or check logs internally.
- **Path**: `/zeroclaw-data`
- **User**: `nobody` (65534)
### 3. Enter Sandbox (`sandbox`)
```bash
./dev/cli.sh shell
```
Use this to act as the "user" or "environment" the agent interacts with.
- **Path**: `/home/developer/workspace`
- **User**: `developer` (sudo-enabled)
### 4. Development Cycle
1. Make changes to Rust code in `src/`.
2. Rebuild the agent:
```bash
./dev/cli.sh build
```
```bash
./dev/cli.sh build
```
3. Test changes inside the container:
```bash
./dev/cli.sh agent
# inside container:
zeroclaw --version
```
```bash
./dev/cli.sh agent
# inside container:
zeroclaw --version
```
### 5. Persistence & Shared Workspace
The local `playground/` directory (in repo root) is mounted as the shared workspace:
- **Agent**: `/zeroclaw-data/workspace`
- **Sandbox**: `/home/developer/workspace`
@ -64,8 +75,77 @@ Files created by the agent are visible to the sandbox user, and vice versa.
The agent configuration lives in `target/.zeroclaw` (mounted to `/zeroclaw-data/.zeroclaw`), so settings persist across container rebuilds.
### 6. Cleanup
Stop containers and remove volumes and generated config:
```bash
./dev/cli.sh clean
```
**Note:** This removes `target/.zeroclaw` (config/DB) but leaves the `playground/` directory intact. To fully wipe everything, manually delete `playground/`.
## Local CI/CD (Docker-Only)
Use this when you want CI-style validation without relying on GitHub Actions and without running Rust toolchain commands on your host.
### 1. Build the local CI image
```bash
./dev/ci.sh build-image
```
### 2. Run full local CI pipeline
```bash
./dev/ci.sh all
```
This runs inside a container:
- `cargo fmt --all -- --check`
- `cargo clippy --locked --all-targets -- -D clippy::correctness`
- `cargo test --locked --verbose`
- `cargo build --release --locked --verbose`
- `cargo deny check licenses sources`
- `cargo audit`
- Docker smoke build (`docker build --target dev ...` + `--version` check)
### 3. Run targeted stages
```bash
./dev/ci.sh lint
./dev/ci.sh test
./dev/ci.sh build
./dev/ci.sh deny
./dev/ci.sh audit
./dev/ci.sh security
./dev/ci.sh docker-smoke
```
Note: local `deny` focuses on license/source policy; advisory scanning is handled by `audit`.
### 4. Enter CI container shell
```bash
./dev/ci.sh shell
```
### 5. Optional shortcut via existing dev CLI
```bash
./dev/cli.sh ci
./dev/cli.sh ci lint
```
### Isolation model
- Rust compilation, tests, and audit/deny tools run in `zeroclaw-local-ci` container.
- Your host filesystem is mounted at `/workspace`; no host Rust toolchain is required.
- Cargo build artifacts are written to container volume `/ci-target` (not your host `target/`).
- Docker smoke stage uses your Docker daemon to build image layers, but build steps execute in containers.
### Build cache notes
- Both `Dockerfile` and `dev/ci/Dockerfile` use BuildKit cache mounts for Cargo registry/git data.
- Local CI reuses named Docker volumes for Cargo registry/git and target outputs.
- The CI image keeps Rust toolchain defaults from `rust:1.92-slim` (no custom `CARGO_HOME`/`RUSTUP_HOME` overrides), preventing repeated toolchain bootstrapping on each run.

103
dev/ci.sh Executable file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env bash
set -euo pipefail
if [ -f "dev/docker-compose.ci.yml" ]; then
COMPOSE_FILE="dev/docker-compose.ci.yml"
elif [ -f "docker-compose.ci.yml" ] && [ "$(basename "$(pwd)")" = "dev" ]; then
COMPOSE_FILE="docker-compose.ci.yml"
else
echo "❌ Run this script from repo root or dev/ directory."
exit 1
fi
compose_cmd=(docker compose -f "$COMPOSE_FILE")
run_in_ci() {
local cmd="$1"
"${compose_cmd[@]}" run --rm local-ci bash -c "$cmd"
}
print_help() {
cat <<'EOF'
ZeroClaw Local CI in Docker
Usage: ./dev/ci.sh <command>
Commands:
build-image Build/update the local CI image
shell Open an interactive shell inside the CI container
lint Run rustfmt + clippy (container only)
test Run cargo test (container only)
build Run release build smoke check (container only)
audit Run cargo audit (container only)
deny Run cargo deny check (container only)
security Run cargo audit + cargo deny (container only)
docker-smoke Build and verify runtime image (host docker daemon)
all Run lint, test, build, security, docker-smoke
clean Remove local CI containers and volumes
EOF
}
if [ $# -lt 1 ]; then
print_help
exit 1
fi
case "$1" in
build-image)
"${compose_cmd[@]}" build local-ci
;;
shell)
"${compose_cmd[@]}" run --rm local-ci bash
;;
lint)
run_in_ci "cargo fmt --all -- --check && cargo clippy --locked --all-targets -- -D clippy::correctness"
;;
test)
run_in_ci "cargo test --locked --verbose"
;;
build)
run_in_ci "cargo build --release --locked --verbose"
;;
audit)
run_in_ci "cargo audit"
;;
deny)
run_in_ci "cargo deny check licenses sources"
;;
security)
run_in_ci "cargo deny check licenses sources"
run_in_ci "cargo audit"
;;
docker-smoke)
docker build --target dev -t zeroclaw-local-smoke:latest .
docker run --rm zeroclaw-local-smoke:latest --version
;;
all)
run_in_ci "cargo fmt --all -- --check && cargo clippy --locked --all-targets -- -D clippy::correctness"
run_in_ci "cargo test --locked --verbose"
run_in_ci "cargo build --release --locked --verbose"
run_in_ci "cargo deny check licenses sources"
run_in_ci "cargo audit"
docker build --target dev -t zeroclaw-local-smoke:latest .
docker run --rm zeroclaw-local-smoke:latest --version
;;
clean)
"${compose_cmd[@]}" down -v --remove-orphans
;;
*)
print_help
exit 1
;;
esac

22
dev/ci/Dockerfile Normal file
View file

@ -0,0 +1,22 @@
# syntax=docker/dockerfile:1.7
FROM rust:1.92-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
git \
pkg-config \
libssl-dev \
curl \
&& rm -rf /var/lib/apt/lists/*
RUN rustup toolchain install 1.92 --profile minimal --component rustfmt --component clippy
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
cargo install --locked cargo-audit && \
cargo install --locked cargo-deny --version 0.18.5
WORKDIR /workspace
CMD ["bash"]

View file

@ -46,6 +46,7 @@ function print_help {
echo -e " ${GREEN}agent${NC} Enter Agent (ZeroClaw CLI)"
echo -e " ${GREEN}logs${NC} View logs"
echo -e " ${GREEN}build${NC} Rebuild images"
echo -e " ${GREEN}ci${NC} Run local CI checks in Docker (see ./dev/ci.sh)"
echo -e " ${GREEN}clean${NC} Stop and wipe workspace data"
}
@ -94,6 +95,15 @@ case "$1" in
echo -e "${GREEN}✅ Rebuild complete.${NC}"
;;
ci)
shift
if [ "$BASE_DIR" = "." ]; then
./ci.sh "${@:-all}"
else
./dev/ci.sh "${@:-all}"
fi
;;
clean)
echo -e "${RED}⚠️ WARNING: This will delete 'target/.zeroclaw' data and Docker volumes.${NC}"
read -p "Are you sure? (y/N) " -n 1 -r

23
dev/docker-compose.ci.yml Normal file
View file

@ -0,0 +1,23 @@
name: zeroclaw-local-ci
services:
local-ci:
build:
context: ..
dockerfile: dev/ci/Dockerfile
container_name: zeroclaw-local-ci
working_dir: /workspace
environment:
- CARGO_TERM_COLOR=always
- PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- CARGO_TARGET_DIR=/ci-target
volumes:
- ..:/workspace
- cargo-registry:/usr/local/cargo/registry
- cargo-git:/usr/local/cargo/git
- ci-target:/ci-target
volumes:
cargo-registry:
cargo-git:
ci-target: