diff --git a/.gitea/workflows/deploy-verso.yml b/.gitea/workflows/deploy-verso.yml index acba91b498..f19771c964 100644 --- a/.gitea/workflows/deploy-verso.yml +++ b/.gitea/workflows/deploy-verso.yml @@ -37,7 +37,23 @@ jobs: args: - | set -eux + REG=registry.git.svc.cluster.local:5000 git clone --depth 1 https://git.alocoq.fr/alois/verso.git /workspace/repo + + # (#1) Build the base image only when it actually changes. + # The base layers' only repo input is Dockerfile-base, so + # we key on a content hash of that file: the base is tagged + # verso-base:base- and the app builds FROM that exact + # tag. If a base with this hash is already in the registry, + # the heavy base build (apt, TeX Live, Quarto) is skipped. + BTAG=$(sha256sum /workspace/repo/server-ce/Dockerfile-base | cut -c1-16) + printf '%s' "$BTAG" > /workspace/base_tag + if wget -qO- "http://$REG/v2/verso-base/tags/list" 2>/dev/null | grep -q "\"base-$BTAG\""; then + echo "Base image base-$BTAG already present - skipping base build" + else + touch /workspace/build-base + echo "Base image base-$BTAG not found - base will be built" + fi volumeMounts: - name: workspace mountPath: /workspace @@ -54,30 +70,47 @@ jobs: # Push to the in-cluster registry (plain HTTP) to bypass # the Traefik ingress, whose read timeout was killing the - # multi-GB TeX Live layer upload mid-stream. The base - # image is pulled back in for the second build, so the - # registry must be marked insecure for both push and pull. - # Write buildkitd.toml inside the container (no extra - # k8s resources needed) so the second build's pull/resolve - # treats the registry as http. + # multi-GB TeX Live layer upload mid-stream. Mark the + # registry http+insecure so both push and the base pull + # for the app build treat it as plain HTTP. Written inside + # the container so no extra k8s resources are needed. REG=registry.git.svc.cluster.local:5000 mkdir -p /etc/buildkit printf '[registry."%s"]\n http = true\n insecure = true\n' "$REG" > /etc/buildkit/buildkitd.toml - buildctl-daemonless.sh build \ - --frontend=dockerfile.v0 \ - --local context=/workspace/repo \ - --local dockerfile=/workspace/repo/server-ce \ - --opt filename=Dockerfile-base \ - --output type=image,name=$REG/verso-base:latest,push=true,registry.insecure=true + BTAG=$(cat /workspace/base_tag) + BASE_REF="$REG/verso-base:base-$BTAG" + # (#1) Base build, only when prepare flagged it changed. + # (#2) Import/export a registry layer cache so that, when + # the base does change, unchanged layers (e.g. apt) are + # still reused instead of rebuilt from scratch. + if [ -f /workspace/build-base ]; then + buildctl-daemonless.sh build \ + --frontend=dockerfile.v0 \ + --local context=/workspace/repo \ + --local dockerfile=/workspace/repo/server-ce \ + --opt filename=Dockerfile-base \ + --import-cache type=registry,ref=$REG/verso-cache:base \ + --export-cache type=registry,ref=$REG/verso-cache:base,mode=max \ + --output type=image,name=$BASE_REF,push=true,registry.insecure=true + else + echo "Reusing existing base image $BASE_REF" + fi + + # App image, built FROM the content-pinned base tag. + # (#2) The registry cache lets yarn install be skipped when + # package.json is unchanged; the web build only re-runs + # when the frontend source actually changes. buildctl-daemonless.sh build \ --frontend=dockerfile.v0 \ --local context=/workspace/repo \ --local dockerfile=/workspace/repo/server-ce \ --opt filename=Dockerfile \ - --opt build-arg:OVERLEAF_BASE_TAG=$REG/verso-base:latest \ + --opt build-arg:OVERLEAF_BASE_TAG=$BASE_REF \ + --import-cache type=registry,ref=$REG/verso-cache:app \ + --export-cache type=registry,ref=$REG/verso-cache:app,mode=max \ --output type=image,name=$REG/verso:latest,push=true,registry.insecure=true volumeMounts: - name: workspace