From 16c212b0c03763b5c406b3e53e6f96201aa566d3 Mon Sep 17 00:00:00 2001 From: Simon Smallchua <40650011+simonsmallchua@users.noreply.github.com> Date: Wed, 27 May 2026 11:51:19 +1000 Subject: [PATCH 1/4] Build deploy images on Blacksmith with cache --- .github/workflows/fly-deploy.yml | 61 ++++++++++++++++++++++---------- Dockerfile | 15 +++++--- Dockerfile.analysis | 9 +++-- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/.github/workflows/fly-deploy.yml b/.github/workflows/fly-deploy.yml index 190096fd..ed2f36ba 100644 --- a/.github/workflows/fly-deploy.yml +++ b/.github/workflows/fly-deploy.yml @@ -46,45 +46,70 @@ jobs: build-shared: name: Build hover image (API + worker) runs-on: blacksmith-4vcpu-ubuntu-2404 + env: + IMAGE_LABEL: deployment-${{ github.run_id }}-${{ github.run_attempt }} outputs: - image: ${{ steps.build.outputs.image }} + image: ${{ steps.image.outputs.image }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/fly-setup with: op-service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + # Build on the Blacksmith runner (not Fly's shared remote builder). + # useblacksmith/build-push-action mounts a sticky Docker layer cache, + # so the Go module/build-cache mounts in the Dockerfile persist across + # runs — incremental builds instead of a cold compile every deploy. + # This also removes the flaky remote-builder release step + # (`error releasing builder: deadline_exceeded`). + - name: Authenticate Docker to the Fly registry + run: flyctl auth docker + - name: Build and push hover image - id: build - env: - IMAGE_LABEL: deployment-${{ github.run_id }}-${{ github.run_attempt }} - run: | - flyctl deploy --build-only --push \ - --image-label "$IMAGE_LABEL" \ - --app hover + uses: useblacksmith/build-push-action@v1 + with: + context: . + file: Dockerfile + push: true + tags: registry.fly.io/hover:${{ env.IMAGE_LABEL }} + + - name: Expose image ref + id: image + run: echo "image=registry.fly.io/hover:${IMAGE_LABEL}" >> "$GITHUB_OUTPUT" build-analysis: name: Build hover-analysis image runs-on: blacksmith-4vcpu-ubuntu-2404 + env: + IMAGE_LABEL: deployment-${{ github.run_id }}-${{ github.run_attempt }} outputs: - image: ${{ steps.build.outputs.image }} + image: ${{ steps.image.outputs.image }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/fly-setup with: op-service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + # Built on the Blacksmith runner with a sticky layer cache — see the + # build-shared job. For this image the Chromium/Lighthouse apt+npm + # layers also cache, so they only rebuild when those lines change. + - name: Authenticate Docker to the Fly registry + run: flyctl auth docker + - name: Build and push hover-analysis image - id: build - env: - IMAGE_LABEL: deployment-${{ github.run_id }}-${{ github.run_attempt }} - run: | - flyctl deploy --build-only --push \ - --image-label "$IMAGE_LABEL" \ - --config fly.analysis.toml \ - --app hover-analysis - echo "image=registry.fly.io/hover-analysis:${IMAGE_LABEL}" >> "$GITHUB_OUTPUT" + uses: useblacksmith/build-push-action@v1 + with: + context: . + file: Dockerfile.analysis + push: true + tags: registry.fly.io/hover-analysis:${{ env.IMAGE_LABEL }} + + - name: Expose image ref + id: image + run: + echo "image=registry.fly.io/hover-analysis:${IMAGE_LABEL}" >> + "$GITHUB_OUTPUT" # ───────── Release phase ───────────────────────────────────────────── # release-api and release-analysis fan out in parallel as soon as their diff --git a/Dockerfile b/Dockerfile index 3eae1886..c4aa266b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,14 +9,21 @@ WORKDIR /app # Copy go mod and sum files COPY go.mod go.sum ./ -# Download dependencies -RUN go mod download +# Download dependencies. The module cache is mounted (not baked into the +# layer) so it persists across builds via BuildKit's cache, avoiding a +# full re-download whenever go.sum changes. +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download # Copy source code COPY . . -# Build both binaries in a single layer -RUN CGO_ENABLED=0 GOOS=linux go build -o main ./cmd/app/main.go && \ +# Build both binaries in a single layer. Mounting the module and build +# caches lets BuildKit reuse compiled dependency objects between builds, +# turning a cold ~minutes-long compile into an incremental one. +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 GOOS=linux go build -o main ./cmd/app/main.go && \ CGO_ENABLED=0 GOOS=linux go build -o worker ./cmd/worker/main.go # Final stage diff --git a/Dockerfile.analysis b/Dockerfile.analysis index e699095f..9e8b5c6c 100644 --- a/Dockerfile.analysis +++ b/Dockerfile.analysis @@ -23,11 +23,16 @@ FROM golang:1.26.3-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ -RUN go mod download +# Mounted module cache persists across builds (see Dockerfile for rationale). +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build -o analysis ./cmd/analysis/main.go +# Mounted module + build caches turn cold compiles into incremental ones. +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 GOOS=linux go build -o analysis ./cmd/analysis/main.go FROM node:20-slim AS runtime From 54db04d72f8890a9ec6fa6604eb8bfbe9535a54d Mon Sep 17 00:00:00 2001 From: Simon Smallchua <40650011+simonsmallchua@users.noreply.github.com> Date: Wed, 27 May 2026 19:45:24 +1000 Subject: [PATCH 2/4] Build review-app images on Blacksmith too --- .github/workflows/review-apps.yml | 68 +++++++++++++++++++------------ 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/.github/workflows/review-apps.yml b/.github/workflows/review-apps.yml index c31eebd5..09c36151 100644 --- a/.github/workflows/review-apps.yml +++ b/.github/workflows/review-apps.yml @@ -462,8 +462,11 @@ jobs: name: Build shared review image (API + worker) runs-on: blacksmith-4vcpu-ubuntu-2404 needs: [provision] + env: + IMAGE_LABEL: ${{ needs.provision.outputs.image_label }} + API_APP: ${{ needs.provision.outputs.api_app }} outputs: - image: ${{ steps.build.outputs.image }} + image: ${{ steps.image.outputs.image }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/fly-setup @@ -471,27 +474,35 @@ jobs: op-service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} validate-redis-url: "false" + # Build on the Blacksmith runner with a sticky layer cache, mirroring + # fly-deploy.yml. The worker release pulls the same image cross-app via + # FLY_API_TOKEN (org-scoped). + - name: Authenticate Docker to the Fly registry + run: flyctl auth docker + - name: Build and push shared image - id: build - env: - IMAGE_LABEL: ${{ needs.provision.outputs.image_label }} - API_APP: ${{ needs.provision.outputs.api_app }} - run: | - # --local-only builds on this runner; --push uploads to - # registry.fly.io/$API_APP:. Worker release pulls the same - # image cross-app via FLY_API_TOKEN (org-scoped). - flyctl deploy --build-only --push --local-only \ - --image-label "$IMAGE_LABEL" \ - --config .fly/review_apps.toml \ - --app "$API_APP" - echo "image=registry.fly.io/${API_APP}:${IMAGE_LABEL}" >> "$GITHUB_OUTPUT" + uses: useblacksmith/build-push-action@v1 + with: + context: . + file: Dockerfile + push: true + tags: registry.fly.io/${{ env.API_APP }}:${{ env.IMAGE_LABEL }} + + - name: Expose image ref + id: image + run: + echo "image=registry.fly.io/${API_APP}:${IMAGE_LABEL}" >> + "$GITHUB_OUTPUT" build-analysis: name: Build analysis review image runs-on: blacksmith-4vcpu-ubuntu-2404 needs: [provision] + env: + IMAGE_LABEL: ${{ needs.provision.outputs.image_label }} + ANALYSIS_APP: ${{ needs.provision.outputs.analysis_app }} outputs: - image: ${{ steps.build.outputs.image }} + image: ${{ steps.image.outputs.image }} steps: - uses: actions/checkout@v6 - uses: ./.github/actions/fly-setup @@ -499,17 +510,24 @@ jobs: op-service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} validate-redis-url: "false" + # Built on the Blacksmith runner with a sticky layer cache, mirroring + # fly-deploy.yml. + - name: Authenticate Docker to the Fly registry + run: flyctl auth docker + - name: Build and push analysis image - id: build - env: - IMAGE_LABEL: ${{ needs.provision.outputs.image_label }} - ANALYSIS_APP: ${{ needs.provision.outputs.analysis_app }} - run: | - flyctl deploy --build-only --push --local-only \ - --image-label "$IMAGE_LABEL" \ - --config .fly/review_apps.analysis.toml \ - --app "$ANALYSIS_APP" - echo "image=registry.fly.io/${ANALYSIS_APP}:${IMAGE_LABEL}" >> "$GITHUB_OUTPUT" + uses: useblacksmith/build-push-action@v1 + with: + context: . + file: Dockerfile.analysis + push: true + tags: registry.fly.io/${{ env.ANALYSIS_APP }}:${{ env.IMAGE_LABEL }} + + - name: Expose image ref + id: image + run: + echo "image=registry.fly.io/${ANALYSIS_APP}:${IMAGE_LABEL}" >> + "$GITHUB_OUTPUT" # ───────── Release phase ───────────────────────────────────────────── # release-api and release-analysis fan out as soon as their builds are From bf28a3c920902e033c6de418e1267cd9d8d185c4 Mon Sep 17 00:00:00 2001 From: Simon Smallchua <40650011+simonsmallchua@users.noreply.github.com> Date: Wed, 27 May 2026 20:03:07 +1000 Subject: [PATCH 3/4] Add changelog entry for build change --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 459ee897..adee9b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,13 @@ On merge, CI will: ## [Unreleased] -_Add unreleased changes here._ +### Changed + +- Deploy and review-app images now build on the Blacksmith CI runner via + `useblacksmith/build-push-action`, with BuildKit cache mounts for the Go + module and build caches, instead of Fly's shared remote builder. This removes + the recurring `error releasing builder: deadline_exceeded` flake and makes + dependency compiles incremental across runs, cutting build time substantially. ## Full changelog history From 5b97359fe79ada072d98dbb203ab2d3ce0dbed65 Mon Sep 17 00:00:00 2001 From: Simon Smallchua <40650011+simonsmallchua@users.noreply.github.com> Date: Wed, 27 May 2026 20:06:28 +1000 Subject: [PATCH 4/4] Pin build-push-action to commit SHA --- .github/workflows/fly-deploy.yml | 4 ++-- .github/workflows/review-apps.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/fly-deploy.yml b/.github/workflows/fly-deploy.yml index ed2f36ba..21727487 100644 --- a/.github/workflows/fly-deploy.yml +++ b/.github/workflows/fly-deploy.yml @@ -66,7 +66,7 @@ jobs: run: flyctl auth docker - name: Build and push hover image - uses: useblacksmith/build-push-action@v1 + uses: useblacksmith/build-push-action@39d1b1a90b3dd4f04cd9e61ee11e097b9028e9ae # v1 with: context: . file: Dockerfile @@ -98,7 +98,7 @@ jobs: run: flyctl auth docker - name: Build and push hover-analysis image - uses: useblacksmith/build-push-action@v1 + uses: useblacksmith/build-push-action@39d1b1a90b3dd4f04cd9e61ee11e097b9028e9ae # v1 with: context: . file: Dockerfile.analysis diff --git a/.github/workflows/review-apps.yml b/.github/workflows/review-apps.yml index 09c36151..8169b4d0 100644 --- a/.github/workflows/review-apps.yml +++ b/.github/workflows/review-apps.yml @@ -481,7 +481,7 @@ jobs: run: flyctl auth docker - name: Build and push shared image - uses: useblacksmith/build-push-action@v1 + uses: useblacksmith/build-push-action@39d1b1a90b3dd4f04cd9e61ee11e097b9028e9ae # v1 with: context: . file: Dockerfile @@ -516,7 +516,7 @@ jobs: run: flyctl auth docker - name: Build and push analysis image - uses: useblacksmith/build-push-action@v1 + uses: useblacksmith/build-push-action@39d1b1a90b3dd4f04cd9e61ee11e097b9028e9ae # v1 with: context: . file: Dockerfile.analysis