Skip to content

Commit f775fe0

Browse files
authored
improvement(ci): bound docker layer caches and right-size ten runners (#7210)
The Blacksmith sticky disks backing our docker layer caches had no eviction policy. setup-docker-builder skips pruning entirely unless max-cache-size-mb is set, and BuildKit's own GC is time-based only (8 days unused), so on a repo that builds this often nothing ever aged out: app.Dockerfile/linux-amd64 hit 351 GB within a day of being created, and realtime — an image under 300 MB — sat at 249 GB. Layer caches alone were 920 GB across ten disks. Cap them per image via a cache_mb matrix field, alongside the bs_runner field that already encodes per-image sizing. The app image keeps 100 GB (several generations over its working set of ~34 layers plus monorepo apt/bun cache mounts); everything else takes the 25 GB default, which is still 4x the tightest working set in the matrix (pii, whose spaCy models are ~2.2 GB). The fallback lives in the composite action rather than an input default, because an unset matrix key arrives as the empty string and would bypass a default — silently restoring unbounded growth on any row that forgot the field. Runner sizes follow measured CPU and memory percentiles over 30 days: - CodeQL splits per language. javascript-typescript peaks at 19.5 GB so it stays on 8 vCPU; actions peaks at 1.3 GB and averages 22% CPU over a 39s median run, and drops to 4 vCPU. - The pii and realtime image builds drop to 2 vCPU. Both already ran on 8 vCPU earlier in the window, so the 8->4 step is measured, not modelled: realtime went 52s -> 51s and pii 24s -> 27s. - Five desktop release jobs drop to 2 vCPU. They peak under 0.4 GB and finish in 4-13s. None of these sit on their group's critical path — each has 190-275s of slack behind an app build that dominates it — so wall-clock is unchanged. Those five desktop jobs also hardcoded a Blacksmith label with no CI_PROVIDER fallback, against the invariant stated at the top of ci.yml. In GitHub break-glass mode they would have sat in `queued` forever; they now fall back like every other job. Left alone deliberately: the 16 vCPU app builds (memory-bound, and 16 vCPU measured 2.1x faster and 6% cheaper than 8 vCPU), Lint and Test (CPU-bound, 62% of the run above 80%), and the push-path Build App (build-amd64 has no needs:, so it is what stops a migration applying for a build that cannot ship).
1 parent 498cdb6 commit f775fe0

3 files changed

Lines changed: 53 additions & 11 deletions

File tree

.github/actions/docker-build/action.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ inputs:
1919
tags:
2020
description: Comma-separated list of tags to push.
2121
required: true
22+
max-cache-size-mb:
23+
description: >-
24+
Layer cache to retain after the post-job prune, in MB. Must stay above one
25+
build's working set (base + dependency layers + RUN --mount=type=cache
26+
dirs) or every build evicts what the next one needs. Falls back to the
27+
small-image default below when empty.
28+
required: false
2229

2330
# Registry logins must precede this action. provenance/sbom stay off: attestation
2431
# manifests break `imagetools create` retagging in promote-images.
@@ -42,11 +49,24 @@ runs:
4249
PLATFORMS: ${{ inputs.platforms }}
4350
run: echo "value=${GITHUB_REPOSITORY##*/}/${FILE#./}/${PLATFORMS//\//-}" >> "$GITHUB_OUTPUT"
4451

52+
# max-cache-size-mb is what bounds the disk: BuildKit's default GC is
53+
# time-based only (layers unused for 8 days), and setup-docker-builder skips
54+
# pruning altogether when the value is empty. On a repo that builds this
55+
# often nothing ever ages out, so the disks grew without limit —
56+
# app.Dockerfile/linux-amd64 reached 351 GB inside a day, and realtime, whose
57+
# image is under 300 MB, sat at 249 GB. Sticky disks bill at ~$0.51/GB-month,
58+
# so that was real money for layers no build would ever read again.
59+
#
60+
# The fallback is here rather than an input `default:` because callers pass
61+
# this from a matrix field, and an unset matrix key arrives as the empty
62+
# string — which counts as "provided", so a `default:` would never apply and
63+
# a row that forgot the field would silently go back to unbounded growth.
4564
- name: Set up Blacksmith builder
4665
if: inputs.provider == '' || inputs.provider == 'blacksmith'
4766
uses: useblacksmith/setup-docker-builder@a5256a73e30f09e37e3eceb8ca36043d17621d24 # v2
4867
with:
4968
cache-key: ${{ steps.cache-key.outputs.value }}
69+
max-cache-size-mb: ${{ inputs.max-cache-size-mb || '25600' }}
5070

5171
- name: Build and push (Blacksmith)
5272
if: inputs.provider == '' || inputs.provider == 'blacksmith'

.github/workflows/ci.yml

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
# (/api/desktop/update) starts offering automatically.
7777
detect-desktop-changes:
7878
name: Detect Desktop Changes
79-
runs-on: blacksmith-4vcpu-ubuntu-2404
79+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
8080
timeout-minutes: 5
8181
if: github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/staging')
8282
outputs:
@@ -165,7 +165,15 @@ jobs:
165165
# build` ~260s). The same `next build` runs on 16 vCPU in the separate
166166
# Build App verification job, which does not gate anything; this one
167167
# was doing comparable work on half the cores.
168+
#
169+
# cache_mb is the layer cache the post-job prune retains, and it is the
170+
# only reason the sticky disks stay bounded — see docker-build's
171+
# action.yml. Rows that omit it take the small-image default there. The
172+
# app image overrides because it carries ~34 layers plus apt and bun
173+
# cache mounts for the whole monorepo; 100 GB is several builds' worth
174+
# of headroom over that working set.
168175
- dockerfile: ./docker/app.Dockerfile
176+
cache_mb: '102400'
169177
ecr_repo_secret: ECR_APP
170178
gh_runner: linux-x64-8-core
171179
bs_runner: blacksmith-16vcpu-ubuntu-2404
@@ -176,11 +184,11 @@ jobs:
176184
- dockerfile: ./docker/realtime.Dockerfile
177185
ecr_repo_secret: ECR_REALTIME
178186
gh_runner: ubuntu-latest
179-
bs_runner: blacksmith-4vcpu-ubuntu-2404
187+
bs_runner: blacksmith-2vcpu-ubuntu-2404
180188
- dockerfile: ./docker/pii.Dockerfile
181189
ecr_repo_secret: ECR_PII
182190
gh_runner: ubuntu-latest
183-
bs_runner: blacksmith-4vcpu-ubuntu-2404
191+
bs_runner: blacksmith-2vcpu-ubuntu-2404
184192
steps:
185193
- name: Checkout code
186194
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
@@ -214,6 +222,7 @@ jobs:
214222
file: ${{ matrix.dockerfile }}
215223
platforms: linux/amd64
216224
tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:dev
225+
max-cache-size-mb: ${{ matrix.cache_mb }}
217226

218227
# Dev: deploy Trigger.dev background tasks to the preview "dev-sim" branch.
219228
# Gated after migrate-dev for the same reason as build-dev — the new task
@@ -280,6 +289,7 @@ jobs:
280289
matrix:
281290
include:
282291
- dockerfile: ./docker/app.Dockerfile
292+
cache_mb: '102400'
283293
ghcr_image: ghcr.io/simstudioai/simstudio
284294
ecr_repo_secret: ECR_APP
285295
gh_runner: linux-x64-8-core
@@ -293,12 +303,12 @@ jobs:
293303
ghcr_image: ghcr.io/simstudioai/realtime
294304
ecr_repo_secret: ECR_REALTIME
295305
gh_runner: ubuntu-latest
296-
bs_runner: blacksmith-4vcpu-ubuntu-2404
306+
bs_runner: blacksmith-2vcpu-ubuntu-2404
297307
- dockerfile: ./docker/pii.Dockerfile
298308
ghcr_image: ghcr.io/simstudioai/pii
299309
ecr_repo_secret: ECR_PII
300310
gh_runner: ubuntu-latest
301-
bs_runner: blacksmith-4vcpu-ubuntu-2404
311+
bs_runner: blacksmith-2vcpu-ubuntu-2404
302312
# No ECR repo is provisioned for cron, so it publishes to GHCR only.
303313
# The tag step below omits the ECR tag when the repo name is empty.
304314
- dockerfile: ./docker/cron.Dockerfile
@@ -382,6 +392,7 @@ jobs:
382392
file: ${{ matrix.dockerfile }}
383393
platforms: linux/amd64
384394
tags: ${{ steps.meta.outputs.tags }}
395+
max-cache-size-mb: ${{ matrix.cache_mb }}
385396

386397
# Promote the sha-tagged ECR images to the deploy tags once tests and
387398
# migrations pass. Pushing the ECR latest/staging tag is what triggers
@@ -484,6 +495,7 @@ jobs:
484495
# hang a release in `queued` rather than fail a PR.
485496
include:
486497
- dockerfile: ./docker/app.Dockerfile
498+
cache_mb: '102400'
487499
image: ghcr.io/simstudioai/simstudio
488500
gh_runner: linux-arm64-8-core
489501
bs_runner: blacksmith-8vcpu-ubuntu-2404-arm
@@ -522,6 +534,7 @@ jobs:
522534
file: ${{ matrix.dockerfile }}
523535
platforms: linux/arm64
524536
tags: ${{ matrix.image }}:${{ github.sha }}-arm64
537+
max-cache-size-mb: ${{ matrix.cache_mb }}
525538

526539
# Publish all mutable GHCR tags (latest, latest-amd64/arm64, version tags)
527540
# and the multi-arch manifests from the immutable sha tags — only on main,
@@ -675,7 +688,7 @@ jobs:
675688
# Job-level `if:` cannot read the secrets context, hence the probe job.
676689
check-desktop-signing:
677690
name: Check Desktop Signing Secrets
678-
runs-on: blacksmith-4vcpu-ubuntu-2404
691+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
679692
timeout-minutes: 2
680693
needs: [detect-version, detect-desktop-changes]
681694
# !cancelled(): detect-desktop-changes is skipped on main (and
@@ -724,7 +737,7 @@ jobs:
724737
# remains testable end to end with a manual download.
725738
create-desktop-prerelease:
726739
name: Create Desktop Prerelease
727-
runs-on: blacksmith-4vcpu-ubuntu-2404
740+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
728741
timeout-minutes: 5
729742
needs: [detect-desktop-changes, check-desktop-signing]
730743
# Requires the signing probe to have actually succeeded (not just "not
@@ -813,7 +826,7 @@ jobs:
813826
# point of view.
814827
publish-desktop-prerelease:
815828
name: Publish Desktop Prerelease
816-
runs-on: blacksmith-4vcpu-ubuntu-2404
829+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
817830
timeout-minutes: 5
818831
needs: [create-desktop-prerelease, desktop-prerelease]
819832
permissions:
@@ -837,7 +850,7 @@ jobs:
837850
# are always garbage by this point — the current run's release is published.
838851
prune-desktop-prereleases:
839852
name: Prune Desktop Prereleases
840-
runs-on: blacksmith-4vcpu-ubuntu-2404
853+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
841854
timeout-minutes: 5
842855
needs: [publish-desktop-prerelease]
843856
permissions:

.github/workflows/codeql.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ permissions:
5454
jobs:
5555
analyze:
5656
name: Analyze ${{ matrix.language }}
57-
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
57+
# Sized per language, not per workflow. The two analyses are nothing alike:
58+
# javascript-typescript peaks at 19.5 GB (p95 over 3090 runs), so it needs
59+
# the 8 vCPU tier's 30.4 GB and would OOM on the 4 vCPU tier's 15.2 GB; the
60+
# actions analysis peaks at 1.3 GB and averages 22% CPU over a 39s median
61+
# run, so 8 vCPU was 4x more machine than it ever used.
62+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.bs_runner || 'ubuntu-latest' }}
5863
timeout-minutes: 60
5964
if: github.event.pull_request.draft != true
6065
permissions:
@@ -71,7 +76,11 @@ jobs:
7176
# entries default setup listed were one analysis, not three.
7277
# `javascript-typescript` is the documented spelling. Python dropped:
7378
# 7 files in the tree.
74-
language: [javascript-typescript, actions]
79+
include:
80+
- language: javascript-typescript
81+
bs_runner: blacksmith-8vcpu-ubuntu-2404
82+
- language: actions
83+
bs_runner: blacksmith-4vcpu-ubuntu-2404
7584

7685
steps:
7786
- name: Checkout repository

0 commit comments

Comments
 (0)