fix(ci): inline docker buildx to stop concurrent-build cache contention

Two failing runs across refaire (run 1709) and rememed (run 1624)
both crash at the same step (`Set up Docker Buildx`) with a node error
on dist/index.js line 1, AND both reference the same act cache dir:
/root/.cache/act/6a647958c11e138a6cfcaf32d2b372bc8e0c97871d617bfb441d003d505b77cf

act keys remote-action cache entries by repo URL alone — pinning to
`@v3.10.0` doesn't help, every game that uses `docker/setup-buildx-
action` lands in the same dir. When you push N games at once on
home-runner-1, the act-runner does parallel `git clone` ops into that
shared dir; the loser's pull aborts ("worktree contains unstaged
changes") and leaves dist/ half-written, so the next job's `node
dist/index.js` throws on line 1 → step fails. That's the entire flake.

Fix: drop the two remote actions that were racing — setup-buildx-action
and build-push-action — and replace them with inline `docker buildx
create` + `docker buildx build --push` shell. Nothing is fetched from
GitHub at runtime, no cache dir is shared, the failure mode disappears.

Same image, same tags, same registry mirror, same cache-from/cache-to
shape, same secret-files mount (`--secret id=...,src=...`). Each job
gets a uniquely-named builder (`builder-<game>-<api-core|web>`) and a
teardown step so the runner host's docker state doesn't accumulate
abandoned builders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 12:23:35 +02:00
parent 6c46ca98a7
commit 0c5f9a347a
+98 -60
View File
@@ -20,12 +20,17 @@ name: Build & Deploy played game (reusable)
# - STEP_CA_PROVISIONER_PASSWORD — for the cert-init container # - STEP_CA_PROVISIONER_PASSWORD — for the cert-init container
# #
# Notes on reliability: # Notes on reliability:
# - All remote actions are pinned to immutable patch tags so the act-runner # - act keys its remote-action cache by repo URL alone (not by full
# action cache hash is stable run-to-run. The cluster of "Cannot find # ref), so every concurrent game build on home-runner-1 shares the
# module .../dist/index.js" failures on home-runner-1 was act re-using a # same /root/.cache/act/<hash> dir for setup-buildx-action +
# partial cache dir for a moving tag (`@v3`); pinning kills that mode. # build-push-action. Two builds racing on that dir corrupt the
# - Registry login is an inline shell step instead of docker/login-action. # checked-out tree ("worktree contains unstaged changes") and the
# One fewer remote-action download = one fewer failure point per job. # next read of dist/index.js throws → step exits 1. Pinning to
# patch tags didn't help because the cache key ignores the ref.
# Fix: do buildx setup + build via inline `docker buildx ...`
# shell, so nothing needs to be cloned from GitHub at runtime.
# - Registry login is also an inline shell step. One fewer remote-
# action download = one fewer failure point per job.
on: on:
workflow_call: workflow_call:
@@ -42,14 +47,25 @@ jobs:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.2.2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0 # Inline replacement for docker/setup-buildx-action. The job
with: # name (BUILDER) is unique-per-game-per-job so concurrent
config-inline: | # builds across games don't fight over a shared `default`
[registry."docker.io"] # builder either. Stop+rm in case a previous run on this
mirrors = ["192.168.1.52:5000"] # runner left one behind.
[registry."192.168.1.52:5000"] env:
http = true BUILDER: builder-${{ inputs.game-id }}-api-core
insecure = true run: |
cat > /tmp/buildkitd.toml <<'EOF'
[registry."docker.io"]
mirrors = ["192.168.1.52:5000"]
[registry."192.168.1.52:5000"]
http = true
insecure = true
EOF
docker buildx rm "$BUILDER" 2>/dev/null || true
docker buildx create --name "$BUILDER" --use --bootstrap \
--driver docker-container \
--config /tmp/buildkitd.toml
- name: Log in to Gitea registry - name: Log in to Gitea registry
env: env:
@@ -67,28 +83,33 @@ jobs:
printenv NPMRC > /tmp/.npmrc printenv NPMRC > /tmp/.npmrc
- name: Build & push api-core - name: Build & push api-core
uses: docker/build-push-action@v6.16.0 # Inline replacement for docker/build-push-action. Same
with: # tags / secrets / cache shape as before; mode=min on
context: . # cache-to to skip re-exporting the bun-install cache
file: ./api/core/Dockerfile # mount on every push (see the original comment for why).
push: true env:
tags: | BUILDER: builder-${{ inputs.game-id }}-api-core
git.unom.io/${{ gitea.repository }}/api-core:latest IMAGE: git.unom.io/${{ gitea.repository }}/api-core
git.unom.io/${{ gitea.repository }}/api-core:${{ gitea.sha }} SHA: ${{ gitea.sha }}
secret-files: | run: |
env=/tmp/.env.prod docker buildx build \
npmrc=/tmp/.npmrc --builder "$BUILDER" \
cache-from: | --push \
type=registry,ref=git.unom.io/${{ gitea.repository }}/api-core:cache --file ./api/core/Dockerfile \
# mode=min: export only the final stage's layers. mode=max --tag "$IMAGE:latest" \
# was re-uploading the bun-install cache mount (~4060s) to --tag "$IMAGE:$SHA" \
# the Gitea OCI registry on every push, even no-op deploys. --secret id=env,src=/tmp/.env.prod \
# Trade-off: a cold buildkitd will re-run `bun install` from --secret id=npmrc,src=/tmp/.npmrc \
# scratch on the installer stage instead of importing it --cache-from "type=registry,ref=$IMAGE:cache" \
# from registry cache — a few-second tax in exchange for --cache-to "type=registry,ref=$IMAGE:cache,mode=min" \
# not paying the export tax on every run. .
cache-to: |
type=registry,ref=git.unom.io/${{ gitea.repository }}/api-core:cache,mode=min - name: Tear down builder
if: always()
env:
BUILDER: builder-${{ inputs.game-id }}-api-core
run: |
docker buildx rm "$BUILDER" 2>/dev/null || true
deploy-api-core: deploy-api-core:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -165,14 +186,22 @@ jobs:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.2.2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0 # See the build-api-core step of the same name above for why
with: # this is inline `docker buildx` rather than docker/setup-buildx-action.
config-inline: | env:
[registry."docker.io"] BUILDER: builder-${{ inputs.game-id }}-web
mirrors = ["192.168.1.52:5000"] run: |
[registry."192.168.1.52:5000"] cat > /tmp/buildkitd.toml <<'EOF'
http = true [registry."docker.io"]
insecure = true mirrors = ["192.168.1.52:5000"]
[registry."192.168.1.52:5000"]
http = true
insecure = true
EOF
docker buildx rm "$BUILDER" 2>/dev/null || true
docker buildx create --name "$BUILDER" --use --bootstrap \
--driver docker-container \
--config /tmp/buildkitd.toml
- name: Log in to Gitea registry - name: Log in to Gitea registry
env: env:
@@ -190,22 +219,31 @@ jobs:
printenv NPMRC > /tmp/.npmrc printenv NPMRC > /tmp/.npmrc
- name: Build & push web - name: Build & push web
uses: docker/build-push-action@v6.16.0 # Inline equivalent of docker/build-push-action. See the
with: # api-core cache-to block above for the mode=min rationale.
context: . env:
file: ./apps/web/Dockerfile BUILDER: builder-${{ inputs.game-id }}-web
push: true IMAGE: git.unom.io/${{ gitea.repository }}/web
tags: | SHA: ${{ gitea.sha }}
git.unom.io/${{ gitea.repository }}/web:latest run: |
git.unom.io/${{ gitea.repository }}/web:${{ gitea.sha }} docker buildx build \
secret-files: | --builder "$BUILDER" \
env=/tmp/.env.prod --push \
npmrc=/tmp/.npmrc --file ./apps/web/Dockerfile \
cache-from: | --tag "$IMAGE:latest" \
type=registry,ref=git.unom.io/${{ gitea.repository }}/web:cache --tag "$IMAGE:$SHA" \
# See the api-core cache-to block above for the mode=min rationale. --secret id=env,src=/tmp/.env.prod \
cache-to: | --secret id=npmrc,src=/tmp/.npmrc \
type=registry,ref=git.unom.io/${{ gitea.repository }}/web:cache,mode=min --cache-from "type=registry,ref=$IMAGE:cache" \
--cache-to "type=registry,ref=$IMAGE:cache,mode=min" \
.
- name: Tear down builder
if: always()
env:
BUILDER: builder-${{ inputs.game-id }}-web
run: |
docker buildx rm "$BUILDER" 2>/dev/null || true
deploy-web: deploy-web:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04