From 7c0db52c35cdc9175a30c72fc7304f9c3dff42b4 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 21 Jul 2026 13:15:47 -0700 Subject: [PATCH 1/4] fix(ci): make GitHub-hosted fallback actually build and test --- .github/actions/docker-build/action.yml | 13 +++++++++++++ .github/workflows/ci.yml | 12 +++++++++--- .github/workflows/test-build.yml | 8 ++++++++ apps/sim/package.json | 2 +- docker/app.Dockerfile | 7 +++++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/.github/actions/docker-build/action.yml b/.github/actions/docker-build/action.yml index 600f4a45582..0707c59ce34 100644 --- a/.github/actions/docker-build/action.yml +++ b/.github/actions/docker-build/action.yml @@ -19,6 +19,10 @@ inputs: tags: description: Comma-separated list of tags to push. required: true + build-args: + description: Newline-separated build args forwarded to the builder. + required: false + default: '' # Registry logins must already have run in the calling job — both backends read # the same ~/.docker/config.json. provenance/sbom stay off everywhere: the @@ -40,6 +44,7 @@ runs: platforms: ${{ inputs.platforms }} push: true tags: ${{ inputs.tags }} + build-args: ${{ inputs.build-args }} provenance: false sbom: false @@ -51,6 +56,11 @@ runs: # the GitHub equivalent (cache-to: type=gha) shares the same 10 GB repo # cache quota as the bun/node_modules/turbo mounts, and four images of # layers would evict them. Fallback builds are cold by design. + # NEXT_BUILD_HEAP_MB is trimmed from the 8192 default here because these + # runners have 16 GB total and `next build` was getting OOM-killed (exit + # 137) partway through. Only app.Dockerfile declares the arg; the other + # images ignore it harmlessly, so it is set unconditionally rather than + # threaded per-image through every call site. - name: Build and push (GitHub) if: inputs.provider != '' && inputs.provider != 'blacksmith' uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -60,5 +70,8 @@ runs: platforms: ${{ inputs.platforms }} push: true tags: ${{ inputs.tags }} + build-args: | + NEXT_BUILD_HEAP_MB=4096 + ${{ inputs.build-args }} provenance: false sbom: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9da49fcfce6..364bc1e2d0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,9 +21,15 @@ name: CI # branch would hard-fail. # # GitHub mode is a break-glass fallback, not a peer. Public-repo ubuntu-latest is -# 4 vCPU with no free 8-core tier, the 10 GB repo cache quota will thrash across -# the bun/node_modules/turbo mounts, and image builds start from cold layers — so -# expect slower runs, not different results. Blacksmith-only actions are reached +# 4 vCPU / 16 GB with no free 8-core tier, the 10 GB repo cache quota will thrash +# across the bun/node_modules/turbo mounts, and image builds start from cold +# layers. The first live flip (2026-07-21) proved it is not merely slower: the +# app image OOM-killed `next build`, and one test failed because it shells out to +# `rg`, which Blacksmith's image ships and GitHub's does not. Both are addressed +# now — ripgrep is installed explicitly in test-build.yml, and the GitHub path +# trims NEXT_BUILD_HEAP_MB — but the app image build stays the tight spot here +# and is the first thing to check if a fallback run fails. Blacksmith-only +# actions are reached # exclusively through .github/actions/cache-mount and .github/actions/docker-build, # which branch internally; a bare useblacksmith/* step added anywhere else will # hard-fail in GitHub mode rather than silently degrade. diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 6e49b97c220..ccc7d593016 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -153,6 +153,14 @@ jobs: - name: Type-check realtime server run: bunx turbo run type-check --filter=@sim/realtime + # cloud-review-tools.test.ts runs the real helper script, which shells out + # to `rg`. In production that script runs inside the E2B sandbox, whose + # template apt-installs ripgrep — but the test executes it on the runner, + # so the runner needs it too. Blacksmith's image ships rg and GitHub's does + # not, which made this an invisible dependency on the runner provider. + - name: Install ripgrep + run: command -v rg || (sudo apt-get update && sudo apt-get install -y ripgrep) + - name: Run tests with coverage env: NODE_OPTIONS: '--no-warnings --max-old-space-size=8192' diff --git a/apps/sim/package.json b/apps/sim/package.json index 1a41e053ea3..8fcb96f5489 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -17,7 +17,7 @@ "load:workflow:baseline": "BASE_URL=${BASE_URL:-http://localhost:3000} WARMUP_DURATION=${WARMUP_DURATION:-10} WARMUP_RATE=${WARMUP_RATE:-2} PEAK_RATE=${PEAK_RATE:-8} HOLD_DURATION=${HOLD_DURATION:-20} bunx artillery run scripts/load/workflow-concurrency.yml", "load:workflow:waves": "BASE_URL=${BASE_URL:-http://localhost:3000} WAVE_ONE_DURATION=${WAVE_ONE_DURATION:-10} WAVE_ONE_RATE=${WAVE_ONE_RATE:-6} QUIET_DURATION=${QUIET_DURATION:-5} WAVE_TWO_DURATION=${WAVE_TWO_DURATION:-15} WAVE_TWO_RATE=${WAVE_TWO_RATE:-8} WAVE_THREE_DURATION=${WAVE_THREE_DURATION:-20} WAVE_THREE_RATE=${WAVE_THREE_RATE:-10} bunx artillery run scripts/load/workflow-waves.yml", "load:workflow:isolation": "BASE_URL=${BASE_URL:-http://localhost:3000} ISOLATION_DURATION=${ISOLATION_DURATION:-30} TOTAL_RATE=${TOTAL_RATE:-9} WORKSPACE_A_WEIGHT=${WORKSPACE_A_WEIGHT:-8} WORKSPACE_B_WEIGHT=${WORKSPACE_B_WEIGHT:-1} bunx artillery run scripts/load/workflow-isolation.yml", - "build": "bun run build:sandbox-bundles && NODE_OPTIONS='--max-old-space-size=8192' next build", + "build": "bun run build:sandbox-bundles && NODE_OPTIONS=--max-old-space-size=${NEXT_BUILD_HEAP_MB:-8192} next build", "build:sandbox-bundles": "bun run ./lib/execution/sandbox/bundles/build.ts", "start": "next start", "prepare": "cd ../.. && bun husky", diff --git a/docker/app.Dockerfile b/docker/app.Dockerfile index c0cc26de7d9..50bc38542ef 100644 --- a/docker/app.Dockerfile +++ b/docker/app.Dockerfile @@ -76,6 +76,13 @@ ENV DATABASE_URL=${DATABASE_URL} ARG NEXT_PUBLIC_APP_URL="http://localhost:3000" ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} +# V8 old-space ceiling for `next build`. Turbopack's own allocations are native +# and sit outside this budget, so the ceiling has to leave room for them plus +# the daemon and BuildKit — on a 16 GB runner the default 8192 is enough to get +# the process OOM-killed. Only the heap size changes; build output does not. +ARG NEXT_BUILD_HEAP_MB=8192 +ENV NEXT_BUILD_HEAP_MB=${NEXT_BUILD_HEAP_MB} + # Per-platform cache id keeps arm64/amd64 SWC artifacts isolated. RUN --mount=type=cache,id=next-cache-${TARGETPLATFORM},target=/app/apps/sim/.next/cache \ --mount=type=cache,id=turbo-cache-${TARGETPLATFORM},target=/app/.turbo \ From 39ef8b2de75a98123dec8117c4289051addee2af Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 21 Jul 2026 14:37:18 -0700 Subject: [PATCH 2/4] fix(ci): drop unused build-args passthrough so the heap ceiling can't be overridden --- .github/actions/docker-build/action.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/actions/docker-build/action.yml b/.github/actions/docker-build/action.yml index 0707c59ce34..991df399829 100644 --- a/.github/actions/docker-build/action.yml +++ b/.github/actions/docker-build/action.yml @@ -19,10 +19,6 @@ inputs: tags: description: Comma-separated list of tags to push. required: true - build-args: - description: Newline-separated build args forwarded to the builder. - required: false - default: '' # Registry logins must already have run in the calling job — both backends read # the same ~/.docker/config.json. provenance/sbom stay off everywhere: the @@ -44,7 +40,6 @@ runs: platforms: ${{ inputs.platforms }} push: true tags: ${{ inputs.tags }} - build-args: ${{ inputs.build-args }} provenance: false sbom: false @@ -60,7 +55,9 @@ runs: # runners have 16 GB total and `next build` was getting OOM-killed (exit # 137) partway through. Only app.Dockerfile declares the arg; the other # images ignore it harmlessly, so it is set unconditionally rather than - # threaded per-image through every call site. + # threaded per-image through every call site. This is deliberately the only + # build arg the action passes — a general passthrough input would let a + # caller re-raise the ceiling and silently restore the OOM. - name: Build and push (GitHub) if: inputs.provider != '' && inputs.provider != 'blacksmith' uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -70,8 +67,6 @@ runs: platforms: ${{ inputs.platforms }} push: true tags: ${{ inputs.tags }} - build-args: | - NEXT_BUILD_HEAP_MB=4096 - ${{ inputs.build-args }} + build-args: NEXT_BUILD_HEAP_MB=4096 provenance: false sbom: false From 7867d867d1fb56b78e54d4ab0278659389412723 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 21 Jul 2026 14:41:25 -0700 Subject: [PATCH 3/4] chore(ci): trim comments to the non-obvious constraints --- .github/actions/cache-mount/action.yml | 18 +++++-------- .github/actions/docker-build/action.yml | 22 +++++---------- .github/workflows/ci.yml | 36 ++++++------------------- .github/workflows/test-build.yml | 12 +++------ docker/app.Dockerfile | 6 ++--- 5 files changed, 27 insertions(+), 67 deletions(-) diff --git a/.github/actions/cache-mount/action.yml b/.github/actions/cache-mount/action.yml index cf4d0276507..df0de0b106c 100644 --- a/.github/actions/cache-mount/action.yml +++ b/.github/actions/cache-mount/action.yml @@ -3,20 +3,18 @@ description: Mount a build cache directory using Blacksmith sticky disks, or the inputs: provider: - description: Empty or 'blacksmith' selects sticky disks. Any other value selects actions/cache. Must stay byte-identical to the runs-on predicate in the calling workflow. + description: Empty or 'blacksmith' selects sticky disks; anything else selects actions/cache. required: false default: '' key: - description: Cache key. Shared by both backends, so callers keep one key regardless of provider. + description: Cache key, shared by both backends. required: true path: - description: Filesystem path the cache is mounted at. + description: Path to mount. required: true -# The two conditions are exact complements of the runs-on expression the caller -# uses. They must be inverted together: a job placed on ubuntu-latest that then -# took the sticky-disk branch would hard-fail, since stickydisk hot-mounts an -# ext4 volume from Blacksmith's storage agents and cannot run anywhere else. +# Predicates must stay the exact complement of the caller's runs-on — stickydisk +# only runs on Blacksmith. runs: using: composite steps: @@ -27,10 +25,8 @@ runs: key: ${{ inputs.key }} path: ${{ inputs.path }} - # Sticky disks always re-commit the mounted path, so the cache rolls forward - # every run. actions/cache skips its save step on an exact key hit, which - # would freeze the cache at whatever the first run wrote — the run-scoped - # suffix plus a prefix restore-key reproduces the rolling behaviour. + # run_id suffix + prefix restore-key: actions/cache skips save on an exact hit, + # which would freeze the cache at the first run's contents. - name: Restore and save cache if: inputs.provider != '' && inputs.provider != 'blacksmith' uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 diff --git a/.github/actions/docker-build/action.yml b/.github/actions/docker-build/action.yml index 991df399829..b22f50449d4 100644 --- a/.github/actions/docker-build/action.yml +++ b/.github/actions/docker-build/action.yml @@ -3,7 +3,7 @@ description: Set up a buildx builder and build/push an image, using Blacksmith's inputs: provider: - description: Empty or 'blacksmith' selects Blacksmith's builder. Any other value selects the upstream docker/* actions. Must stay byte-identical to the runs-on predicate in the calling workflow. + description: Empty or 'blacksmith' selects Blacksmith's builder; anything else selects the docker/* actions. required: false default: '' context: @@ -20,10 +20,8 @@ inputs: description: Comma-separated list of tags to push. required: true -# Registry logins must already have run in the calling job — both backends read -# the same ~/.docker/config.json. provenance/sbom stay off everywhere: the -# attestation manifests they add break `imagetools create` retagging in the -# promote-images and create-ghcr-manifests jobs. +# Registry logins must precede this action. provenance/sbom stay off: attestation +# manifests break `imagetools create` retagging in promote-images. runs: using: composite steps: @@ -47,17 +45,9 @@ runs: if: inputs.provider != '' && inputs.provider != 'blacksmith' uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 - # No layer cache here. Blacksmith caches layers on the builder itself; - # the GitHub equivalent (cache-to: type=gha) shares the same 10 GB repo - # cache quota as the bun/node_modules/turbo mounts, and four images of - # layers would evict them. Fallback builds are cold by design. - # NEXT_BUILD_HEAP_MB is trimmed from the 8192 default here because these - # runners have 16 GB total and `next build` was getting OOM-killed (exit - # 137) partway through. Only app.Dockerfile declares the arg; the other - # images ignore it harmlessly, so it is set unconditionally rather than - # threaded per-image through every call site. This is deliberately the only - # build arg the action passes — a general passthrough input would let a - # caller re-raise the ceiling and silently restore the OOM. + # No cache-to: type=gha — it shares the 10 GB repo quota with the cache mounts. + # Heap trimmed from the 8192 default: 16 GB runners OOM-killed next build (exit + # 137). Hardcoded, not an input, so a caller can't re-raise it. - name: Build and push (GitHub) if: inputs.provider != '' && inputs.provider != 'blacksmith' uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 364bc1e2d0b..162662d9b90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,38 +1,18 @@ name: CI -# Runner provider toggle. Every job here and in the reusable workflows reads the -# CI_PROVIDER repository variable: +# Runner provider toggle, read from the CI_PROVIDER repo variable: # # gh variable set CI_PROVIDER --body github # fall back to GitHub-hosted # gh variable delete CI_PROVIDER # back to Blacksmith (default) # -# It is a repo variable rather than a committed value on purpose: during a -# Blacksmith outage there is no working CI to merge a switchover commit through, -# and the variable takes effect on the next run with nothing to land. +# A repo variable, not a committed value: during a Blacksmith outage there is no +# working CI to merge a switchover through. Only unset/'blacksmith' selects +# Blacksmith; anything unrecognized selects GitHub so a typo can't queue jobs +# against the provider you're escaping. Every runs-on and both composite actions +# share this predicate and must change together. # -# Only unset or the exact value 'blacksmith' selects Blacksmith; everything else -# selects GitHub-hosted. The unrecognized case resolves toward GitHub on purpose, -# because that is the provider that is always available: a typo'd or whitespace- -# padded break-glass value then yields slow-but-working CI instead of jobs queued -# forever against the provider you were trying to escape. `==` is case-insensitive -# in Actions expressions, so 'GitHub' and 'Blacksmith' both work. Every runs-on -# expression and both composite actions use this same predicate — they must be -# changed together, since a job on ubuntu-latest that took a Blacksmith-only -# branch would hard-fail. -# -# GitHub mode is a break-glass fallback, not a peer. Public-repo ubuntu-latest is -# 4 vCPU / 16 GB with no free 8-core tier, the 10 GB repo cache quota will thrash -# across the bun/node_modules/turbo mounts, and image builds start from cold -# layers. The first live flip (2026-07-21) proved it is not merely slower: the -# app image OOM-killed `next build`, and one test failed because it shells out to -# `rg`, which Blacksmith's image ships and GitHub's does not. Both are addressed -# now — ripgrep is installed explicitly in test-build.yml, and the GitHub path -# trims NEXT_BUILD_HEAP_MB — but the app image build stays the tight spot here -# and is the first thing to check if a fallback run fails. Blacksmith-only -# actions are reached -# exclusively through .github/actions/cache-mount and .github/actions/docker-build, -# which branch internally; a bare useblacksmith/* step added anywhere else will -# hard-fail in GitHub mode rather than silently degrade. +# GitHub mode is break-glass, not a peer — 4 vCPU/16 GB, cold layers, and the app +# image build is the tight spot (it OOM-killed next build on the first live flip). on: push: diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index ccc7d593016..d96fd4987ad 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -30,7 +30,7 @@ jobs: # Cache keys are scoped by event name, and fork PRs get their own # namespace on top: untrusted fork runs must never share a cache with # push runs (whose caches feed production image builds) or with trusted - # internal-PR runs. The scoping holds on both backends. + # internal-PR runs. - name: Mount Bun cache uses: ./.github/actions/cache-mount with: @@ -153,11 +153,8 @@ jobs: - name: Type-check realtime server run: bunx turbo run type-check --filter=@sim/realtime - # cloud-review-tools.test.ts runs the real helper script, which shells out - # to `rg`. In production that script runs inside the E2B sandbox, whose - # template apt-installs ripgrep — but the test executes it on the runner, - # so the runner needs it too. Blacksmith's image ships rg and GitHub's does - # not, which made this an invisible dependency on the runner provider. + # cloud-review-tools.test.ts runs the real helper on the runner, which shells + # out to rg. Blacksmith's image ships it, GitHub's doesn't. - name: Install ripgrep run: command -v rg || (sudo apt-get update && sudo apt-get install -y ripgrep) @@ -196,8 +193,7 @@ jobs: # with test-build (identical content from the same lockfile — LWW loss is # harmless), but the Turbo cache gets its own key: with a shared key, only # the last committer's new entries survive each run, so the test and build - # Turbo entries would evict each other nondeterministically. The same split - # is correct on actions/cache, which is likewise last-writer-wins per key. + # Turbo entries would evict each other nondeterministically. build: name: Build App runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }} diff --git a/docker/app.Dockerfile b/docker/app.Dockerfile index 50bc38542ef..f42472f2532 100644 --- a/docker/app.Dockerfile +++ b/docker/app.Dockerfile @@ -76,10 +76,8 @@ ENV DATABASE_URL=${DATABASE_URL} ARG NEXT_PUBLIC_APP_URL="http://localhost:3000" ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} -# V8 old-space ceiling for `next build`. Turbopack's own allocations are native -# and sit outside this budget, so the ceiling has to leave room for them plus -# the daemon and BuildKit — on a 16 GB runner the default 8192 is enough to get -# the process OOM-killed. Only the heap size changes; build output does not. +# V8 old-space ceiling for next build. Turbopack allocates natively, outside this +# budget, so the ceiling must leave room for it plus BuildKit. ARG NEXT_BUILD_HEAP_MB=8192 ENV NEXT_BUILD_HEAP_MB=${NEXT_BUILD_HEAP_MB} From 50bed00a044f3cf9cc19c623ab9d714a26c00d48 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 21 Jul 2026 16:07:15 -0700 Subject: [PATCH 4/4] fix(ci): build the app image on a larger runner in GitHub mode --- .github/actions/docker-build/action.yml | 3 --- .github/workflows/ci.yml | 25 ++++++++++++++++++++----- apps/sim/package.json | 2 +- docker/app.Dockerfile | 5 ----- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/actions/docker-build/action.yml b/.github/actions/docker-build/action.yml index b22f50449d4..bb241713490 100644 --- a/.github/actions/docker-build/action.yml +++ b/.github/actions/docker-build/action.yml @@ -46,8 +46,6 @@ runs: uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 # No cache-to: type=gha — it shares the 10 GB repo quota with the cache mounts. - # Heap trimmed from the 8192 default: 16 GB runners OOM-killed next build (exit - # 137). Hardcoded, not an input, so a caller can't re-raise it. - name: Build and push (GitHub) if: inputs.provider != '' && inputs.provider != 'blacksmith' uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 @@ -57,6 +55,5 @@ runs: platforms: ${{ inputs.platforms }} push: true tags: ${{ inputs.tags }} - build-args: NEXT_BUILD_HEAP_MB=4096 provenance: false sbom: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 162662d9b90..0aa7a40ed9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,9 @@ name: CI # against the provider you're escaping. Every runs-on and both composite actions # share this predicate and must change together. # -# GitHub mode is break-glass, not a peer — 4 vCPU/16 GB, cold layers, and the app -# image build is the tight spot (it OOM-killed next build on the first live flip). +# GitHub mode is break-glass, not a peer — cold layers, slower runs. The app image +# is the one job on a paid larger runner: next build needs ~32 GB and OOM-kills +# (exit 137) on the free 16 GB runners at any heap ceiling. on: push: @@ -95,7 +96,7 @@ jobs: name: Build Dev ECR needs: [detect-version, migrate-dev] if: github.event_name == 'push' && github.ref == 'refs/heads/dev' - runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }} + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || matrix.gh_runner }} timeout-minutes: 30 permissions: contents: read @@ -104,14 +105,20 @@ jobs: fail-fast: false matrix: include: + # Only the app image needs the paid 8-core/32 GB runner: next build + # exhausts the free 16 GB one (exit 137). The others build in <5 min. - dockerfile: ./docker/app.Dockerfile ecr_repo_secret: ECR_APP + gh_runner: linux-x64-8-core - dockerfile: ./docker/db.Dockerfile ecr_repo_secret: ECR_MIGRATIONS + gh_runner: ubuntu-latest - dockerfile: ./docker/realtime.Dockerfile ecr_repo_secret: ECR_REALTIME + gh_runner: ubuntu-latest - dockerfile: ./docker/pii.Dockerfile ecr_repo_secret: ECR_PII + gh_runner: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -200,7 +207,7 @@ jobs: if: >- github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') - runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }} + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || matrix.gh_runner }} timeout-minutes: 30 permissions: contents: read @@ -213,15 +220,19 @@ jobs: - dockerfile: ./docker/app.Dockerfile ghcr_image: ghcr.io/simstudioai/simstudio ecr_repo_secret: ECR_APP + gh_runner: linux-x64-8-core - dockerfile: ./docker/db.Dockerfile ghcr_image: ghcr.io/simstudioai/migrations ecr_repo_secret: ECR_MIGRATIONS + gh_runner: ubuntu-latest - dockerfile: ./docker/realtime.Dockerfile ghcr_image: ghcr.io/simstudioai/realtime ecr_repo_secret: ECR_REALTIME + gh_runner: ubuntu-latest - dockerfile: ./docker/pii.Dockerfile ghcr_image: ghcr.io/simstudioai/pii ecr_repo_secret: ECR_PII + gh_runner: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -364,7 +375,7 @@ jobs: # never moves a documented tag. build-ghcr-arm64: name: Build ARM64 (GHCR Only) - runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404-arm' || 'ubuntu-24.04-arm' }} + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404-arm' || matrix.gh_runner }} timeout-minutes: 30 if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: @@ -376,12 +387,16 @@ jobs: include: - dockerfile: ./docker/app.Dockerfile image: ghcr.io/simstudioai/simstudio + gh_runner: linux-arm64-8-core - dockerfile: ./docker/db.Dockerfile image: ghcr.io/simstudioai/migrations + gh_runner: ubuntu-24.04-arm - dockerfile: ./docker/realtime.Dockerfile image: ghcr.io/simstudioai/realtime + gh_runner: ubuntu-24.04-arm - dockerfile: ./docker/pii.Dockerfile image: ghcr.io/simstudioai/pii + gh_runner: ubuntu-24.04-arm steps: - name: Checkout code diff --git a/apps/sim/package.json b/apps/sim/package.json index 8fcb96f5489..1a41e053ea3 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -17,7 +17,7 @@ "load:workflow:baseline": "BASE_URL=${BASE_URL:-http://localhost:3000} WARMUP_DURATION=${WARMUP_DURATION:-10} WARMUP_RATE=${WARMUP_RATE:-2} PEAK_RATE=${PEAK_RATE:-8} HOLD_DURATION=${HOLD_DURATION:-20} bunx artillery run scripts/load/workflow-concurrency.yml", "load:workflow:waves": "BASE_URL=${BASE_URL:-http://localhost:3000} WAVE_ONE_DURATION=${WAVE_ONE_DURATION:-10} WAVE_ONE_RATE=${WAVE_ONE_RATE:-6} QUIET_DURATION=${QUIET_DURATION:-5} WAVE_TWO_DURATION=${WAVE_TWO_DURATION:-15} WAVE_TWO_RATE=${WAVE_TWO_RATE:-8} WAVE_THREE_DURATION=${WAVE_THREE_DURATION:-20} WAVE_THREE_RATE=${WAVE_THREE_RATE:-10} bunx artillery run scripts/load/workflow-waves.yml", "load:workflow:isolation": "BASE_URL=${BASE_URL:-http://localhost:3000} ISOLATION_DURATION=${ISOLATION_DURATION:-30} TOTAL_RATE=${TOTAL_RATE:-9} WORKSPACE_A_WEIGHT=${WORKSPACE_A_WEIGHT:-8} WORKSPACE_B_WEIGHT=${WORKSPACE_B_WEIGHT:-1} bunx artillery run scripts/load/workflow-isolation.yml", - "build": "bun run build:sandbox-bundles && NODE_OPTIONS=--max-old-space-size=${NEXT_BUILD_HEAP_MB:-8192} next build", + "build": "bun run build:sandbox-bundles && NODE_OPTIONS='--max-old-space-size=8192' next build", "build:sandbox-bundles": "bun run ./lib/execution/sandbox/bundles/build.ts", "start": "next start", "prepare": "cd ../.. && bun husky", diff --git a/docker/app.Dockerfile b/docker/app.Dockerfile index f42472f2532..c0cc26de7d9 100644 --- a/docker/app.Dockerfile +++ b/docker/app.Dockerfile @@ -76,11 +76,6 @@ ENV DATABASE_URL=${DATABASE_URL} ARG NEXT_PUBLIC_APP_URL="http://localhost:3000" ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} -# V8 old-space ceiling for next build. Turbopack allocates natively, outside this -# budget, so the ceiling must leave room for it plus BuildKit. -ARG NEXT_BUILD_HEAP_MB=8192 -ENV NEXT_BUILD_HEAP_MB=${NEXT_BUILD_HEAP_MB} - # Per-platform cache id keeps arm64/amd64 SWC artifacts isolated. RUN --mount=type=cache,id=next-cache-${TARGETPLATFORM},target=/app/apps/sim/.next/cache \ --mount=type=cache,id=turbo-cache-${TARGETPLATFORM},target=/app/.turbo \