Vibecoded CI/CD
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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
27
lean/entrypoint.sh
Normal 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 "$@"
|
||||
Reference in New Issue
Block a user