136 lines
6.1 KiB
Docker
136 lines
6.1 KiB
Docker
# Mathew — the Lean editor server
|
|
#
|
|
# Builds lean4web (https://github.com/leanprover-community/lean4web) from its
|
|
# upstream source and runs its production server: one port serves the editor
|
|
# client and the Lean sessions, which is what the wiki frames at /lean.
|
|
#
|
|
# Lean4web runs a real Lean process per session, so upstream asks for a
|
|
# dedicated server — this image carries the editor and Mathlib, the machine
|
|
# carries the load.
|
|
#
|
|
# docker build -t mathew-lean ./lean
|
|
# docker run -p 8080:8080 mathew-lean # → http://localhost:8080
|
|
#
|
|
# The build is heavy by nature (Mathlib is fetched and its precompiled cache
|
|
# unpacked), but multi-stage keeps the junk out of the pushed image: the
|
|
# compilers, npm dev dependencies and caches stay in the builder, and the
|
|
# final image is a slim runtime with exactly what a session needs — the node
|
|
# server, the elan toolchain, the built client, and the project's Lean cache.
|
|
#
|
|
# To ship an image without Mathlib itself (~4GB instead of ~11GB), pass
|
|
# `--build-arg BAKE_MATHLIB=false` and hold the cache in a volume instead:
|
|
# the entrypoint fills a fresh volume once at first start. See
|
|
# "Keeping Mathlib out of the image" in lean/README.md.
|
|
|
|
# ---------- build ----------
|
|
FROM node:24-bookworm AS build
|
|
|
|
# lake fetches dependencies over git/curl, drives clang to link, and elan
|
|
# (Lean's toolchain manager) installs the toolchains each project pins
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
bash curl git ca-certificates unzip rsync \
|
|
build-essential clang pkg-config libssl-dev \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# elan, with no default toolchain — every project pins its own in its
|
|
# lean-toolchain file, and elan installs that one when asked
|
|
RUN curl -sSfL https://elan.lean-lang.org/elan-init.sh | bash -s -- -y --default-toolchain none
|
|
ENV PATH="/root/.elan/bin:${PATH}"
|
|
|
|
# cypress (a root dev dependency) pulls an ~800MB browser bundle on install —
|
|
# nothing in this build runs browser tests, so skip it
|
|
ENV CYPRESS_INSTALL_BINARY=0
|
|
|
|
# The lean4web source. Pass a tag or branch to pin a build:
|
|
# docker build --build-arg LEAN4WEB_REF=v0.2.2 ./lean
|
|
ARG LEAN4WEB_REF=main
|
|
RUN git clone --depth 1 --branch "${LEAN4WEB_REF}" \
|
|
https://github.com/leanprover-community/lean4web.git /opt/lean4web
|
|
|
|
# Ship only the MathlibDemo project: the wiki opens the default project, and
|
|
# upstream's second demo ("Stable") pins its own toolchain — elan would carry
|
|
# a second ~3GB one that no session ever uses.
|
|
RUN rm -rf /opt/lean4web/Projects/Stable
|
|
|
|
WORKDIR /opt/lean4web
|
|
RUN npm install
|
|
|
|
# Build the Lean projects under Projects/. For MathlibDemo this fetches
|
|
# Mathlib and unpacks its precompiled cache — the slow part of the image.
|
|
# With BAKE_MATHLIB=false that step is skipped and only the pinned toolchain
|
|
# is installed: the project ships unbuilt, and the entrypoint fills it on a
|
|
# first start (the intended pairing is a volume over the project's .lake).
|
|
ARG BAKE_MATHLIB=true
|
|
RUN if [ "${BAKE_MATHLIB}" = "true" ]; then \
|
|
npm run build:projects; \
|
|
else \
|
|
(cd Projects/MathlibDemo && lake --version > /dev/null); \
|
|
fi
|
|
|
|
# Mathew's theme layer: the wiki's font, stylesheet, and theme handshake
|
|
# join the client's public/ so they ship in the built bundle, and a sed
|
|
# wires them into index.html (and renames the tab). The wiki opens the
|
|
# frame as ?theme=light|dark and the editor opens matching the page
|
|
# around it — lean/theme/mathew.css explains how the skin attaches.
|
|
COPY theme/mathew.css theme/theme-init.js ./client/public/
|
|
COPY theme/fonts/. ./client/public/fonts/
|
|
RUN sed -i \
|
|
-e 's#<link rel="stylesheet" href="src/css/index.css" />#<script src="/theme-init.js"></script>\n <link rel="stylesheet" href="src/css/index.css" />\n <link rel="stylesheet" href="/mathew.css" />#' \
|
|
-e 's#<title>Lean Web</title>#<title>Lean · Mathew</title>#' \
|
|
client/index.html
|
|
|
|
RUN npm run build:client
|
|
|
|
# ---------- runtime ----------
|
|
# Only what serving sessions needs. The lean/lake binaries carry their own
|
|
# shared libraries — nothing else from the builder has to come along.
|
|
FROM node:24-bookworm-slim
|
|
|
|
# bash for scripts, curl for the healthcheck and Mathlib's cache fetch,
|
|
# git for lake's dependency operations
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
bash curl git ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# elan and the toolchain(s) the projects resolved at build time
|
|
COPY --from=build /root/.elan /root/.elan
|
|
ENV PATH="/root/.elan/bin:${PATH}"
|
|
|
|
WORKDIR /opt/lean4web
|
|
|
|
# The server, the built (wiki-skinned) client, and the manifests. Note the
|
|
# whole prod dependency tree: the server imports vscode-ws-jsonrpc out of the
|
|
# client workspace (npm hoists it), so a server-only `npm ci -w` would miss it.
|
|
COPY --from=build /opt/lean4web/package.json /opt/lean4web/package-lock.json ./
|
|
COPY --from=build /opt/lean4web/client/package.json ./client/
|
|
COPY --from=build /opt/lean4web/server ./server
|
|
COPY --from=build /opt/lean4web/client/dist ./client/dist
|
|
# The Lean project: toolchain pin, lakefile, and — unless BAKE_MATHLIB=false
|
|
# — Mathlib and its unpacked cache, the bulk of the image.
|
|
COPY --from=build /opt/lean4web/Projects ./Projects
|
|
|
|
RUN npm ci --omit=dev \
|
|
&& rm -rf /root/.npm
|
|
|
|
# Normally a no-op: the image already carries the built Mathlib. With
|
|
# BAKE_MATHLIB=false and a volume over the project's .lake, this fills a
|
|
# fresh volume once before the server starts (see lean/README.md).
|
|
COPY entrypoint.sh /entrypoint.sh
|
|
RUN chmod +x /entrypoint.sh
|
|
|
|
# Bubblewrap cannot sandbox inside a container (no nested user namespaces by
|
|
# default) — Docker itself is the isolation here, so the server runs Lean
|
|
# directly. PORT 8080 is lean4web's production default: editor + websocket.
|
|
# `npm run prod` would only add nodemon (a dev dependency) around this same
|
|
# `node index.mjs`, so the final image runs the server directly.
|
|
ENV NODE_ENV=production \
|
|
NO_BWRAP=true \
|
|
PORT=8080
|
|
EXPOSE 8080
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \
|
|
CMD curl -sf http://localhost:8080/health || exit 1
|
|
|
|
ENTRYPOINT ["/entrypoint.sh"]
|
|
CMD ["node", "server/index.mjs"]
|