Skip to content

Commit 26e5e4c

Browse files
henrypark133claude
andauthored
feat(docker): pre-bundle WASM extensions in staging image (#2210)
* feat(docker): pre-bundle WASM extensions in staging image Add a wasm-builder Docker stage that builds all registry tool/channel extensions from source and copies the .wasm + .capabilities.json files into the staging runtime image. Production images are unaffected — Docker only builds the wasm-builder stage when --target runtime-staging is used. The docker.yml workflow passes --target runtime-staging for scheduled (staging) builds and workflow_dispatch with tag=staging, while all other builds use --target runtime (no extensions). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(docker): address PR review feedback - Use COPY --chown instead of separate RUN chown layer (fewer layers) - Reorder stages so runtime (production) is last — bare docker build defaults to production, not staging - Use --locked when Cargo.lock is present for reproducible WASM builds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent aaeb904 commit 26e5e4c

3 files changed

Lines changed: 64 additions & 4 deletions

File tree

.dockerignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ target/
55
*.md
66
!CLAUDE.md
77
node_modules/
8-
tools-src/

.github/workflows/docker.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ jobs:
8585
echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
8686
echo "worker_tags=${WORKER_TAGS}" >> "$GITHUB_OUTPUT"
8787
88+
# Staging builds get pre-bundled WASM extensions
89+
if [[ "${EVENT_NAME}" == "schedule" || "${INPUT_TAG}" == "staging" ]]; then
90+
echo "target=runtime-staging" >> "$GITHUB_OUTPUT"
91+
else
92+
echo "target=runtime" >> "$GITHUB_OUTPUT"
93+
fi
94+
8895
- name: Set up Docker Buildx
8996
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
9097

@@ -100,6 +107,7 @@ jobs:
100107
context: .
101108
push: true
102109
tags: ${{ steps.tags.outputs.tags }}
110+
target: ${{ steps.tags.outputs.target }}
103111
platforms: linux/amd64
104112
cache-from: type=gha
105113
cache-to: type=gha,mode=max

Dockerfile

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ COPY tests/ tests/
3232
COPY migrations/ migrations/
3333
COPY registry/ registry/
3434
COPY channels-src/ channels-src/
35+
COPY tools-src/ tools-src/
3536
COPY wit/ wit/
3637
COPY providers.json providers.json
3738

@@ -59,13 +60,56 @@ COPY tests/ tests/
5960
COPY migrations/ migrations/
6061
COPY registry/ registry/
6162
COPY channels-src/ channels-src/
63+
COPY tools-src/ tools-src/
6264
COPY wit/ wit/
6365
COPY providers.json providers.json
6466

6567
RUN cargo build --profile dist --bin ironclaw
6668

67-
# Stage 5: Minimal runtime
68-
FROM debian:bookworm-slim
69+
# Stage 4b: Build all WASM extensions from source (only used by runtime-staging)
70+
FROM builder AS wasm-builder
71+
72+
RUN apt-get update && apt-get install -y --no-install-recommends jq && rm -rf /var/lib/apt/lists/*
73+
74+
RUN set -eux; \
75+
mkdir -p /app/wasm-bundles/tools /app/wasm-bundles/channels; \
76+
for manifest in registry/tools/*.json registry/channels/*.json; do \
77+
[ -f "$manifest" ] || continue; \
78+
kind=$(jq -r '.kind' "$manifest"); \
79+
ext_name=$(jq -r '.name' "$manifest"); \
80+
source_dir=$(jq -r '.source.dir' "$manifest"); \
81+
caps_file=$(jq -r '.source.capabilities' "$manifest"); \
82+
crate_name=$(jq -r '.source.crate_name' "$manifest"); \
83+
[ -d "$source_dir" ] || continue; \
84+
# Telegram is embedded in the binary at build time; skip it
85+
[ "$ext_name" = "telegram" ] && continue; \
86+
echo "=== Building $ext_name from $source_dir ==="; \
87+
if [ -f "$source_dir/Cargo.lock" ]; then \
88+
CARGO_TARGET_DIR=/app/target cargo build --locked --release --target wasm32-wasip2 \
89+
--manifest-path "$source_dir/Cargo.toml" || { echo "WARN: build failed for $ext_name"; continue; }; \
90+
else \
91+
CARGO_TARGET_DIR=/app/target cargo build --release --target wasm32-wasip2 \
92+
--manifest-path "$source_dir/Cargo.toml" || { echo "WARN: build failed for $ext_name"; continue; }; \
93+
fi; \
94+
wasm_artifact=$(echo "${crate_name}" | tr '-' '_'); \
95+
raw_wasm="/app/target/wasm32-wasip2/release/${wasm_artifact}.wasm"; \
96+
[ -f "$raw_wasm" ] || continue; \
97+
dest_dir="/app/wasm-bundles/tools"; \
98+
[ "$kind" = "channel" ] && dest_dir="/app/wasm-bundles/channels"; \
99+
wasm-tools component new "$raw_wasm" -o "$dest_dir/${ext_name}.wasm" 2>/dev/null \
100+
|| cp "$raw_wasm" "$dest_dir/${ext_name}.wasm"; \
101+
wasm-tools strip "$dest_dir/${ext_name}.wasm" -o "$dest_dir/${ext_name}.wasm.tmp" 2>/dev/null \
102+
&& mv "$dest_dir/${ext_name}.wasm.tmp" "$dest_dir/${ext_name}.wasm" \
103+
|| true; \
104+
[ -f "$source_dir/$caps_file" ] && cp "$source_dir/$caps_file" "$dest_dir/${ext_name}.capabilities.json"; \
105+
echo " -> $dest_dir/${ext_name}.wasm"; \
106+
done; \
107+
count=$(find /app/wasm-bundles -name '*.wasm' | wc -l); \
108+
echo "Built $count WASM extensions"; \
109+
[ "$count" -gt 0 ] || { echo "ERROR: No WASM extensions were built"; exit 1; }
110+
111+
# Stage 5a: Shared runtime base
112+
FROM debian:bookworm-slim AS runtime-base
69113

70114
RUN apt-get update \
71115
&& apt-get install -y --no-install-recommends ca-certificates \
@@ -80,10 +124,19 @@ RUN useradd -m -d /home/ironclaw -u 1000 ironclaw \
80124
&& mkdir -p /home/ironclaw/.ironclaw \
81125
&& chown -R ironclaw:ironclaw /home/ironclaw
82126
WORKDIR /home/ironclaw
83-
USER ironclaw
84127

85128
EXPOSE 3000
86129

87130
ENV RUST_LOG=ironclaw=info
88131

89132
ENTRYPOINT ["ironclaw"]
133+
134+
# Stage 5b: Staging runtime (with pre-built WASM extensions)
135+
FROM runtime-base AS runtime-staging
136+
COPY --from=wasm-builder --chown=ironclaw:ironclaw /app/wasm-bundles/tools/ /home/ironclaw/.ironclaw/tools/
137+
COPY --from=wasm-builder --chown=ironclaw:ironclaw /app/wasm-bundles/channels/ /home/ironclaw/.ironclaw/channels/
138+
USER ironclaw
139+
140+
# Stage 5c: Production runtime (default — no pre-bundled extensions)
141+
FROM runtime-base AS runtime
142+
USER ironclaw

0 commit comments

Comments
 (0)