From 699b5a53e18c76c262f40ae0ae1a32dc726e4851 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Fri, 10 Jul 2026 13:58:04 +0200 Subject: [PATCH 01/20] ci: gate DigitalOcean steps behind vars.DO_ENABLED (we no longer use DO) (#987) DigitalOcean image pushes are now opt-in. Each docker-build-publish workflow (dev/stage/prod): - Passes vars.DO_ENABLED into the "Resolve image tags" (meta) step and only appends the registry.digitalocean.com/... tags when DO_ENABLED == 'true', so the push:true build-push-action never tries to push to DO (which would 403) when DO is disabled. - ANDs vars.DO_ENABLED == 'true' into the existing `if:` on the "Install doctl" and "Login to DigitalOcean Container Registry" steps. No DO code is deleted -- it is simply skipped by default and re-enabled by setting the org/repo variable DO_ENABLED=true. GHCR/Docker Hub and all other steps are unchanged. Co-authored-by: Claude Opus 4.8 --- .github/workflows/docker-build-publish-dev.yml | 10 +++++++--- .github/workflows/docker-build-publish-prod.yml | 10 +++++++--- .github/workflows/docker-build-publish-stage.yml | 10 +++++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker-build-publish-dev.yml b/.github/workflows/docker-build-publish-dev.yml index ae4dbe293..971563da6 100644 --- a/.github/workflows/docker-build-publish-dev.yml +++ b/.github/workflows/docker-build-publish-dev.yml @@ -32,6 +32,7 @@ jobs: DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} DIGITALOCEAN_REGISTRY: ${{ vars.DIGITALOCEAN_REGISTRY || 'ever' }} + DO_ENABLED: ${{ vars.DO_ENABLED }} run: | set -euo pipefail @@ -57,7 +58,10 @@ jobs: fi digitalocean_enabled=false - if [[ -n "$DIGITALOCEAN_ACCESS_TOKEN" && -n "$DIGITALOCEAN_REGISTRY" ]]; then + # DigitalOcean image pushes are opt-in: gated behind vars.DO_ENABLED == 'true' + # so the registry.digitalocean.com tag is never added (and buildx never pushes + # to DO) unless DO is explicitly enabled. DO code is kept, just skipped by default. + if [[ "${DO_ENABLED:-}" == "true" && -n "$DIGITALOCEAN_ACCESS_TOKEN" && -n "$DIGITALOCEAN_REGISTRY" ]]; then do_registry="$(printf '%s' "$DIGITALOCEAN_REGISTRY" | tr '[:upper:]' '[:lower:]')" tags+=( "registry.digitalocean.com/${do_registry}/${image_name}:latest" @@ -89,13 +93,13 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Install doctl - if: steps.meta.outputs.digitalocean_enabled == 'true' + if: ${{ steps.meta.outputs.digitalocean_enabled == 'true' && vars.DO_ENABLED == 'true' }} uses: digitalocean/action-doctl@v2 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: Login to DigitalOcean Container Registry - if: steps.meta.outputs.digitalocean_enabled == 'true' + if: ${{ steps.meta.outputs.digitalocean_enabled == 'true' && vars.DO_ENABLED == 'true' }} run: doctl registry login --expiry-seconds 3600 - name: Set up Docker Buildx diff --git a/.github/workflows/docker-build-publish-prod.yml b/.github/workflows/docker-build-publish-prod.yml index 6ba3b48b5..58ae72fbc 100644 --- a/.github/workflows/docker-build-publish-prod.yml +++ b/.github/workflows/docker-build-publish-prod.yml @@ -32,6 +32,7 @@ jobs: DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} DIGITALOCEAN_REGISTRY: ${{ vars.DIGITALOCEAN_REGISTRY || 'ever' }} + DO_ENABLED: ${{ vars.DO_ENABLED }} run: | set -euo pipefail @@ -57,7 +58,10 @@ jobs: fi digitalocean_enabled=false - if [[ -n "$DIGITALOCEAN_ACCESS_TOKEN" && -n "$DIGITALOCEAN_REGISTRY" ]]; then + # DigitalOcean image pushes are opt-in: gated behind vars.DO_ENABLED == 'true' + # so the registry.digitalocean.com tag is never added (and buildx never pushes + # to DO) unless DO is explicitly enabled. DO code is kept, just skipped by default. + if [[ "${DO_ENABLED:-}" == "true" && -n "$DIGITALOCEAN_ACCESS_TOKEN" && -n "$DIGITALOCEAN_REGISTRY" ]]; then do_registry="$(printf '%s' "$DIGITALOCEAN_REGISTRY" | tr '[:upper:]' '[:lower:]')" tags+=( "registry.digitalocean.com/${do_registry}/${image_name}:latest" @@ -89,13 +93,13 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Install doctl - if: steps.meta.outputs.digitalocean_enabled == 'true' + if: ${{ steps.meta.outputs.digitalocean_enabled == 'true' && vars.DO_ENABLED == 'true' }} uses: digitalocean/action-doctl@v2 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: Login to DigitalOcean Container Registry - if: steps.meta.outputs.digitalocean_enabled == 'true' + if: ${{ steps.meta.outputs.digitalocean_enabled == 'true' && vars.DO_ENABLED == 'true' }} run: doctl registry login --expiry-seconds 3600 - name: Set up Docker Buildx diff --git a/.github/workflows/docker-build-publish-stage.yml b/.github/workflows/docker-build-publish-stage.yml index 7723c7253..1befa3aa7 100644 --- a/.github/workflows/docker-build-publish-stage.yml +++ b/.github/workflows/docker-build-publish-stage.yml @@ -32,6 +32,7 @@ jobs: DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} DIGITALOCEAN_REGISTRY: ${{ vars.DIGITALOCEAN_REGISTRY || 'ever' }} + DO_ENABLED: ${{ vars.DO_ENABLED }} run: | set -euo pipefail @@ -57,7 +58,10 @@ jobs: fi digitalocean_enabled=false - if [[ -n "$DIGITALOCEAN_ACCESS_TOKEN" && -n "$DIGITALOCEAN_REGISTRY" ]]; then + # DigitalOcean image pushes are opt-in: gated behind vars.DO_ENABLED == 'true' + # so the registry.digitalocean.com tag is never added (and buildx never pushes + # to DO) unless DO is explicitly enabled. DO code is kept, just skipped by default. + if [[ "${DO_ENABLED:-}" == "true" && -n "$DIGITALOCEAN_ACCESS_TOKEN" && -n "$DIGITALOCEAN_REGISTRY" ]]; then do_registry="$(printf '%s' "$DIGITALOCEAN_REGISTRY" | tr '[:upper:]' '[:lower:]')" tags+=( "registry.digitalocean.com/${do_registry}/${image_name}:latest" @@ -89,13 +93,13 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Install doctl - if: steps.meta.outputs.digitalocean_enabled == 'true' + if: ${{ steps.meta.outputs.digitalocean_enabled == 'true' && vars.DO_ENABLED == 'true' }} uses: digitalocean/action-doctl@v2 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: Login to DigitalOcean Container Registry - if: steps.meta.outputs.digitalocean_enabled == 'true' + if: ${{ steps.meta.outputs.digitalocean_enabled == 'true' && vars.DO_ENABLED == 'true' }} run: doctl registry login --expiry-seconds 3600 - name: Set up Docker Buildx From 3505dc46fd28b26272f418f2553dfd6bcca74fc2 Mon Sep 17 00:00:00 2001 From: "Ever (Claude Code)" Date: Sat, 11 Jul 2026 15:04:08 +0200 Subject: [PATCH 02/20] ci(docs): build directory-web-template-docs image (was stale) apps/docs (@ever-works/docs, Docusaurus) had no Dockerfile / matrix entry, so ghcr.io/ever-works/directory-web-template-docs went stale (last 2026-07-08) while docs-demo-ever-works-prod keeps running it. Add: - apps/docs/Dockerfile: base -> turbo prune @ever-works/docs -> pnpm install -> docusaurus build -> nginx (mirrors the root Dockerfile + ever-works docs build; copies repo-root docs/ since Docusaurus reads ../../docs). - apps/docs/nginx.conf: static serve with clean-URL try_files + Docusaurus 404. - k8s-build.yml: matrix entry directory-web-template-docs -> :dev/:stage/:prod+:sha. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/k8s-build.yml | 3 ++ apps/docs/Dockerfile | 58 +++++++++++++++++++++++++++++++++ apps/docs/nginx.conf | 46 ++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 apps/docs/Dockerfile create mode 100644 apps/docs/nginx.conf diff --git a/.github/workflows/k8s-build.yml b/.github/workflows/k8s-build.yml index 3867dac9f..9f2770af9 100644 --- a/.github/workflows/k8s-build.yml +++ b/.github/workflows/k8s-build.yml @@ -19,6 +19,9 @@ jobs: - image: directory-web-template dockerfile: Dockerfile context: . + - image: directory-web-template-docs + dockerfile: apps/docs/Dockerfile + context: . steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 diff --git a/apps/docs/Dockerfile b/apps/docs/Dockerfile new file mode 100644 index 000000000..96989fe18 --- /dev/null +++ b/apps/docs/Dockerfile @@ -0,0 +1,58 @@ +# syntax=docker/dockerfile:1.7 +# Ever Works directory-web-template — Docusaurus documentation site (apps/docs = @ever-works/docs). +# Serves docs.demo.ever.works. Mirrors the repo's OWN root Dockerfile (turbo prune) + the ever-works +# docs build. Docs content lives in the repo-root docs/ (Docusaurus `path: ../../docs`). +# docker build -f apps/docs/Dockerfile -t ghcr.io/ever-works/directory-web-template-docs . + +ARG NODE_VERSION=22-alpine +ARG PNPM_VERSION=10.31.0 +ARG TURBO_VERSION=2.9.14 + +# ---- base ------------------------------------------------------------------ +FROM node:${NODE_VERSION} AS base +RUN corepack enable && \ + corepack prepare pnpm@${PNPM_VERSION} --activate && \ + npm install -g turbo@${TURBO_VERSION} +ENV CI=true +ENV NEXT_TELEMETRY_DISABLED=1 + +# ---- pruner: isolate the docs workspace + its deps ------------------------- +FROM base AS pruner +WORKDIR /app +COPY . . +RUN turbo prune @ever-works/docs --docker + +# ---- builder: install deps + build the static site ------------------------ +FROM base AS builder +WORKDIR /app +# NB: do NOT set NODE_ENV=production for the install — Docusaurus build tooling +# lives in devDependencies. NODE_OPTIONS raises the heap for the multi-locale build. +ENV NODE_OPTIONS="--max-old-space-size=4096" +RUN apk add --no-cache git libc6-compat python3 make g++ pkgconfig + +COPY --from=pruner /app/out/json/ . +COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml + +# Internal Verdaccio cache override (fork-safe; empty => public npm), same as the root image. +ARG VERDACCIO_REGISTRY="" +RUN if [ -n "$VERDACCIO_REGISTRY" ]; then \ + echo "registry=${VERDACCIO_REGISTRY}" >> /app/.npmrc && \ + { sed -i "s|https://registry.npmjs.org|${VERDACCIO_REGISTRY%/}|g" /app/pnpm-lock.yaml 2>/dev/null || true; }; \ + fi + +RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile + +COPY --from=pruner /app/out/full/ . +# Docusaurus reads content from ../../docs (the repo-root docs/); turbo prune omits +# non-workspace dirs, so copy it in explicitly (resolves to /app/docs -> apps/docs/../../docs). +COPY docs/ ./docs/ + +RUN pnpm --filter @ever-works/docs build + +# ---- runtime: nginx serving the static build ------------------------------ +FROM nginx:1.27-alpine +COPY apps/docs/nginx.conf /etc/nginx/conf.d/default.conf +RUN rm -rf /usr/share/nginx/html/* +COPY --from=builder /app/apps/docs/build /usr/share/nginx/html +EXPOSE 80 diff --git a/apps/docs/nginx.conf b/apps/docs/nginx.conf new file mode 100644 index 000000000..d87152f84 --- /dev/null +++ b/apps/docs/nginx.conf @@ -0,0 +1,46 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Standard text-asset compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_types + text/plain + text/css + text/javascript + application/javascript + application/json + application/xml + application/rss+xml + image/svg+xml; + + # Long cache for hashed/static assets emitted by Docusaurus + location ~* \.(js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|webp|ico)$ { + expires 30d; + add_header Cache-Control "public, max-age=2592000, immutable"; + try_files $uri =404; + } + + # No cache for HTML so deploys ship instantly + location ~* \.html$ { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + try_files $uri =404; + } + + # Serve clean URLs (Docusaurus emits both /foo/ and /foo/index.html) + location / { + try_files $uri $uri/ $uri.html $uri/index.html =404; + } + + # Custom 404 page from Docusaurus + error_page 404 /404.html; + location = /404.html { + internal; + } +} From a4dd80a06cc0fd84a989c443c80c74c71040eb9b Mon Sep 17 00:00:00 2001 From: "Ever (Claude Code)" Date: Sat, 11 Jul 2026 20:23:13 +0200 Subject: [PATCH 03/20] =?UTF-8?q?ci(docs):=20build=20docs=20English-only?= =?UTF-8?q?=20(full=20multi-locale=20SSG=20ran=20~2h=20=E2=86=92=20ENOSPC?= =?UTF-8?q?=20on=20RAM-disk)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first attempt's `docusaurus build` (all locales) rendered every locale of the large API docs, ran ~7480s on the ARC RAM-disk runner (starving the shared pool) and died with ENOSPC mid-way through /pt/. Switch to `build:en` (English only) — a fraction of the disk/time, fits the RAM-disk. Re-enable full-locale later on a disk-backed builder. Co-Authored-By: Claude Opus 4.8 --- apps/docs/Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/docs/Dockerfile b/apps/docs/Dockerfile index 96989fe18..9c1e75254 100644 --- a/apps/docs/Dockerfile +++ b/apps/docs/Dockerfile @@ -48,7 +48,11 @@ COPY --from=pruner /app/out/full/ . # non-workspace dirs, so copy it in explicitly (resolves to /app/docs -> apps/docs/../../docs). COPY docs/ ./docs/ -RUN pnpm --filter @ever-works/docs build +# English-only build. The full multi-locale SSG (build) renders every locale (en/es/fr/pt/…) +# of the large API docs, which on the ARC RAM-disk runner runs for ~2h and then dies with +# ENOSPC. `build:en` (docusaurus build --locale en) is a fraction of the size/time and fits. +# TODO: re-enable full-locale once the docs image builds on a disk-backed runner. +RUN pnpm --filter @ever-works/docs build:en # ---- runtime: nginx serving the static build ------------------------------ FROM nginx:1.27-alpine From 69c76bf6d31e0f146f3278c516928ab8aa92430b Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:02 +0200 Subject: [PATCH 04/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 022f5d120..dd56c10c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,13 +27,13 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: '24' cache: pnpm From cb3b692a433cc0783f6225b6d8413b56fcdd8eef Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:05 +0200 Subject: [PATCH 05/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bec2e9b35..ebf3fcde1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -31,13 +31,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: '24' cache: pnpm From c293d95893c078abb5200b5a0a4dbbfd864e3155 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:15 +0200 Subject: [PATCH 06/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/deploy_vercel.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_vercel.yaml b/.github/workflows/deploy_vercel.yaml index 50ce67674..7563d2a8d 100644 --- a/.github/workflows/deploy_vercel.yaml +++ b/.github/workflows/deploy_vercel.yaml @@ -35,13 +35,13 @@ jobs: - name: Target environment run: echo "Deploying to ${{ inputs.environment }}" - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: "24" cache: pnpm From 25d0f06ba4389479744166d040da21fc6e9dc5f7 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:18 +0200 Subject: [PATCH 07/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/disable-default-codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/disable-default-codeql.yml b/.github/workflows/disable-default-codeql.yml index 429e48f63..77f55c5f5 100644 --- a/.github/workflows/disable-default-codeql.yml +++ b/.github/workflows/disable-default-codeql.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Disable Default CodeQL Setup uses: github/codeql-action/init@v3 From 26eef74ddd90fe5638b74fc30d7d45bc8d563821 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:21 +0200 Subject: [PATCH 08/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/docker-build-publish-dev.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-build-publish-dev.yml b/.github/workflows/docker-build-publish-dev.yml index 971563da6..b8534a526 100644 --- a/.github/workflows/docker-build-publish-dev.yml +++ b/.github/workflows/docker-build-publish-dev.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Resolve image tags id: meta @@ -79,7 +79,7 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} @@ -87,7 +87,7 @@ jobs: - name: Login to Docker Hub if: steps.meta.outputs.dockerhub_enabled == 'true' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -103,10 +103,10 @@ jobs: run: doctl registry login --expiry-seconds 3600 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and publish image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: Dockerfile From 486e9402e67678c5fbde4042fd5dc492529a8a07 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:24 +0200 Subject: [PATCH 09/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/docker-build-publish-prod.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-build-publish-prod.yml b/.github/workflows/docker-build-publish-prod.yml index 58ae72fbc..c8f87a9c0 100644 --- a/.github/workflows/docker-build-publish-prod.yml +++ b/.github/workflows/docker-build-publish-prod.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Resolve image tags id: meta @@ -79,7 +79,7 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} @@ -87,7 +87,7 @@ jobs: - name: Login to Docker Hub if: steps.meta.outputs.dockerhub_enabled == 'true' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -103,10 +103,10 @@ jobs: run: doctl registry login --expiry-seconds 3600 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and publish image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: Dockerfile From 895cd2260eb58804ae31c135927796efee54f5d8 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:26 +0200 Subject: [PATCH 10/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/docker-build-publish-stage.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-build-publish-stage.yml b/.github/workflows/docker-build-publish-stage.yml index 1befa3aa7..d3aed6b44 100644 --- a/.github/workflows/docker-build-publish-stage.yml +++ b/.github/workflows/docker-build-publish-stage.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Resolve image tags id: meta @@ -79,7 +79,7 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} @@ -87,7 +87,7 @@ jobs: - name: Login to Docker Hub if: steps.meta.outputs.dockerhub_enabled == 'true' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -103,10 +103,10 @@ jobs: run: doctl registry login --expiry-seconds 3600 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and publish image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: Dockerfile From 566d7489a29d791fe5238a540221aa352079fc5c Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:30 +0200 Subject: [PATCH 11/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f32f53dab..097b53c56 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,13 +22,13 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: '24' cache: pnpm From 9a825351c2a342bb16ce5fb4e9c1ee2101e8d2cf Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:32 +0200 Subject: [PATCH 12/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/e2e.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 66d32a9e9..288f84242 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -108,13 +108,13 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: '24' cache: pnpm @@ -250,7 +250,7 @@ jobs: - name: Upload Playwright report (shard ${{ matrix.shard }}) if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: playwright-report-shard-${{ matrix.shard }} path: apps/web-e2e/playwright-report @@ -258,7 +258,7 @@ jobs: - name: Upload test results (shard ${{ matrix.shard }}) if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: playwright-test-results-shard-${{ matrix.shard }} path: apps/web-e2e/test-results From bff7f5e618bd512a64f52c2b9f62d58395de3bbe Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 17:51:36 +0200 Subject: [PATCH 13/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/k8s-build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/k8s-build.yml b/.github/workflows/k8s-build.yml index 9f2770af9..b665b694f 100644 --- a/.github/workflows/k8s-build.yml +++ b/.github/workflows/k8s-build.yml @@ -11,7 +11,7 @@ permissions: packages: write jobs: build: - runs-on: ${{ vars.RUNNER_LINUX_X64_8 || vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }} + runs-on: ${{ vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }} strategy: fail-fast: false matrix: @@ -23,9 +23,9 @@ jobs: dockerfile: apps/docs/Dockerfile context: . steps: - - uses: actions/checkout@v4 - - uses: docker/setup-buildx-action@v3 - - uses: docker/login-action@v3 + - uses: actions/checkout@v7 + - uses: docker/setup-buildx-action@v4 + - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} @@ -38,7 +38,7 @@ jobs: main|master) echo "tag=prod" >> "$GITHUB_OUTPUT" ;; *) echo "tag=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_OUTPUT" ;; esac - - uses: docker/build-push-action@v6 + - uses: docker/build-push-action@v7 with: context: ${{ matrix.context }} file: ${{ matrix.dockerfile }} From efa639772d5c134ec7f8cfc9124fc6650c57a70d Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 20:17:04 +0200 Subject: [PATCH 14/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd56c10c6..6ed1eca7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@v7 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - name: Setup Node.js uses: actions/setup-node@v7 From 334b5461bbc53f35ad045374af6e76bbebd669f4 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 20:17:07 +0200 Subject: [PATCH 15/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ebf3fcde1..5f73d92a4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@v7 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - name: Setup Node.js uses: actions/setup-node@v7 From 1521cfd3b4178c9ba75b719e3d19f3bc138fa148 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 20:17:14 +0200 Subject: [PATCH 16/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/deploy_vercel.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_vercel.yaml b/.github/workflows/deploy_vercel.yaml index 7563d2a8d..805d7a432 100644 --- a/.github/workflows/deploy_vercel.yaml +++ b/.github/workflows/deploy_vercel.yaml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - name: Setup Node.js uses: actions/setup-node@v7 From 9c64a575523fe1519d6b74bb3833be22907c054e Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 20:17:21 +0200 Subject: [PATCH 17/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 097b53c56..ec3510fc4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v7 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - name: Setup Node.js uses: actions/setup-node@v7 From 4e9d3772c13e81100a6d94515a1d6d723b410b76 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Tue, 14 Jul 2026 20:17:24 +0200 Subject: [PATCH 18/20] ci: x4 runner (not x8) + bump actions to latest Node-24 versions (removes Node-20 deprecation warnings) [skip ci] --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 288f84242..5914a6b81 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -111,7 +111,7 @@ jobs: uses: actions/checkout@v7 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - name: Setup Node.js uses: actions/setup-node@v7 From 9648d620073006de27fe8158124abbbea1be80bd Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Sun, 19 Jul 2026 00:04:22 +0200 Subject: [PATCH 19/20] fix(cache): skip 2MB-capped Data Cache for oversized listings (#988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Large directories (e.g. the demo's 3271 items ≈ 2.6MB) exceed Next.js `unstable_cache`'s hard 2MB per-entry limit. Next then SILENTLY drops the entry and recomputes + re-serializes the whole catalogue on every request ("Failed to set Next.js data cache … items over 2MB can not be cached"), pegging CPU (demo.ever.works web pod sat at ~1.5 cores sustained, ~49 cache-fail log lines per 500). The listing wrappers (getCachedItems / getCachedItemsByCategory / getCachedItemsByTag / getCachedItemsByCategoryAndTag) now size-gate the Data Cache layer: when a listing exceeds DATA_CACHE_MAX_ITEMS (default 2000, override via CONTENT_DATA_CACHE_MAX_ITEMS) they skip unstable_cache and serve from the in-memory fetchItems cache (10-min TTL, cleared on content sync) instead. Listing items are metadata-only (markdown stripped, ~0.8KB each), so item count is a reliable size proxy. Small directories are unaffected and keep the persistent Data Cache. Co-authored-by: Claude Opus 4.8 --- apps/web/lib/cache-config.ts | 18 ++++++++++++++++++ apps/web/lib/content.ts | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/web/lib/cache-config.ts b/apps/web/lib/cache-config.ts index e0db4710d..41c93364d 100644 --- a/apps/web/lib/cache-config.ts +++ b/apps/web/lib/cache-config.ts @@ -68,3 +68,21 @@ export const CACHE_TAGS = { /** Comparisons by locale */ COMPARISONS_LOCALE: (locale: string) => `comparisons:${locale}` } as const; + +/** + * Max number of items a *listing* may contain to still be persisted in Next's Data Cache + * (`unstable_cache`). + * + * `unstable_cache` hard-caps a single entry at 2MB and *silently drops* anything larger — the + * entry is then recomputed AND re-serialized on every request (`Failed to set Next.js data cache … + * items over 2MB can not be cached`). On a large directory this pegs CPU. Listing items are + * metadata-only (the markdown body is stripped in `fetchItems`, ~0.8KB/item), so ~2000 items + * ≈ 1.6MB stays safely under the 2MB ceiling. Bigger catalogues skip the persistent layer and + * fall back to the in-memory `fetchItems` cache (10-min TTL, cleared on content sync) instead. + * + * Override with the `CONTENT_DATA_CACHE_MAX_ITEMS` env var if your items are unusually large/small. + */ +export const DATA_CACHE_MAX_ITEMS: number = (() => { + const raw = Number(process.env.CONTENT_DATA_CACHE_MAX_ITEMS); + return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 2000; +})(); diff --git a/apps/web/lib/content.ts b/apps/web/lib/content.ts index 52817845f..2f4a6a2ef 100644 --- a/apps/web/lib/content.ts +++ b/apps/web/lib/content.ts @@ -10,7 +10,7 @@ import { dirExists, fsExists, getContentPath } from './lib'; import { getContentConfigPaths, mergeConfigObjects } from './content-config-file'; import { unstable_cache } from 'next/cache'; import { PaymentInterval, PaymentProvider } from './constants'; -import { CACHE_TAGS, CACHE_TTL as CONTENT_CACHE_TTL } from './cache-config'; +import { CACHE_TAGS, CACHE_TTL as CONTENT_CACHE_TTL, DATA_CACHE_MAX_ITEMS } from './cache-config'; import { Collection } from '@/types/collection'; import type { ComparisonData, ComparisonDetail, ComparisonDimension } from '@/types/comparison'; import type { ItemLocationData } from '@/lib/types/item'; @@ -2131,11 +2131,28 @@ export async function fetchHeroContent(source: string, locale: string = 'en'): P // Listings: short-TTL unstable_cache keyed by content revision so cold starts // don't re-walk the YAML tree. Single-item / page reads keep the longer TTL. +/** + * A listing may only be persisted in Next's Data Cache (`unstable_cache`) if it fits the 2MB + * per-entry ceiling. Beyond it, `unstable_cache` silently discards the entry and recomputes + + * re-serializes the whole payload on EVERY request (`items over 2MB can not be cached`), which + * pegs CPU on large directories. When a listing is too big we skip the persistent layer and let + * the in-memory `fetchItemsCache` (10-min TTL, cleared on content sync) serve it instead. + * See {@link DATA_CACHE_MAX_ITEMS}. + */ +const canPersistListing = (itemCount: number): boolean => itemCount <= DATA_CACHE_MAX_ITEMS; + export const getCachedItems = async (options: FetchOptions = {}) => { if (!CONTENT_CACHE_ENABLED) { return fetchItems(options); } + // `fetchItems` is in-memory cached, so this is cheap on the hot path and lets us size-gate + // the (2MB-capped) Data Cache layer before paying to serialize an oversized catalogue. + const result = await fetchItems(options); + if (!canPersistListing(result.items.length)) { + return result; + } + const locale = options.lang || 'en'; const revision = await getContentRevision(); const optionsKey = JSON.stringify(options); @@ -2290,6 +2307,11 @@ export const getCachedItemsByCategory = async (raw: string, options: FetchOption return fetchByCategory(raw, options); } + const result = await fetchByCategory(raw, options); + if (!canPersistListing(result.items.length)) { + return result; + } + const locale = options.lang || 'en'; const revision = await getContentRevision(); const optionsKey = JSON.stringify(options); @@ -2308,6 +2330,11 @@ export const getCachedItemsByTag = async (raw: string, options: FetchOptions = { return fetchByTag(raw, options); } + const result = await fetchByTag(raw, options); + if (!canPersistListing(result.items.length)) { + return result; + } + const locale = options.lang || 'en'; const revision = await getContentRevision(); const optionsKey = JSON.stringify(options); @@ -2326,6 +2353,11 @@ export const getCachedItemsByCategoryAndTag = async (category: string, tag: stri return fetchByCategoryAndTag(category, tag, options); } + const result = await fetchByCategoryAndTag(category, tag, options); + if (!canPersistListing(result.items.length)) { + return result; + } + const locale = options.lang || 'en'; const revision = await getContentRevision(); const optionsKey = JSON.stringify(options); From e468bc3b6d51519c5c4d3b06ec27586e71978be7 Mon Sep 17 00:00:00 2001 From: evereq Date: Sun, 19 Jul 2026 13:56:20 +0200 Subject: [PATCH 20/20] ci: move CodeQL runners to self-hosted ARC fleet Point the two remaining bare `ubuntu-latest` jobs (CodeQL analysis and the default-CodeQL disabler) at the self-hosted ARC var, keeping the GitHub-hosted fallback: runs-on: ${{ vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }} CodeQL/SARIF jobs use the smallest (_4) pool per runner policy. All other workflows already use the ARC vars. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/codeql.yml | 2 +- .github/workflows/disable-default-codeql.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5f73d92a4..f5545baf4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,7 +16,7 @@ concurrency: jobs: analyze: name: Analyze (${{ matrix.language }}) - runs-on: ubuntu-latest + runs-on: ${{ vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }} timeout-minutes: 360 permissions: actions: read diff --git a/.github/workflows/disable-default-codeql.yml b/.github/workflows/disable-default-codeql.yml index 77f55c5f5..99b3bfaa1 100644 --- a/.github/workflows/disable-default-codeql.yml +++ b/.github/workflows/disable-default-codeql.yml @@ -10,7 +10,7 @@ concurrency: jobs: disable-default: name: Disable Default CodeQL Configuration - runs-on: ubuntu-latest + runs-on: ${{ vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }} permissions: security-events: write actions: read