214 lines
11 KiB
Text
214 lines
11 KiB
Text
# Build context: repo root (website/).
|
|
# External path deps assembled by ctx-leptos into the build context:
|
|
# rustelo/ → /workspace/rustelo/ (Cargo path deps: ../rustelo/...)
|
|
#
|
|
# Build arguments — supplied via --build-arg from lian-build/build_directives.ncl.
|
|
# Canonical values live in the NCL; no defaults here to avoid drift.
|
|
# RUST_VERSION lamina catalog tag for leptos/node/nickel layers
|
|
# RUSTELO_VERSION lamina rustelo layer tag (rustelo framework version, e.g. 0.1.0)
|
|
# NODE_VERSION lamina/node tag (e.g. 22)
|
|
# LAMINA_REGISTRY lamina catalog registry base
|
|
# BIN_NAME [[bin]] name in crates/server/Cargo.toml
|
|
# LEPTOS_OUTPUT_NAME name in [[workspace.metadata.leptos]]
|
|
# BIN_FEATURES extra --bin-features passed to cargo leptos build
|
|
# SERVER_PORT port the server listens on (EXPOSE + LEPTOS_SITE_ADDR)
|
|
#
|
|
ARG RUST_VERSION
|
|
ARG RUSTELO_VERSION
|
|
ARG LEPTOS_VERSION
|
|
ARG NICKEL_VERSION
|
|
ARG NODE_VERSION
|
|
ARG LAMINA_REGISTRY
|
|
ARG BIN_NAME
|
|
ARG LEPTOS_OUTPUT_NAME
|
|
ARG BIN_FEATURES
|
|
ARG SERVER_PORT
|
|
|
|
# Named alias — BuildKit does not expand ARGs inside --from=; named stage is required.
|
|
FROM --platform=$BUILDPLATFORM ${LAMINA_REGISTRY}/nickel:${NICKEL_VERSION} AS nickel-bin
|
|
|
|
# ─── Stage 0: cargo-chef planner ─────────────────────────────────────────────
|
|
# lamina/leptos: cargo + cargo-chef + sccache + wasm32 + wasm-bindgen + wasm-opt + cargo-leptos
|
|
FROM --platform=$BUILDPLATFORM ${LAMINA_REGISTRY}/leptos:${LEPTOS_VERSION} AS planner
|
|
WORKDIR /workspace
|
|
COPY Cargo.toml Cargo.lock ./website/
|
|
COPY crates/ ./website/crates/
|
|
COPY rustelo/ /workspace/rustelo/
|
|
COPY stratumiops/ /workspace/stratumiops/
|
|
WORKDIR /workspace/website
|
|
RUN cargo chef prepare --recipe-path recipe.json
|
|
|
|
# ─── Stage 1: dependency cache ────────────────────────────────────────────────
|
|
# rustelo layer (FROM leptos + chef cook for rustelo workspace) gives us
|
|
# ~/.cargo/registry pre-populated with rustelo's transitive crate downloads,
|
|
# so cargo chef cook here only needs to fetch website-specific deps.
|
|
FROM ${LAMINA_REGISTRY}/rustelo:${RUSTELO_VERSION} AS builder-deps
|
|
# Pin target dir: base images vary (leptos=/cargo-install-target, rustelo=/workspace/target).
|
|
# COPY paths in the runtime stage depend on this value being stable.
|
|
ENV CARGO_TARGET_DIR=/workspace/website/target
|
|
|
|
COPY --from=nickel-bin /usr/local/bin/nickel /usr/local/bin/nickel
|
|
|
|
WORKDIR /workspace/website
|
|
COPY --from=planner /workspace/website/recipe.json recipe.json
|
|
COPY rustelo/ /workspace/rustelo/
|
|
COPY stratumiops/ /workspace/stratumiops/
|
|
# site/config needed by build.rs: nickel evaluates NCL during cargo compilation
|
|
COPY site/config/ /workspace/website/site/config/
|
|
|
|
# SSR target only — WASM cook pulls incompatible platform deps (mio/tokio-net);
|
|
# cargo-leptos handles WASM compilation with correct feature isolation.
|
|
ARG BIN_FEATURES
|
|
RUN --mount=type=secret,id=sccache_env,required \
|
|
--mount=type=cache,target=/workspace/website/target \
|
|
set -a && . /run/secrets/sccache_env && set +a && \
|
|
export RUSTC_WRAPPER=sccache && \
|
|
cargo chef cook --release --no-default-features --features "ssr,${BIN_FEATURES}" \
|
|
--recipe-path recipe.json && \
|
|
sccache --show-stats
|
|
|
|
# ─── Stage 2: CSS (unocss) ────────────────────────────────────────────────────
|
|
# lamina/node has node + pnpm pre-installed — no corepack setup needed.
|
|
# unocss scans .rs sources — WORKDIR and rustelo/ must match workspace layout.
|
|
FROM --platform=$BUILDPLATFORM ${LAMINA_REGISTRY}/node:${NODE_VERSION} AS css
|
|
WORKDIR /workspace/website
|
|
COPY package.json pnpm-lock.yaml ./
|
|
RUN pnpm install --frozen-lockfile
|
|
COPY uno.config.ts .
|
|
COPY crates/ ./crates/
|
|
COPY rustelo/ /workspace/rustelo/
|
|
RUN pnpm run css:build
|
|
|
|
# ─── Stage 3: Rust build ──────────────────────────────────────────────────────
|
|
FROM builder-deps AS builder
|
|
WORKDIR /workspace
|
|
COPY Cargo.toml Cargo.lock ./website/
|
|
COPY crates/ ./website/crates/
|
|
COPY rustelo/ /workspace/rustelo/
|
|
COPY stratumiops/ /workspace/stratumiops/
|
|
# ManifestResolver (rustelo_utils) walks up from CARGO_MANIFEST_DIR looking for
|
|
# rustelo.manifest.toml at the workspace root — must be present for build scripts.
|
|
COPY rustelo.manifest.toml ./website/rustelo.manifest.toml
|
|
COPY rustelo/ ./website/rustelo/
|
|
# Pre-built static assets (CSS bundles, themes, highlight, logos, etc.)
|
|
# cargo-leptos copies site/public/ → target/site/ during build
|
|
COPY site/public/ ./website/site/public/
|
|
# Freshly generated unocss output overwrites any stale pre-built copy
|
|
COPY --from=css /workspace/website/site/public/styles/website.css \
|
|
./website/site/public/styles/website.css
|
|
# NCL config + runtime config files
|
|
COPY site/config/ ./website/site/config/
|
|
COPY site/rbac.ncl ./website/site/rbac.ncl
|
|
# content-static feature: build.rs bakes content and i18n at compile time
|
|
COPY site/content/ ./website/site/content/
|
|
COPY site/i18n/ ./website/site/i18n/
|
|
COPY site/templates/ ./website/site/templates/
|
|
|
|
WORKDIR /workspace/website
|
|
# build.rs (build_page_generator) reads SITE_CONFIG_PATH to locate config.ncl
|
|
ENV SITE_CONFIG_PATH=/workspace/website/site/config/index.ncl
|
|
# rustelo_config::nickel::resolve_import_path walks for rustelo/nickel/ but the
|
|
# actual layout is rustelo/resources/nickel/. Set NICKEL_IMPORT_PATH explicitly
|
|
# so the env-var fallback fires. site/config is included for cross-domain imports.
|
|
ENV NICKEL_IMPORT_PATH=/workspace/rustelo/resources/nickel:/workspace/website/site/config
|
|
# wasm-opt on linux/arm64 in this base image strips the externref table
|
|
# growability that wasm-bindgen needs at hydration init → runtime
|
|
# `WebAssembly.Table.grow(): failed to grow table by 4`. Neither v123 nor v129
|
|
# behaves correctly here, so we disable optimization entirely by stubbing
|
|
# wasm-opt with a passthrough script. cargo-leptos resolves wasm-opt via
|
|
# `from_global_path` first (cargo-leptos-0.3.6/src/ext/exe.rs:225), so a stub
|
|
# on PATH wins over any downloaded copy. cargo-leptos invokes it as
|
|
# `wasm-opt <flags> <file> -o <file>` (in-place), so a no-op exit 0 leaves the
|
|
# wasm-bindgen output untouched.
|
|
RUN printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/wasm-opt && \
|
|
chmod +x /usr/local/bin/wasm-opt
|
|
ARG BIN_FEATURES
|
|
ARG BIN_NAME
|
|
ARG LEPTOS_OUTPUT_NAME
|
|
RUN --mount=type=secret,id=sccache_env,required \
|
|
--mount=type=cache,target=/workspace/website/target \
|
|
set -a && . /run/secrets/sccache_env && set +a && \
|
|
export RUSTC_WRAPPER=sccache && \
|
|
sccache --start-server && \
|
|
cargo leptos build --release --features "${BIN_FEATURES}" && \
|
|
cp /workspace/website/target/release/${BIN_NAME} /tmp/${BIN_NAME} && \
|
|
cp -r /workspace/website/target/site/ /tmp/site/ && \
|
|
sccache --show-stats
|
|
|
|
# ─── Stage 4: runtime ─────────────────────────────────────────────────────────
|
|
FROM debian:bookworm-slim
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
ca-certificates libssl3 curl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
RUN useradd -m -u 1000 www
|
|
|
|
ARG BIN_NAME
|
|
ARG LEPTOS_OUTPUT_NAME
|
|
ARG SERVER_PORT
|
|
|
|
# Server binary → /usr/local/bin/ so data PV mounts never shadow it.
|
|
COPY --from=builder /tmp/${BIN_NAME} /usr/local/bin/${BIN_NAME}
|
|
# nickel required at runtime: server calls `nickel export` to parse site/config/*.ncl
|
|
# on startup and site/rbac.ncl on RBAC hot-reload.
|
|
COPY --from=nickel-bin /usr/local/bin/nickel /usr/local/bin/nickel
|
|
# Rustelo framework nickel contracts — required by site/config/*.ncl at runtime.
|
|
# NICKEL_IMPORT_PATH must include this path so nickel resolves server/, site/, etc.
|
|
COPY --from=builder /workspace/rustelo/resources/nickel/ /usr/local/share/rustelo/nickel/
|
|
# Leptos WASM bundle paired with the server binary — both produced by the same
|
|
# cargo-leptos invocation in the builder stage. Source path is /tmp/site/pkg/
|
|
# because /workspace/website/target is a buildkit cache-mount, unmounted at
|
|
# the end of the RUN; only /tmp survives into later stages. Lives outside
|
|
# /var/www/ so the PV mount can't shadow it. The bootstrap recipe copies this
|
|
# into the PV at /var/www/site/pkg/ so binary and WASM stay version-matched by
|
|
# construction (Leptos server function hashes break under any binary↔WASM drift).
|
|
COPY --from=builder /tmp/site/pkg/ /usr/local/share/website/pkg/
|
|
|
|
WORKDIR /var/www
|
|
# Content-agnostic image: NO site/ baked in. The site tree (pkg/, public/, content/,
|
|
# config/, i18n/, templates/, rbac.ncl) is delivered to the PV mounted at
|
|
# /var/www/site/ by the publish tool — a gtar of website/site/ is packed,
|
|
# uploaded to the PV, unpacked, and the pod restarted to pick up the new tree.
|
|
# server.ncl public_dir = "site/public" resolves identically in dev (source tree)
|
|
# and prod (PV-delivered tree).
|
|
# The PV mount must be populated before first pod start; the K8s securityContext
|
|
# fsGroup=1000 (from the catalog deployment template) handles ownership at mount.
|
|
|
|
USER www
|
|
EXPOSE ${SERVER_PORT}
|
|
|
|
ENV RUST_LOG=info \
|
|
LEPTOS_ENV=PROD \
|
|
LEPTOS_SITE_ADDR=0.0.0.0:${SERVER_PORT} \
|
|
LEPTOS_SITE_ROOT=site \
|
|
LEPTOS_OUTPUT_NAME=${LEPTOS_OUTPUT_NAME} \
|
|
SITE_CONFIG_PATH=/var/www/site/config/index.ncl \
|
|
NICKEL_IMPORT_PATH=/usr/local/share/rustelo/nickel:/var/www/site/config \
|
|
SITE_SERVER_CONTENT_URL=/r \
|
|
SITE_SERVER_ROOT_CONTENT=r \
|
|
SITE_SERVER_CONTENT_ROOT=site/r \
|
|
NATS_ADMIN_CREDENTIALS_FILE=
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD curl -sf http://localhost:${SERVER_PORT}/health > /dev/null || exit 1
|
|
|
|
# Entrypoint: wait for the PV-delivered site tree before exec'ing the server.
|
|
# Image is content-agnostic; site/config/index.ncl must exist for the binary
|
|
# to boot. While the PV is empty (first install, before publish-tool delivery),
|
|
# this keeps the container alive and logs a clear "waiting" message instead
|
|
# of crash-looping. Signals propagate via exec when the server takes over.
|
|
COPY --chmod=755 <<'EOF' /usr/local/bin/entrypoint.sh
|
|
#!/bin/sh
|
|
set -eu
|
|
CONFIG="${SITE_CONFIG_PATH:-/var/www/site/config/index.ncl}"
|
|
while [ ! -f "$CONFIG" ]; do
|
|
printf '[%s] site config missing at %s — waiting for PV bootstrap (publish tool to deliver site tarball)\n' \
|
|
"$(date -Iseconds)" "$CONFIG" >&2
|
|
sleep 10
|
|
done
|
|
printf '[%s] site config found at %s — starting server\n' "$(date -Iseconds)" "$CONFIG" >&2
|
|
exec /usr/local/bin/rustelo-leptos-server "$@"
|
|
EOF
|
|
|
|
CMD ["/usr/local/bin/entrypoint.sh"]
|