diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 9ff033ff..177f5ac7 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -4,16 +4,30 @@ # Stage 1: Build frontend SPA FROM node:22.23.0-slim@sha256:d9f850096136edbc402debdd8729579a288aac64574ada0ff4db26b6ae58b0b2 AS frontend +# Public Supabase anon key for the savings leaderboard; empty by default so +# the image's leaderboard stays disabled (#589). Pass --build-arg to enable. +ARG OPENJARVIS_LEADERBOARD_PUBLIC_ANON= WORKDIR /frontend COPY frontend/package.json frontend/package-lock.json* ./ RUN npm ci --ignore-scripts 2>/dev/null || npm install COPY frontend/ . -RUN npm run build +RUN VITE_SUPABASE_ANON_KEY="${OPENJARVIS_LEADERBOARD_PUBLIC_ANON}" npm run build # Stage 2: Build Python package FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf AS builder +RUN apt-get update && \ + apt-get install -y --no-install-recommends build-essential ca-certificates curl && \ + rm -rf /var/lib/apt/lists/* + +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --profile minimal --default-toolchain none && \ + rustup toolchain install 1.88 --profile minimal && \ + rustup default 1.88 + WORKDIR /app # Install dependencies from the committed lockfile (#567). `uv export --frozen` @@ -24,11 +38,13 @@ WORKDIR /app COPY pyproject.toml uv.lock README.md ./ RUN pip install --no-cache-dir uv && \ uv export --frozen --no-dev --extra server --no-emit-project > requirements.txt && \ - uv pip install --system --no-deps -r requirements.txt + uv pip install --system --no-deps -r requirements.txt && \ + uv pip install --system --no-deps "maturin>=1.12.6,<2" # Copy the source and the non-src force-include paths (see pyproject # [tool.hatch.build.targets.wheel.force-include]) before building the project. COPY src/ src/ +COPY rust/ rust/ COPY scripts/install scripts/install COPY deploy/windows deploy/windows @@ -36,7 +52,15 @@ COPY deploy/windows deploy/windows COPY --from=frontend /src/openjarvis/server/static src/openjarvis/server/static/ # Install the project itself without re-resolving dependencies. -RUN uv pip install --system --no-deps . +RUN uv pip install --system --no-deps . && \ + maturin build --release \ + --manifest-path rust/crates/openjarvis-python/Cargo.toml \ + --interpreter python3 \ + --out /tmp/openjarvis-rust-wheel && \ + uv pip install --system --no-deps /tmp/openjarvis-rust-wheel/*.whl && \ + python3 -c "import openjarvis_rust; print('openjarvis_rust ok')" && \ + python3 -m pip uninstall -y maturin && \ + rm -rf /tmp/openjarvis-rust-wheel rust # Stage 3: Runtime FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf diff --git a/deploy/docker/Dockerfile.gpu b/deploy/docker/Dockerfile.gpu index 3e37afd3..505461e6 100644 --- a/deploy/docker/Dockerfile.gpu +++ b/deploy/docker/Dockerfile.gpu @@ -4,20 +4,37 @@ # Stage 1: Build frontend SPA FROM node:22.23.0-slim@sha256:d9f850096136edbc402debdd8729579a288aac64574ada0ff4db26b6ae58b0b2 AS frontend +# Public Supabase anon key for the savings leaderboard; empty by default so +# the image's leaderboard stays disabled (#589). Pass --build-arg to enable. +ARG OPENJARVIS_LEADERBOARD_PUBLIC_ANON= WORKDIR /frontend COPY frontend/package.json frontend/package-lock.json* ./ RUN npm ci --ignore-scripts 2>/dev/null || npm install COPY frontend/ . -RUN npm run build +RUN VITE_SUPABASE_ANON_KEY="${OPENJARVIS_LEADERBOARD_PUBLIC_ANON}" npm run build # Stage 2: Build Python package (NVIDIA CUDA 12.4) FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04@sha256:af8bd179ed3bf69d4b63b19a763662a6141f0f62ef099283f68d0b14b4bab0e3 AS builder RUN apt-get update && \ - apt-get install -y --no-install-recommends python3 python3-pip python3-venv && \ + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + curl \ + python3 \ + python3-dev \ + python3-pip \ + python3-venv && \ rm -rf /var/lib/apt/lists/* +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --profile minimal --default-toolchain none && \ + rustup toolchain install 1.88 --profile minimal && \ + rustup default 1.88 + WORKDIR /app # Install dependencies from the committed lockfile (#567). See deploy/docker/Dockerfile @@ -25,15 +42,25 @@ WORKDIR /app COPY pyproject.toml uv.lock README.md ./ RUN pip install --no-cache-dir uv && \ uv export --frozen --no-dev --extra server --no-emit-project > requirements.txt && \ - uv pip install --system --no-deps -r requirements.txt + uv pip install --system --no-deps -r requirements.txt && \ + uv pip install --system --no-deps "maturin>=1.12.6,<2" COPY src/ src/ +COPY rust/ rust/ COPY scripts/install scripts/install COPY deploy/windows deploy/windows COPY --from=frontend /src/openjarvis/server/static src/openjarvis/server/static/ -RUN uv pip install --system --no-deps . +RUN uv pip install --system --no-deps . && \ + maturin build --release \ + --manifest-path rust/crates/openjarvis-python/Cargo.toml \ + --interpreter python3 \ + --out /tmp/openjarvis-rust-wheel && \ + uv pip install --system --no-deps /tmp/openjarvis-rust-wheel/*.whl && \ + python3 -c "import openjarvis_rust; print('openjarvis_rust ok')" && \ + python3 -m pip uninstall -y maturin && \ + rm -rf /tmp/openjarvis-rust-wheel rust # Stage 3: Runtime FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04@sha256:af8bd179ed3bf69d4b63b19a763662a6141f0f62ef099283f68d0b14b4bab0e3 diff --git a/deploy/docker/Dockerfile.gpu.rocm b/deploy/docker/Dockerfile.gpu.rocm index ddbef492..e3d9b080 100644 --- a/deploy/docker/Dockerfile.gpu.rocm +++ b/deploy/docker/Dockerfile.gpu.rocm @@ -4,20 +4,37 @@ # Stage 1: Build frontend SPA FROM node:22.23.0-slim@sha256:d9f850096136edbc402debdd8729579a288aac64574ada0ff4db26b6ae58b0b2 AS frontend +# Public Supabase anon key for the savings leaderboard; empty by default so +# the image's leaderboard stays disabled (#589). Pass --build-arg to enable. +ARG OPENJARVIS_LEADERBOARD_PUBLIC_ANON= WORKDIR /frontend COPY frontend/package.json frontend/package-lock.json* ./ RUN npm ci --ignore-scripts 2>/dev/null || npm install COPY frontend/ . -RUN npm run build +RUN VITE_SUPABASE_ANON_KEY="${OPENJARVIS_LEADERBOARD_PUBLIC_ANON}" npm run build # Stage 2: Build Python package (AMD ROCm 7.2) FROM rocm/dev-ubuntu-22.04:7.2@sha256:05af5f04a06b04676d4c7438997d0deadaeb7478961ad621376e199bf3aeb644 AS builder RUN apt-get update && \ - apt-get install -y --no-install-recommends python3 python3-pip python3-venv && \ + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + curl \ + python3 \ + python3-dev \ + python3-pip \ + python3-venv && \ rm -rf /var/lib/apt/lists/* +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --profile minimal --default-toolchain none && \ + rustup toolchain install 1.88 --profile minimal && \ + rustup default 1.88 + WORKDIR /app # Install dependencies from the committed lockfile (#567). See deploy/docker/Dockerfile @@ -25,15 +42,25 @@ WORKDIR /app COPY pyproject.toml uv.lock README.md ./ RUN pip install --no-cache-dir uv && \ uv export --frozen --no-dev --extra server --no-emit-project > requirements.txt && \ - uv pip install --system --no-deps -r requirements.txt + uv pip install --system --no-deps -r requirements.txt && \ + uv pip install --system --no-deps "maturin>=1.12.6,<2" COPY src/ src/ +COPY rust/ rust/ COPY scripts/install scripts/install COPY deploy/windows deploy/windows COPY --from=frontend /src/openjarvis/server/static src/openjarvis/server/static/ -RUN uv pip install --system --no-deps . +RUN uv pip install --system --no-deps . && \ + maturin build --release \ + --manifest-path rust/crates/openjarvis-python/Cargo.toml \ + --interpreter python3 \ + --out /tmp/openjarvis-rust-wheel && \ + uv pip install --system --no-deps /tmp/openjarvis-rust-wheel/*.whl && \ + python3 -c "import openjarvis_rust; print('openjarvis_rust ok')" && \ + python3 -m pip uninstall -y maturin && \ + rm -rf /tmp/openjarvis-rust-wheel rust # Stage 3: Runtime FROM rocm/dev-ubuntu-22.04:7.2@sha256:05af5f04a06b04676d4c7438997d0deadaeb7478961ad621376e199bf3aeb644 diff --git a/deploy/docker/Dockerfile.sandbox b/deploy/docker/Dockerfile.sandbox index a57f28a7..07af2ffd 100644 --- a/deploy/docker/Dockerfile.sandbox +++ b/deploy/docker/Dockerfile.sandbox @@ -7,20 +7,18 @@ # image digest is the integrity check, and the copy is architecture-agnostic. FROM node:22.23.0-slim@sha256:d9f850096136edbc402debdd8729579a288aac64574ada0ff4db26b6ae58b0b2 AS node -FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf +FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf AS builder -# libstdc++6 + ca-certificates are the only runtime requirements of the Node -# binary copied below (the python slim image already provides libc/libgcc). RUN apt-get update && \ - apt-get install -y --no-install-recommends ca-certificates libstdc++6 && \ + apt-get install -y --no-install-recommends build-essential ca-certificates curl && \ rm -rf /var/lib/apt/lists/* -# Transplant the Node.js runtime from the official image. Both images are Debian -# bookworm, so the glibc/libstdc++ ABI matches. -COPY --from=node /usr/local/bin/node /usr/local/bin/node -COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules -RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ - ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx +ENV PATH="/root/.cargo/bin:${PATH}" + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --profile minimal --default-toolchain none && \ + rustup toolchain install 1.88 --profile minimal && \ + rustup default 1.88 WORKDIR /app @@ -31,12 +29,41 @@ WORKDIR /app COPY pyproject.toml uv.lock README.md ./ RUN pip install --no-cache-dir uv && \ uv export --frozen --no-dev --extra server --no-emit-project > requirements.txt && \ - uv pip install --system --no-deps -r requirements.txt + uv pip install --system --no-deps -r requirements.txt && \ + uv pip install --system --no-deps "maturin>=1.12.6,<2" COPY . . # Install the project itself without re-resolving dependencies. -RUN uv pip install --system --no-deps . +RUN uv pip install --system --no-deps . && \ + maturin build --release \ + --manifest-path rust/crates/openjarvis-python/Cargo.toml \ + --interpreter python3 \ + --out /tmp/openjarvis-rust-wheel && \ + uv pip install --system --no-deps /tmp/openjarvis-rust-wheel/*.whl && \ + python3 -c "import openjarvis_rust; print('openjarvis_rust ok')" && \ + python3 -m pip uninstall -y maturin && \ + rm -rf /tmp/openjarvis-rust-wheel rust/target + +FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf + +# libstdc++6 + ca-certificates are the only runtime requirements of the Node +# binary copied below (the python slim image already provides libc/libgcc). +RUN apt-get update && \ + apt-get install -y --no-install-recommends ca-certificates libstdc++6 && \ + rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/local /usr/local +COPY --from=builder /app /app + +# Transplant the Node.js runtime from the official image. Both images are Debian +# bookworm, so the glibc/libstdc++ ABI matches. +COPY --from=node /usr/local/bin/node /usr/local/bin/node +COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules +RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \ + ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx + +WORKDIR /app LABEL openjarvis-sandbox=true diff --git a/tests/deployment/test_docker.py b/tests/deployment/test_docker.py index dbd5ef21..18192c93 100644 --- a/tests/deployment/test_docker.py +++ b/tests/deployment/test_docker.py @@ -123,6 +123,35 @@ class TestDockerFiles: assert "jarvis:" in content assert "ollama:" in content + def test_dockerfiles_build_native_rust_extension(self): + build_dockerfiles = [ + "Dockerfile", + "Dockerfile.gpu", + "Dockerfile.gpu.rocm", + "Dockerfile.sandbox", + ] + required_markers = [ + "rustup toolchain install 1.88", + "maturin build --release", + "rust/crates/openjarvis-python/Cargo.toml", + "/tmp/openjarvis-rust-wheel/*.whl", + "import openjarvis_rust", + ] + + for name in build_dockerfiles: + content = (DOCKER_DIR / name).read_text() + for marker in required_markers: + assert marker in content, ( + f"{name}: missing native build marker {marker!r}" + ) + + for name in ["Dockerfile", "Dockerfile.gpu", "Dockerfile.gpu.rocm"]: + content = (DOCKER_DIR / name).read_text() + assert "COPY rust/ rust/" in content, f"{name}: rust workspace not copied" + assert content.index("COPY rust/ rust/") < content.index( + "maturin build --release" + ), f"{name}: rust workspace copied after native build" + def test_systemd_service_exists(self): assert (SYSTEMD_DIR / "openjarvis.service").is_file()