Vibecoded CI/CD
Some checks failed
Build and Deploy Lean / build (push) Failing after 16m44s
Build and Deploy Nuxt / build (push) Failing after 48s

This commit is contained in:
2026-10-06 00:23:17 +02:00
parent a57770619e
commit e2c2c66d7f
79 changed files with 6044 additions and 47 deletions

View File

@@ -11,10 +11,19 @@
# 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); expect a long build and an image of several GB.
# 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.
FROM node:24-bookworm
# ---------- 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
@@ -28,19 +37,35 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
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/ (MathlibDemo pulls Mathlib and
# unpacks its precompiled cache — the slow part of the image) and then the
# editor client, which the server serves alongside its websocket
RUN npm run build:projects
# 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
@@ -56,9 +81,48 @@ RUN sed -i \
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
@@ -67,4 +131,5 @@ EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \
CMD curl -sf http://localhost:8080/health || exit 1
CMD ["npm", "run", "prod"]
ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "server/index.mjs"]

View File

@@ -25,6 +25,14 @@ docker build -t mathew-lean ./lean
docker run --rm -p 8080:8080 mathew-lean
```
The build is **multi-stage**: compilers, dev dependencies and build caches stay
in the builder, and the pushed image is a slim runtime carrying only what
sessions need — the node server, the elan toolchain, the built client, and
Mathlib's precompiled cache (the bulk of it, ~7GB of olean files; total image
~11GB). The upstream "Stable" demo project is dropped: it pinned a second
~3GB toolchain, and the wiki only ever opens the default project
(`MathlibDemo`).
`GET http://localhost:8080/health` answers when the server is up — the image
carries a `HEALTHCHECK` for it. The frontend finds the server at
`NUXT_PUBLIC_LEAN_URL` (default `http://localhost:8080`, dev included), so out
@@ -38,6 +46,29 @@ docker build --build-arg LEAN4WEB_REF=v0.2.2 -t mathew-lean ./lean
(any branch or tag; the clone is shallow, so commit SHAs are not offered.)
## Keeping Mathlib out of the image
Mathlib's compiled cache is what makes this image (and pushing it) heavy. If
the registry or the CI runner chokes on it, build without Mathlib and let a
volume hold it instead:
```bash
docker build --build-arg BAKE_MATHLIB=false -t mathew-lean ./lean # ~4GB
docker run --rm -p 8080:8080 -v lean-mathlib:/opt/lean4web/Projects/MathlibDemo/.lake mathew-lean
```
The `BAKE_MATHLIB=false` image still ships the pinned elan toolchain, but not
the project's `.lake`: the entrypoint notices a fresh volume and fills it once
at first start — `lake update` fetches the sources pinned in
`lake-manifest.json`, Mathlib's post-update hook downloads the matching
precompiled cache, `lake build` writes the project's own module. Expect ~10
minutes and a few hundred MB of download; restarts after that are instant,
and **image rebuilds never touch Mathlib again** (the volume stays). The
trade: the cache must survive on the host, first start needs network, and it
redoes once if you recreate with a clean volume — hence the default staying
`BAKE_MATHLIB=true`. The commented blocks in `docker-compose.yml` wire this
up for the production stack.
## The protocol the wiki's client speaks
The websocket endpoint is `/websocket/<project>` (project names come from
@@ -117,21 +148,27 @@ The image clones lean4web at build time, so a rebuild is an update:
docker compose build --pull lean && docker compose up -d lean
```
Rebuilding also re-runs `Projects/*/leanweb-build.sh` — the demo projects are
rebuilt and Mathlib's cache re-fetched, so treat this like the heavy build it
is (several GB of image; run it on the machine that hosts the editor).
Rebuilding also re-runs the project build — Mathlib's pin moves with upstream
and its cache re-downloads, so treat this like the heavy build it is (run it
on the machine that hosts the editor; pin `LEAN4WEB_REF` for a build that
stands still).
CI follows the same split: `.gitea/workflows/deploy-lean.yml` rebuilds, pushes
and redeploys the lean image **only when `lean/**` changes**; the regular
deploy pipeline ignores `lean/**` entirely and just pulls whatever
`mathew-lean:latest` the registry holds. (In volume mode the pushed image is
~4GB and a lean rebuild doesn't fetch Mathlib at all.)
## Adding Lean projects
Anything under `Projects/` of the *image source* becomes a selectable project
(`#project=<name>`). To ship one with the image, build a custom image that
copies it in before the project build step:
Anything under `Projects/` of the *running image* becomes a selectable project
(`#project=<name>`). To ship one with the image, add it in the Dockerfile's
builder stage, before the project build step:
```dockerfile
FROM mathew-lean AS base # or edit lean/Dockerfile directly
# in lean/Dockerfile, build stage, before the BAKE_MATHLIB step
COPY MyProject /opt/lean4web/Projects/MyProject
WORKDIR /opt/lean4web/Projects/MyProject
RUN lake build
RUN cd Projects/MyProject && lake build
```
See upstream's [Adding Projects](https://github.com/leanprover-community/lean4web/blob/main/doc/Projects.md)

27
lean/entrypoint.sh Normal file
View File

@@ -0,0 +1,27 @@
#!/bin/sh
# Mathew — lean server entrypoint.
#
# In the usual image this does nothing: the built Mathlib already sits under
# the project and the server starts straight away.
#
# With the image built as `--build-arg BAKE_MATHLIB=false` (see README's
# "Keeping Mathlib out of the image"), the project's .lake is meant to be a
# volume: a fresh one gets filled once, at first start, so Mathlib's ~7GB of
# compiled cache never travels through the registry.
set -eu
PROJECT=/opt/lean4web/Projects/MathlibDemo
if [ ! -f "$PROJECT/.lake/build/lib/lean/MathlibDemo.olean" ]; then
echo "[mathew-lean] $PROJECT/.lake holds no built Mathlib — fetching it once (expect ~10 minutes)"
cd "$PROJECT"
# lake update pulls the sources pinned in lake-manifest.json, and Mathlib's
# post-update hook fetches the matching precompiled cache; lake build then
# only writes the project's own module. The image's pinned toolchain
# (elan) stays as it is — no lean-toolchain re-curl, no surprise downloads.
lake update -R
lake build
echo "[mathew-lean] Mathlib ready"
fi
exec "$@"