- Use x-forwarded-proto header (allowlisted to http/https only) when building the invite link origin so it shows https behind a TLS proxy - Show "View Draft Board" link in the DraftInfoCard header when the draft is actively running, matching the link already shown on active/completed seasons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.6 KiB
CI Build Optimization Plan
The Forgejo Actions build pipeline currently takes ~15 minutes on main branch pushes:
- ~5 min:
setup-buildx-action - ~5 min: build and push to container registry
- ~5 min: deploy job (docker pull + migrate + compose up)
This plan cuts that to ~3-5 minutes for typical code changes.
Root Causes
1. Dockerfile layer invalidation (biggest win)
The development-dependencies-env stage copies all source files before running npm ci:
FROM node:20-alpine AS development-dependencies-env
COPY . /app # <-- invalidates npm cache on EVERY commit
WORKDIR /app
RUN npm ci
Every single push blows the npm install cache layer, even if package.json hasn't changed. This means:
- The build job re-runs
npm ciinside Docker on every commit (~1-2 min) - The server's
docker compose pullre-downloads the large node_modules layer (~200-300MB) on every deploy
The production-dependencies-env stage already does this correctly — the dev stage needs the same fix.
2. Buildx setup taking 5 minutes
setup-buildx-action normally takes ~20-30 seconds. 5 minutes means QEMU multi-platform emulators are being installed (for arm64 support). Since we only target linux/amd64, explicitly declaring the platform skips QEMU installation entirely.
3. No npm cache in CI jobs
The test, typecheck, and lint jobs each run npm ci independently with no caching. This adds ~30-60s per job on cold runners.
Changes
Fix 1: Dockerfile — copy only manifests before npm ci
File: Dockerfile
FROM node:20-alpine AS development-dependencies-env
COPY package.json package-lock.json .npmrc /app/ # only manifests
WORKDIR /app
RUN npm ci
FROM node:20-alpine AS production-dependencies-env
COPY ./package.json package-lock.json .npmrc /app/
WORKDIR /app
RUN npm ci --omit=dev
FROM node:20-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN npm run build
FROM node:20-alpine
COPY ./package.json package-lock.json /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
COPY --from=build-env /app/dist /app/dist
COPY ./drizzle /app/drizzle
COPY ./scripts /app/scripts
COPY ./instrument.server.mjs /app/instrument.server.mjs
WORKDIR /app
CMD ["npm", "run", "start"]
Fix 2: Declare platform in build-push-action
File: .forgejo/workflows/deploy.yml — the build job's "Build and Push" step
Add platforms: linux/amd64:
- name: 🗄️ Build and Push to Container Registry
uses: https://github.com/docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64
tags: ${{ vars.CONTAINER_REGISTRY }}/brackt:latest
cache-from: type=registry,ref=${{ vars.CONTAINER_REGISTRY }}/brackt:buildcache
cache-to: type=registry,ref=${{ vars.CONTAINER_REGISTRY }}/brackt:buildcache,mode=max
Fix 3: Add npm cache to CI jobs
File: .forgejo/workflows/deploy.yml — test, typecheck, lint jobs
Add after checkout in each job:
- name: 📦 Cache node_modules
uses: https://github.com/actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
Note: If Forgejo's runner doesn't support the cache action, this silently no-ops (harmless). An alternative is merging the three parallel jobs into one that installs once and runs all checks sequentially — unconditionally saves two
npm cicalls.
Expected Results
docker pull on the server is incremental — it only downloads layers that changed since the last deploy. After the Dockerfile fix, most deploys only pull the small build-output layer instead of the full node_modules.
| Step | Before | After (code change) | After (dep change) |
|---|---|---|---|
| Setup buildx | ~5 min | ~30 sec | ~30 sec |
| Build + push | ~5 min | ~1-2 min | ~3-4 min |
| Deploy (docker pull + up) | ~5 min | ~1-2 min | ~3-4 min |
| Total | ~15 min | ~3-5 min | ~8-10 min |
Verification
- Push a code-only commit (no package.json change) — confirm Docker build uses registry cache for npm install layers and the build step completes in under 2 minutes
- Push a commit changing package.json — confirm npm cache is correctly invalidated and reruns
- Check the
setup-buildx-actionlog — should show ~30s, no QEMU download - Check deploy job —
docker compose pullshould show most layers asAlready exists - Confirm deployed app is functional