fix(channels): reply via reply_target and improve local Docker cache reuse

This commit is contained in:
Will Sarg 2026-02-17 09:22:01 -05:00
parent 9e0958dee5
commit b8bef379e2
4 changed files with 41 additions and 16 deletions

View file

@ -1,4 +1,4 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1.7
# ── Stage 1: Build ──────────────────────────────────────────── # ── Stage 1: Build ────────────────────────────────────────────
FROM rust:1.93-slim-trixie@sha256:9663b80a1621253d30b146454f903de48f0af925c967be48c84745537cd35d8b AS builder FROM rust:1.93-slim-trixie@sha256:9663b80a1621253d30b146454f903de48f0af925c967be48c84745537cd35d8b AS builder
@ -6,27 +6,30 @@ FROM rust:1.93-slim-trixie@sha256:9663b80a1621253d30b146454f903de48f0af925c967be
WORKDIR /app WORKDIR /app
# Install build dependencies # Install build dependencies
RUN apt-get update && apt-get install -y \ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
pkg-config \ --mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y \
pkg-config \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 1. Copy manifests to cache dependencies # 1. Copy manifests to cache dependencies
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
# Create dummy main.rs to build dependencies # Create dummy main.rs to build dependencies
RUN mkdir src && echo "fn main() {}" > src/main.rs RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN --mount=type=cache,target=/usr/local/cargo/registry \ RUN --mount=type=cache,id=zeroclaw-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
--mount=type=cache,target=/usr/local/cargo/git \ --mount=type=cache,id=zeroclaw-cargo-git,target=/usr/local/cargo/git,sharing=locked \
--mount=type=cache,id=zeroclaw-target,target=/app/target,sharing=locked \
cargo build --release --locked cargo build --release --locked
RUN rm -rf src RUN rm -rf src
# 2. Copy source code # 2. Copy source code
COPY . . COPY . .
# Touch main.rs to force rebuild RUN --mount=type=cache,id=zeroclaw-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
RUN touch src/main.rs --mount=type=cache,id=zeroclaw-cargo-git,target=/usr/local/cargo/git,sharing=locked \
RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,id=zeroclaw-target,target=/app/target,sharing=locked \
--mount=type=cache,target=/usr/local/cargo/git \
cargo build --release --locked && \ cargo build --release --locked && \
strip target/release/zeroclaw cp target/release/zeroclaw /app/zeroclaw && \
strip /app/zeroclaw
# ── Stage 2: Permissions & Config Prep ─────────────────────── # ── Stage 2: Permissions & Config Prep ───────────────────────
FROM busybox:1.37@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f AS permissions FROM busybox:1.37@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f AS permissions
@ -35,7 +38,7 @@ RUN mkdir -p /zeroclaw-data/.zeroclaw /zeroclaw-data/workspace
# Create minimal config for PRODUCTION (allows binding to public interfaces) # Create minimal config for PRODUCTION (allows binding to public interfaces)
# NOTE: Provider configuration must be done via environment variables at runtime # NOTE: Provider configuration must be done via environment variables at runtime
RUN cat > /zeroclaw-data/.zeroclaw/config.toml << 'EOF' RUN cat > /zeroclaw-data/.zeroclaw/config.toml <<EOF
workspace_dir = "/zeroclaw-data/workspace" workspace_dir = "/zeroclaw-data/workspace"
config_path = "/zeroclaw-data/.zeroclaw/config.toml" config_path = "/zeroclaw-data/.zeroclaw/config.toml"
api_key = "" api_key = ""
@ -65,7 +68,7 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY --from=permissions /zeroclaw-data /zeroclaw-data COPY --from=permissions /zeroclaw-data /zeroclaw-data
COPY --from=builder /app/target/release/zeroclaw /usr/local/bin/zeroclaw COPY --from=builder /app/zeroclaw /usr/local/bin/zeroclaw
# Overwrite minimal config with DEV template (Ollama defaults) # Overwrite minimal config with DEV template (Ollama defaults)
COPY dev/config.template.toml /zeroclaw-data/.zeroclaw/config.toml COPY dev/config.template.toml /zeroclaw-data/.zeroclaw/config.toml
@ -92,7 +95,7 @@ CMD ["gateway", "--port", "3000", "--host", "[::]"]
# ── Stage 4: Production Runtime (Distroless) ───────────────── # ── Stage 4: Production Runtime (Distroless) ─────────────────
FROM gcr.io/distroless/cc-debian13:nonroot@sha256:84fcd3c223b144b0cb6edc5ecc75641819842a9679a3a58fd6294bec47532bf7 AS release FROM gcr.io/distroless/cc-debian13:nonroot@sha256:84fcd3c223b144b0cb6edc5ecc75641819842a9679a3a58fd6294bec47532bf7 AS release
COPY --from=builder /app/target/release/zeroclaw /usr/local/bin/zeroclaw COPY --from=builder /app/zeroclaw /usr/local/bin/zeroclaw
COPY --from=permissions /zeroclaw-data /zeroclaw-data COPY --from=permissions /zeroclaw-data /zeroclaw-data
# Environment setup # Environment setup

View file

@ -163,5 +163,7 @@ Note: local `deny` focuses on license/source policy; advisory scanning is handle
### Build cache notes ### Build cache notes
- Both `Dockerfile` and `dev/ci/Dockerfile` use BuildKit cache mounts for Cargo registry/git data. - Both `Dockerfile` and `dev/ci/Dockerfile` use BuildKit cache mounts for Cargo registry/git data.
- The root `Dockerfile` also caches Rust `target/` (`id=zeroclaw-target`) to speed repeat local image builds.
- Local CI reuses named Docker volumes for Cargo registry/git and target outputs. - Local CI reuses named Docker volumes for Cargo registry/git and target outputs.
- `./dev/ci.sh docker-smoke` and `./dev/ci.sh all` now use `docker buildx` local cache at `.cache/buildx-smoke` when available.
- The CI image keeps Rust toolchain defaults from `rust:1.92-slim` and installs pinned toolchain `1.92.0` (no custom `CARGO_HOME`/`RUSTUP_HOME` overrides), preventing repeated toolchain bootstrapping on each run. - The CI image keeps Rust toolchain defaults from `rust:1.92-slim` and installs pinned toolchain `1.92.0` (no custom `CARGO_HOME`/`RUSTUP_HOME` overrides), preventing repeated toolchain bootstrapping on each run.

View file

@ -11,12 +11,32 @@ else
fi fi
compose_cmd=(docker compose -f "$COMPOSE_FILE") compose_cmd=(docker compose -f "$COMPOSE_FILE")
SMOKE_CACHE_DIR="${SMOKE_CACHE_DIR:-.cache/buildx-smoke}"
run_in_ci() { run_in_ci() {
local cmd="$1" local cmd="$1"
"${compose_cmd[@]}" run --rm local-ci bash -c "$cmd" "${compose_cmd[@]}" run --rm local-ci bash -c "$cmd"
} }
build_smoke_image() {
if docker buildx version >/dev/null 2>&1; then
mkdir -p "$SMOKE_CACHE_DIR"
local build_args=(
--load
--target dev
--cache-to "type=local,dest=$SMOKE_CACHE_DIR,mode=max"
-t zeroclaw-local-smoke:latest
.
)
if [ -f "$SMOKE_CACHE_DIR/index.json" ]; then
build_args=(--cache-from "type=local,src=$SMOKE_CACHE_DIR" "${build_args[@]}")
fi
docker buildx build "${build_args[@]}"
else
DOCKER_BUILDKIT=1 docker build --target dev -t zeroclaw-local-smoke:latest .
fi
}
print_help() { print_help() {
cat <<'EOF' cat <<'EOF'
ZeroClaw Local CI in Docker ZeroClaw Local CI in Docker
@ -88,7 +108,7 @@ case "$1" in
;; ;;
docker-smoke) docker-smoke)
docker build --target dev -t zeroclaw-local-smoke:latest . build_smoke_image
docker run --rm zeroclaw-local-smoke:latest --version docker run --rm zeroclaw-local-smoke:latest --version
;; ;;
@ -98,7 +118,7 @@ case "$1" in
run_in_ci "cargo build --release --locked --verbose" run_in_ci "cargo build --release --locked --verbose"
run_in_ci "cargo deny check licenses sources" run_in_ci "cargo deny check licenses sources"
run_in_ci "cargo audit" run_in_ci "cargo audit"
docker build --target dev -t zeroclaw-local-smoke:latest . build_smoke_image
docker run --rm zeroclaw-local-smoke:latest --version docker run --rm zeroclaw-local-smoke:latest --version
;; ;;

View file

@ -227,7 +227,7 @@ async fn process_channel_message(ctx: Arc<ChannelRuntimeContext>, msg: traits::C
truncate_with_ellipsis(&response, 80) truncate_with_ellipsis(&response, 80)
); );
if let Some(channel) = target_channel.as_ref() { if let Some(channel) = target_channel.as_ref() {
if let Err(e) = channel.send(&response, &msg.channel).await { if let Err(e) = channel.send(&response, &msg.reply_target).await {
eprintln!(" ❌ Failed to reply on {}: {e}", channel.name()); eprintln!(" ❌ Failed to reply on {}: {e}", channel.name());
} }
} }