forked from nearai/ironclaw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
154 lines (129 loc) · 5.73 KB
/
Dockerfile
File metadata and controls
154 lines (129 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# Multi-stage Dockerfile for the IronClaw agent (cloud deployment).
#
# Uses cargo-chef for dependency caching — only rebuilds deps when
# Cargo.toml/Cargo.lock change, not on every source edit.
#
# Debian-based build + runtime. The bundled libSQL/SQLite C code has
# threading issues when statically linked against musl (segfault on
# database reopen), so we use glibc.
#
# Build:
# docker build --platform linux/amd64 --target runtime -t ironclaw:latest .
#
# Run:
# docker run --env-file .env -p 3000:3000 ironclaw:latest
# Stage 1: Install cargo-chef
FROM rust:1.92-bookworm AS chef
RUN rustup target add wasm32-wasip2 \
&& cargo install --locked cargo-chef@0.1.77 wasm-tools@1.246.1
WORKDIR /app
# Stage 2: Generate the dependency recipe (changes only when Cargo.toml/lock change)
FROM chef AS planner
COPY Cargo.toml Cargo.lock ./
COPY crates/ crates/
COPY build.rs build.rs
COPY src/ src/
COPY tests/ tests/
COPY migrations/ migrations/
COPY registry/ registry/
COPY channels-src/ channels-src/
COPY tools-src/ tools-src/
COPY wit/ wit/
COPY providers.json providers.json
RUN cargo chef prepare --recipe-path recipe.json
# Stage 3: Build dependencies (cached unless Cargo.toml/lock change)
FROM chef AS deps
# Docker-only overrides for the dist profile (not in Cargo.toml because
# cargo-dist uses dist for release binaries that need unwinding).
ENV CARGO_PROFILE_DIST_PANIC=abort \
CARGO_PROFILE_DIST_CODEGEN_UNITS=1
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --profile dist --recipe-path recipe.json
# Stage 4: Build the actual binary (only recompiles ironclaw source)
FROM deps AS builder
COPY Cargo.toml Cargo.lock ./
COPY crates/ crates/
COPY build.rs build.rs
COPY src/ src/
COPY tests/ tests/
COPY migrations/ migrations/
COPY registry/ registry/
COPY channels-src/ channels-src/
COPY tools-src/ tools-src/
COPY wit/ wit/
COPY providers.json providers.json
COPY profiles/ profiles/
RUN cargo build --profile dist --bin ironclaw
# Stage 4b: Build all WASM extensions from source (only used by runtime-staging)
#
# Inherits from chef (not builder) so WASM extensions only rebuild when
# tools-src/, channels-src/, registry/, or wit/ change — not on every
# src/ edit. The extensions are standalone crates with their own lockfiles.
FROM chef AS wasm-builder
RUN apt-get update && apt-get install -y --no-install-recommends jq && rm -rf /var/lib/apt/lists/*
COPY tools-src/ tools-src/
COPY channels-src/ channels-src/
COPY registry/ registry/
COPY wit/ wit/
RUN set -eux; \
mkdir -p /app/wasm-bundles/tools /app/wasm-bundles/channels; \
for manifest in registry/tools/*.json registry/channels/*.json; do \
[ -f "$manifest" ] || continue; \
kind=$(jq -r '.kind' "$manifest"); \
ext_name=$(jq -r '.name' "$manifest"); \
source_dir=$(jq -r '.source.dir' "$manifest"); \
caps_file=$(jq -r '.source.capabilities' "$manifest"); \
crate_name=$(jq -r '.source.crate_name' "$manifest"); \
[ -d "$source_dir" ] || continue; \
# Telegram is embedded in the binary at build time; skip it
[ "$ext_name" = "telegram" ] && continue; \
echo "=== Building $ext_name from $source_dir ==="; \
if [ -f "$source_dir/Cargo.lock" ]; then \
CARGO_TARGET_DIR=/app/target cargo build --locked --release --target wasm32-wasip2 \
--manifest-path "$source_dir/Cargo.toml" || { echo "WARN: build failed for $ext_name"; continue; }; \
else \
CARGO_TARGET_DIR=/app/target cargo build --release --target wasm32-wasip2 \
--manifest-path "$source_dir/Cargo.toml" || { echo "WARN: build failed for $ext_name"; continue; }; \
fi; \
wasm_artifact=$(echo "${crate_name}" | tr '-' '_'); \
raw_wasm="/app/target/wasm32-wasip2/release/${wasm_artifact}.wasm"; \
[ -f "$raw_wasm" ] || continue; \
dest_dir="/app/wasm-bundles/tools"; \
[ "$kind" = "channel" ] && dest_dir="/app/wasm-bundles/channels"; \
wasm-tools component new "$raw_wasm" -o "$dest_dir/${ext_name}.wasm" 2>/dev/null \
|| cp "$raw_wasm" "$dest_dir/${ext_name}.wasm"; \
wasm-tools strip "$dest_dir/${ext_name}.wasm" -o "$dest_dir/${ext_name}.wasm.tmp" 2>/dev/null \
&& mv "$dest_dir/${ext_name}.wasm.tmp" "$dest_dir/${ext_name}.wasm" \
|| true; \
[ -f "$source_dir/$caps_file" ] && cp "$source_dir/$caps_file" "$dest_dir/${ext_name}.capabilities.json"; \
echo " -> $dest_dir/${ext_name}.wasm"; \
done; \
count=$(find /app/wasm-bundles -name '*.wasm' | wc -l); \
echo "Built $count WASM extensions"; \
[ "$count" -gt 0 ] || { echo "ERROR: No WASM extensions were built"; exit 1; }
# Stage 5a: Shared runtime base
FROM debian:bookworm-slim AS runtime-base
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/dist/ironclaw /usr/local/bin/ironclaw
COPY --from=builder /app/migrations /app/migrations
# Non-root user
ENV HOME=/home/ironclaw
RUN useradd -m -d /home/ironclaw -u 1000 ironclaw \
&& mkdir -p /home/ironclaw/.ironclaw \
&& chown -R ironclaw:ironclaw /home/ironclaw
WORKDIR /home/ironclaw
EXPOSE 3000
ENV RUST_LOG=ironclaw=info
ENTRYPOINT ["ironclaw"]
# Stage 5b: Production runtime (no pre-bundled extensions)
FROM runtime-base AS runtime
USER ironclaw
# Stage 5c: Staging runtime (with pre-built WASM extensions)
# Last stage = default target. Railway doesn't support --target, so this
# must be last for Railway deploys. CI uses explicit --target flags.
FROM runtime-base AS runtime-staging
COPY --from=wasm-builder --chown=ironclaw:ironclaw /app/wasm-bundles/tools/ /home/ironclaw/.ironclaw/tools/
COPY --from=wasm-builder --chown=ironclaw:ironclaw /app/wasm-bundles/channels/ /home/ironclaw/.ironclaw/channels/
USER ironclaw