build: cache the tailwind binary so deploys stop fetching it from GitHub #44
Loading…
Reference in a new issue
No description provided.
Delete branch "build/cache-tailwind-binary"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
OpenSpec change:
cache-tailwind-binary-for-container-builds.Problem
The
Build and Push Docker Imagejob runs on an ephemeral runner with no Docker layer cache, so every deploy'smix assets.deployre-downloaded the tailwindcss v4.0.9 standalone binary from GitHub (~130 MB). Deploys were slower and coupled to GitHub availability.What changed
.forgejo/scripts/fetch-tailwind-bin.sh— provisions.build-bin/tailwind-<target>-<version>: the version always comes from theconfig :tailwindblock ofconfig/config.exs, the target from the runner arch; fetches withcurl --fail --location --retry, verifies a pinned sha256, chmod 0755.resolvemode feeds the cache key;fetchmode is a no-op cache hit when the file is present..forgejo/workflows/ci.yml(docker job) — resolve version → probe the runner cache service (same pattern as the quality job) → cache.build-bin/keyedtailwind-bin-<target>-<version>(exact key: no stale-binary fallback) → provision with pinned digests.Dockerfile— copies.build-bin/into the builder at the exact pathdeps/tailwindresolves by default (default_bin_path/1→/app/_build/tailwind-<target>-<version>), so the package skips its GitHub download; absent file → today's download fallback (tracked.build-bin/.keepkeeps the COPY valid for local builds).docs/infrastructure/deployment.mddocuments the contract: version bump = one fetch + digest update, fallback on cold cache.Verification
::error::, broken parse → loud::error::, tampered download → sha256 mismatch → loud fail + file removed.Dockerfilelayers replicated in isolated builds:.keep-only context and binary-present context both build;chmodguard no-ops safely.docker build --checkclean on the real Dockerfile.Related, out of scope:
esbuildre-downloads its binaries the same way; the same pattern extends to it later.🤖 Skippy PR review
2 findings — 2 blocking before merge.
.forgejo/scripts/fetch-tailwind-bin.sh:55.forgejo/scripts/fetch-tailwind-bin.sh:90Reviewed
9767d960279c. Commentskippy reviewto re-run.@ -0,0 +52,4 @@;;esacFILENAME="tailwind-${TARGET}-${VERSION}"🟠 High — Cached binary lands under a filename deps/tailwind never looks for
FILENAMEistailwind-<TARGET>-<VERSION>whereTARGETis the release asset name, so this producestailwind-tailwindcss-linux-x64-4.0.9. The path the package resolves istailwind-linux-x64-4.0.9:Tailwind.default_bin_path/1(tailwind 0.5.1,lib/tailwind.ex:225-238) buildstailwind-<target>-<version>fromsystem_target/1, whose glibc Linux x86_64 target islinux-x64, nottailwindcss-linux-x64. Probed against this project's dep:Tailwind.bin_path("4.0.9")returns_build/tailwind-linux-x64-4.0.9, so in the containerCOPY .build-bin/ /app/_build/lands a file thatinstall_and_run/2'sFile.exists?(bin_path(version))never sees, and every deploy still downloads the 137MB binary from GitHub. The cache, the digest pinning and the copy are all a silent no-op. Keep the asset name and the bin target separate:pinned_digest/0's case arms and the workflow's cache key must followTARGETtolinux-x64/linux-arm64.@ -0,0 +87,4 @@url="${RELEASE_BASE}/v${VERSION}/${TARGET}"echo "fetching ${url}"curl --fail --silent --show-error --location --retry 3 --retry-delay 2 \🟡 Warning — A truncated download can poison the cache under the exact key
curl writes straight to
out_path, so a connection that dies mid-transfer (137MB over a flaky link, retries exhausted) exits non-zero underset -eand leaves a partial file there. Only the digest-mismatch branch below removes it, and that branch is never reached. The step fails, but the cache post-step still saves.build-binundertailwind-bin-<target>-<version>, and from then on the cache-hit path at line 77 returns 0 on[ -f ]without hashing, so every deploy copies a truncated, non-executable file into/app/_buildandmix assets.deploydies on it with an opaque exec error until the entry is evicted by hand. Download to${out_path}.partandmvit into place after the digest check, or verify the digest on the cache-hit path too.Resolved 2 of 2 earlier findings — the cached file is now
tailwind-linux-<arch>-<version>(matchesdefault_bin_path/1on tailwind 0.5.1), and downloads stage to.partialwith the cache-hit path re-hashing before use, so a truncated entry self-heals instead of reaching the builder.Nothing new in
9767d96..f78d02e; the pinned digests match the release's ownsha256sums.txtfor v4.0.9.