Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 43 additions & 18 deletions .github/workflows/fly-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
simonsmallchua marked this conversation as resolved.
- 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@39d1b1a90b3dd4f04cd9e61ee11e097b9028e9ae # 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@39d1b1a90b3dd4f04cd9e61ee11e097b9028e9ae # 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
Expand Down
68 changes: 43 additions & 25 deletions .github/workflows/review-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -462,54 +462,72 @@ 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
Comment thread
simonsmallchua marked this conversation as resolved.
- uses: ./.github/actions/fly-setup
with:
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:<tag>. 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@39d1b1a90b3dd4f04cd9e61ee11e097b9028e9ae # 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
with:
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@39d1b1a90b3dd4f04cd9e61ee11e097b9028e9ae # 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
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 11 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions Dockerfile.analysis
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading