build(docker): consolidate WASM build into multi-stage Dockerfile

- Move WASM compilation from GitHub Actions into Docker build stages
- Add emsdkVersion to package.json config as single source of truth
- Implement cargo-chef for dependency caching in both WASM and server builds
- Update .dockerignore to include packed game assets while excluding unpacked
- Simplify deploy workflow by removing local WASM build steps
This commit is contained in:
2025-12-29 16:43:37 -06:00
parent a65836bd5b
commit 16fba6aabc
7 changed files with 176 additions and 102 deletions
+123 -32
View File
@@ -1,67 +1,158 @@
ARG RUST_VERSION=1.89.0
ARG RUST_VERSION=1.86.0
ARG EMSDK_VERSION=4.0.22
ARG GIT_COMMIT_SHA
# ========== Stage 1: WASM Planner ==========
FROM emscripten/emsdk:${EMSDK_VERSION} AS wasm-planner
ARG RUST_VERSION
WORKDIR /app
# Install Rust and cargo-chef for dependency caching
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
--default-toolchain ${RUST_VERSION} \
--profile minimal
ENV PATH="/root/.cargo/bin:${PATH}"
RUN cargo install cargo-chef --locked
# Copy workspace for recipe generation
COPY Cargo.toml Cargo.lock rust-toolchain.toml ./
COPY .cargo/ ./.cargo/
COPY pacman-common/ ./pacman-common/
COPY pacman/ ./pacman/
COPY pacman-server/ ./pacman-server/
# Generate dependency recipe
RUN cargo chef prepare --bin pacman --recipe-path recipe.json
# ========== Stage 2: WASM Dependency Builder ==========
FROM emscripten/emsdk:${EMSDK_VERSION} AS wasm-deps
ARG RUST_VERSION
WORKDIR /app
# Install Rust with WASM target and cargo-chef
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
--default-toolchain ${RUST_VERSION} \
--profile minimal
ENV PATH="/root/.cargo/bin:${PATH}"
RUN rustup target add wasm32-unknown-emscripten && \
cargo install cargo-chef --locked
# Cook dependencies only (this layer is cached until Cargo.toml/Cargo.lock change)
# Note: Assets are required during linking due to --preload-file in rustflags
COPY --from=wasm-planner /app/recipe.json recipe.json
COPY .cargo/ ./.cargo/
COPY pacman/assets/game/ ./pacman/assets/game/
RUN cargo chef cook --release --target wasm32-unknown-emscripten --recipe-path recipe.json
# ========== Stage 3: WASM Builder ==========
FROM emscripten/emsdk:${EMSDK_VERSION} AS wasm-builder
ARG RUST_VERSION
WORKDIR /app
# Install Rust with WASM target (minimal, no cargo-chef needed)
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
--default-toolchain ${RUST_VERSION} \
--profile minimal
ENV PATH="/root/.cargo/bin:${PATH}"
RUN rustup target add wasm32-unknown-emscripten
# Copy cached dependencies from wasm-deps
COPY --from=wasm-deps /app/target target
COPY --from=wasm-deps /root/.cargo /root/.cargo
# Copy workspace source (build from workspace root so preload-file paths work)
COPY Cargo.toml Cargo.lock rust-toolchain.toml ./
COPY .cargo/ ./.cargo/
COPY pacman-common/ ./pacman-common/
COPY pacman/ ./pacman/
COPY pacman-server/ ./pacman-server/
# Build WASM binary (dependencies already cached)
RUN cargo build --release --target wasm32-unknown-emscripten --bin pacman
# ========== Stage 4: Frontend Builder ==========
FROM oven/bun:1 AS frontend-builder
WORKDIR /app
# Copy package files for dependency installation
COPY web/package.json web/bun.lock* ./
RUN bun install --frozen-lockfile
# Copy WASM artifacts from wasm-builder stage
# Note: .wasm and .js are in release/, but .data (preloaded assets) is in release/deps/
RUN mkdir -p ./public
COPY --from=wasm-builder /app/target/wasm32-unknown-emscripten/release/pacman.wasm ./public/pacman.wasm
COPY --from=wasm-builder /app/target/wasm32-unknown-emscripten/release/pacman.js ./public/pacman.js
COPY --from=wasm-builder /app/target/wasm32-unknown-emscripten/release/deps/pacman.data ./public/pacman.data
# Verify WASM artifacts exist and have reasonable sizes
RUN test -f ./public/pacman.wasm && \
test -f ./public/pacman.js && \
test -f ./public/pacman.data && \
[ $(stat -c%s ./public/pacman.wasm) -gt $((1024 * 1024)) ] && \
[ $(stat -c%s ./public/pacman.js) -gt $((100 * 1024)) ] && \
[ $(stat -c%s ./public/pacman.data) -gt $((10 * 1024)) ] && \
echo "WASM artifacts verified (wasm >1MiB, js >100KiB, data >10KiB)" || \
(echo "WASM artifacts missing or too small!" && exit 1)
# Copy frontend source
COPY web/ ./
# Build frontend (Vite bundles WASM files from public/)
RUN bun run build
# ========== Stage 5: Backend Chef ==========
FROM lukemathwalker/cargo-chef:latest-rust-${RUST_VERSION} AS chef
WORKDIR /app
# -- Planner stage --
# ========== Stage 6: Backend Planner ==========
FROM chef AS planner
COPY . .
RUN cargo chef prepare --bin pacman-server --recipe-path recipe.json
# -- Frontend builder stage --
FROM oven/bun:1 AS frontend-builder
WORKDIR /app
# Copy frontend package files first for layer caching
COPY web/package.json web/bun.lock* ./
RUN bun install --frozen-lockfile
# Copy all frontend source including public directory (contains WASM files)
COPY web/ ./
# Build the frontend (Vite will copy public/ contents to dist/client/)
RUN bun run build
# -- Backend builder stage --
# ========== Stage 7: Backend Builder ==========
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --bin pacman-server --recipe-path recipe.json
# Copy the source code AFTER, so that dependencies are already cached
# Copy source code
COPY . .
# Install build dependencies, then build the server
RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
# Install build dependencies and compile server
RUN apt-get update && \
apt-get install -y pkg-config libssl-dev && \
rm -rf /var/lib/apt/lists/*
RUN cargo build --package pacman-server --release --bin pacman-server
# -- Runtime stage --
# ========== Stage 8: Runtime ==========
FROM debian:bookworm-slim AS runtime
WORKDIR /app
# Install runtime dependencies (curl needed for healthcheck)
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates tzdata curl && \
rm -rf /var/lib/apt/lists/*
# Copy compiled server binary
COPY --from=builder /app/target/release/pacman-server /usr/local/bin/pacman-server
# Copy frontend static files from frontend-builder stage
# Copy frontend static files (includes WASM)
COPY --from=frontend-builder /app/dist/client /app/static
# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
tzdata \
&& rm -rf /var/lib/apt/lists/*
# Environment configuration
ARG TZ=Etc/UTC
ENV TZ=${TZ}
# Optional build-time environment variable for embedding the Git commit SHA
ARG GIT_COMMIT_SHA
ENV GIT_COMMIT_SHA=${GIT_COMMIT_SHA}
# Specify PORT at build-time or run-time, default to 3000
ARG PORT=3000
ENV PORT=${PORT}
EXPOSE ${PORT}
# Set static files directory for the server to serve
ENV STATIC_FILES_DIR=/app/static
CMD ["sh", "-c", "exec /usr/local/bin/pacman-server"]
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl --fail http://localhost:${PORT}/api/health || exit 1
CMD ["pacman-server"]