From 499a4d228d5b0a2dd206d6e898e83ba15567f205 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 10 Aug 2026 12:31:47 -0700 Subject: [PATCH] perf(ci): build the app image on 16 vCPU and keep the toolchain out of it Three independent changes to the app image build. The app image builds on 16 vCPU instead of 8. Its dominant steps -- bun install (~300-465s) and next build (~262s) -- both scale with cores, and this build gates every deploy because nothing ships until the image is pushed. The same next build already runs on 16 vCPU in the Build App verification job, which gates nothing. The isolated-vm rebuild will not benefit: JOBS=4 caps it deliberately to avoid an OOM. ARM64 stays at 8 -- that job is off the deploy path, and the workflow warns an unprovisioned runner label hangs a release in queued. node-gyp comes from the lockfile rather than npx. npx resolved it from the registry during every production image build, which pulled 13.x over the pinned 12.4.0 and bypassed the minimumReleaseAge gate in bunfig.toml. It has to be an apps/sim devDependency because the only other copy is transitive through @electron/rebuild, which turbo prune sim strips. base splits into base and build-base. The compiler toolchain exists only to build isolated-vm against Node and the runner copies the finished binary, so shipping it inflated every ECS task pull: measured 1.21 GB for base against 1.6 GB for build-base, so ~390 MB stays out of the final image. ffmpeg and python3 stay -- fluent-ffmpeg is a serverExternalPackage, and node-gyp needs the interpreter. Verified against real image builds rather than by inspection: g++/make/pip3 are absent from the runtime image while node/bun/python3/ffmpeg remain, the isolated-vm native module loads and evaluates, lib0/yjs/y-protocols including the lib0/logging.js subpath are intact, sharp and libvips load and encode a PNG so #6499's fix still works against the slimmed image, the container boots to Next.js Ready, and the full suite passes 21250 tests through the dependency hoisting reshuffle that promoting node-gyp causes (root which@6.0.1, fluent-ffmpeg keeps its own 1.3.1). Not included: an earlier attempt added a cache-key input to useblacksmith/setup-docker-builder, on the theory that the deps layer re-runs every build (286s/465s observed) because all five Dockerfiles share one layer cache. That input does not exist at the pinned SHA or on main, and the key is not configurable -- setup_builder.ts uses GITHUB_REPO_NAME. CI logs also show the sticky disk being obtained with a parent snapshot rather than falling back, so the cache is present and that cause remains unidentified. --- .github/workflows/ci.yml | 19 ++++++++++++------- apps/sim/package.json | 1 + bun.lock | 25 ++++++++++++++----------- docker/app.Dockerfile | 40 +++++++++++++++++++++++++++++++++------- 4 files changed, 60 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 227fda6e1fe..4940a8d6b34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,15 +155,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. - # bs_runner mirrors that per-image sizing on Blacksmith — a single - # pinned tier put every image on 8 vCPU, where the non-app builds idle - # at 12-15% CPU and under 10% memory. + # Only the app image needs a large runner: next build exhausts the free + # 16 GB one (exit 137). The others build in <5 min and idle at 12-15% + # CPU on 8 vCPU, so they stay on the smaller tiers. + # + # 16 vCPU on Blacksmith because this build is the critical path to a + # deploy — nothing ships until the image is pushed — and its two + # dominant steps both scale with cores (`bun install` ~300-400s, `next + # build` ~260s). The same `next build` runs on 16 vCPU in the separate + # Build App verification job, which does not gate anything; this one + # was doing comparable work on half the cores. - dockerfile: ./docker/app.Dockerfile ecr_repo_secret: ECR_APP gh_runner: linux-x64-8-core - bs_runner: blacksmith-8vcpu-ubuntu-2404 + bs_runner: blacksmith-16vcpu-ubuntu-2404 - dockerfile: ./docker/db.Dockerfile ecr_repo_secret: ECR_MIGRATIONS gh_runner: ubuntu-latest @@ -278,7 +283,7 @@ jobs: ghcr_image: ghcr.io/simstudioai/simstudio ecr_repo_secret: ECR_APP gh_runner: linux-x64-8-core - bs_runner: blacksmith-8vcpu-ubuntu-2404 + bs_runner: blacksmith-16vcpu-ubuntu-2404 - dockerfile: ./docker/db.Dockerfile ghcr_image: ghcr.io/simstudioai/migrations ecr_repo_secret: ECR_MIGRATIONS diff --git a/apps/sim/package.json b/apps/sim/package.json index 7b0e95742e6..5bd4eb6fbc6 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -263,6 +263,7 @@ "@types/three": "0.177.0", "@vitejs/plugin-react": "^6.0.5", "@vitest/coverage-v8": "^4.1.0", + "node-gyp": "12.4.0", "postcss": "^8", "react-email": "6.9.0", "tailwindcss": "^3.4.1", diff --git a/bun.lock b/bun.lock index 3f93d781422..fdedeaa89ec 100644 --- a/bun.lock +++ b/bun.lock @@ -366,6 +366,7 @@ "@types/three": "0.177.0", "@vitejs/plugin-react": "^6.0.5", "@vitest/coverage-v8": "^4.1.0", + "node-gyp": "12.4.0", "postcss": "^8", "react-email": "6.9.0", "tailwindcss": "^3.4.1", @@ -2799,7 +2800,7 @@ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], @@ -3193,7 +3194,7 @@ "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], "isolated-vm": ["isolated-vm@6.1.2", "", { "dependencies": { "node-gyp-build": "^4.8.4" } }, "sha512-GGfsHqtlZiiurZaxB/3kY7LLAXR3sgzDul0fom4cSyBjx6ZbjpTrFWiH3z/nUfLJGJ8PIq9LQmQFiAxu24+I7A=="], @@ -4453,7 +4454,7 @@ "when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="], - "which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + "which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], @@ -4607,6 +4608,8 @@ "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + "@electron/get/env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], @@ -4943,6 +4946,8 @@ "conf/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "conf/env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], @@ -5015,6 +5020,8 @@ "fluent-ffmpeg/async": ["async@0.2.10", "", {}, "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="], + "fluent-ffmpeg/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -5115,12 +5122,8 @@ "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - "node-gyp/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - "node-gyp/undici": ["undici@6.28.0", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="], - "node-gyp/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], - "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "nuqs/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], @@ -5415,8 +5418,6 @@ "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "app-builder-lib/@electron/get/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -5435,6 +5436,8 @@ "cmdk/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], @@ -5509,6 +5512,8 @@ "express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "fluent-ffmpeg/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "gcp-metadata/gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], @@ -5589,8 +5594,6 @@ "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - "node-gyp/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], - "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA=="], diff --git a/docker/app.Dockerfile b/docker/app.Dockerfile index 21b92971d45..28f6391b31d 100644 --- a/docker/app.Dockerfile +++ b/docker/app.Dockerfile @@ -1,23 +1,42 @@ # ======================================== -# Base Stage: Debian-based Bun with Node.js 24 +# Base Stage: runtime-only dependencies (inherited by the final image) # ======================================== FROM oven/bun:1.3.14-slim AS base -# Install Node.js 24 (Active LTS) and common dependencies once in base stage. +# Install Node.js 24 (Active LTS) and the runtime dependencies once in base. # Node runs only the isolated-vm sandbox worker (the app itself runs under Bun); # the version is kept in lockstep with the `isolated-vm` pin in # apps/sim/package.json — Node 24 (ABI 137) requires isolated-vm 6.x. +# +# Only what the running container needs belongs here. ffmpeg backs the +# `fluent-ffmpeg` serverExternalPackage; python3 is the node-gyp interpreter and +# is kept because build-base inherits from this stage. The compiler toolchain +# lives in build-base so the runner does not ship it. RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends \ - python3 python3-pip python3-venv make g++ curl ca-certificates bash ffmpeg \ + python3 curl ca-certificates bash ffmpeg \ && curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ && apt-get install -y nodejs +# ======================================== +# Build Base: adds the native toolchain the isolated-vm rebuild needs +# ======================================== +FROM base AS build-base + +# The compiler toolchain, needed only to build isolated-vm against Node. The +# runner copies the finished binary from deps, so shipping these would inflate +# every ECS task pull for nothing: measured 1.21 GB for base against 1.6 GB for +# build-base, so ~390 MB stays out of the final image. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + python3-pip python3-venv make g++ + # ======================================== # Pruner Stage: Emit a minimal monorepo subset that sim depends on # ======================================== -FROM base AS pruner +FROM build-base AS pruner WORKDIR /app RUN bun install -g turbo@2.9.6 @@ -29,7 +48,7 @@ RUN turbo prune sim --docker # ======================================== # Dependencies Stage: Install Dependencies # ======================================== -FROM base AS deps +FROM build-base AS deps WORKDIR /app # Pruned manifests from the pruner stage. This layer only invalidates when @@ -44,15 +63,22 @@ COPY --from=pruner /app/bun.lock ./bun.lock # Install all dependencies (including devDependencies — tailwindcss/postcss are # devDeps but required at build time). Then rebuild isolated-vm against Node.js. # JOBS=4 caps node-gyp parallelism — higher values OOM isolated-vm (laverdet/isolated-vm#428). +# +# node-gyp comes from the lockfile, not `npx`. It is a devDependency of apps/sim +# purely so `turbo prune sim` keeps it: the only other copy is transitive through +# `@electron/rebuild`, which belongs to apps/desktop and is pruned away. `npx` +# resolved it from the registry at build time, which pulled a different major +# (13.x vs the pinned 12.4.0) and bypassed the `minimumReleaseAge` supply-chain +# gate in bunfig.toml on every production image build. RUN --mount=type=cache,id=bun-cache,target=/root/.bun/install/cache \ --mount=type=cache,id=npm-cache,target=/root/.npm \ HUSKY=0 bun install --ignore-scripts --linker=hoisted && \ - cd node_modules/isolated-vm && JOBS=4 npx node-gyp rebuild --release + cd node_modules/isolated-vm && JOBS=4 /app/node_modules/.bin/node-gyp rebuild --release # ======================================== # Builder Stage: Build the Application # ======================================== -FROM base AS builder +FROM build-base AS builder ARG TARGETPLATFORM WORKDIR /app