From f2440fbf24ff130403bdebaa3b33951a94cf7d7c Mon Sep 17 00:00:00 2001 From: julien Date: Mon, 4 May 2026 15:33:04 +0200 Subject: [PATCH] fix(ci): disable broken pnpm cache on actions/setup-node (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary `act_runner`'s built-in GitHub-Actions-cache server binds inside the runner container on the compose-defined `apf-portal-act-runners` bridge. Jobs spawned via the mounted Docker socket land on Docker's default `bridge` network and can't reach it. Every job opting into `cache: 'pnpm'` ate ~2 min `ETIMEDOUT` on restore + another ~2 min on save — across the 5 jobs, ~20 min wasted per CI run for zero cache hits. Drop `cache: 'pnpm'` everywhere. `pnpm install --frozen-lockfile` is fast on the warm store inside the job container, so removing the cache layer is a net gain today. The proper fix (cross-container networking / fixed-port cache binding) is documented in `infra/README.md` → "Cache server (deferred)" so it can be picked up as an isolated infra spike later. ## Test plan - [ ] CI run on this PR: every job's `Set up Node.js` step finishes in seconds (no ETIMEDOUT warning). - [ ] `Complete job` step also finishes promptly (no `reserveCache failed` warning). - [ ] Total wall-clock time for the run should drop by ~15-20 min vs. previous runs. --------- Co-authored-by: Julien Gautier Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/8 --- .gitea/workflows/ci.yml | 14 +++++++++----- infra/README.md | 13 +++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 80db345..e46aa76 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -2,6 +2,15 @@ # Thin YAML — orchestration lives in package.json scripts (ci:check, # ci:audit, ci:commits, ci:perf) and Nx targets. Any change to gate # behaviour belongs in those scripts, not in this file. +# +# `cache: 'pnpm'` is intentionally NOT enabled on actions/setup-node +# below: act_runner's built-in GitHub-Actions-cache server is bound on +# the runner container and is unreachable from job containers (which +# run on Docker's default network, not the runners' compose network). +# Each request burns a ~2 min ETIMEDOUT on restore + another on save, +# for zero hit rate. Once the runner network/cache config is fixed +# (tracked in infra/README.md "Cache server"), re-add `cache: 'pnpm'` +# here. name: CI @@ -41,7 +50,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'pnpm' - run: pnpm install --frozen-lockfile - run: pnpm ci:check @@ -53,7 +61,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'pnpm' - run: pnpm install --frozen-lockfile - run: pnpm ci:audit # Dependency vulnerability scan (binary tool, invoked via official @@ -82,7 +89,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'pnpm' - run: pnpm install --frozen-lockfile - run: COMMIT_LINT_FROM=origin/main pnpm ci:commits @@ -100,7 +106,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'pnpm' - run: pnpm install --frozen-lockfile - run: pnpm ci:perf - uses: actions/upload-artifact@v4 @@ -118,7 +123,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' - cache: 'pnpm' - run: pnpm install --frozen-lockfile # Placeholder until the e2e a11y suite (axe-core via Playwright, # per ADR-0016) is wired with the first real screens. The job diff --git a/infra/README.md b/infra/README.md index c0c3357..ffa6410 100644 --- a/infra/README.md +++ b/infra/README.md @@ -62,6 +62,19 @@ The compose mounts `/var/run/docker.sock` into each runner so jobs can spawn con - **No host filesystem mounts beyond the docker socket:** the compose intentionally does not mount `/`, `/etc`, or any project source. Workflows that need data on the host must do so via Docker volumes. - **Future hardening (out of scope of v1):** migrate to **rootless Docker** on the runner host, or to a **DinD (Docker-in-Docker) sidecar** so the runner cannot escape into the host daemon. Decided when the org's RSSI confirms the security posture, or when the runner host is shared with anything else of value. +### Cache server (deferred) + +`act_runner` ships a built-in GitHub-Actions-cache-compatible server, used by `actions/setup-node@v4` (`cache: 'pnpm'`), `actions/cache`, and similar. In the current setup it does **not** work because the runner containers and the job containers live on different Docker networks: the runner is on the compose-defined `apf-portal-act-runners` bridge, while jobs spawned via the mounted `/var/run/docker.sock` come up on Docker's default `bridge` network. The cache server binds inside the runner container on a random port — unreachable from the job. The symptom is a ~2 min `ETIMEDOUT` at the start (restore) and end (save) of every job that opts into caching. + +For now `cache: 'pnpm'` is left **disabled** in `.gitea/workflows/ci.yml`. `pnpm install --frozen-lockfile` is fast enough on a warm pnpm store inside the job image (~30–60 s cold) that the cache layer adds no value as long as it doesn't actually transfer anything. + +When this is reactivated, the right fix is one of: + +- attach jobs to the same Docker network as the runners (`runner.container.network` in act_runner's `config.yaml`, then advertise the cache `host` on the bridge IP); or +- bind act_runner's cache server on a fixed `host_port` reachable from any container (host gateway IP), and set `ACTIONS_CACHE_URL` accordingly. + +Either path needs a single-runner spike before being rolled out to the three. + ### Image pinning The compose pins `gitea/act_runner:0.2.13`. Update the pin deliberately, not via `:latest`: