feat(worker): ship the worker image as an esbuild bundle over prod-only deps#638
Conversation
…ly deps Audit 2026-07-13 finding 12 (deferred item D1): the worker container ran worker/index.ts through tsx, forcing the image to carry the full dev-inclusive node_modules. - scripts/build-worker.mjs bundles worker/index.ts to dist/worker/index.mjs (packages external, server-only aliased to the standalone stub at build time — the same guarantee run-tsx.mjs gave at runtime). - Dockerfile.worker becomes a 3-stage build: bundle with dev deps, npm ci --omit=dev for the runtime stage, CMD runs the bundle under plain node. Local build: 2.2GB vs 3.59GB dev-inclusive (~40% smaller). - Boot-proofing fixes surfaced by the smoke run: next/server aliased to next/server.js (next has no exports map, so plain-node ESM needs the explicit file) and worker/index.ts uses the repo's namespace-import interop for CJS @next/env. - tests/worker-bundle.test.ts locks the two container-only failure modes: every bundle external must resolve under native node ESM semantics and must survive the --omit=dev prune (via package-lock dev flags). - docker-image.yml + ci-change-scope treat scripts/build-worker.mjs as container-affecting; runbook + architecture docs updated. Verified: verify:cheap green (2243 tests), local docker build green, container boots to the expected env-validation fail-fast without secrets, bundle boots to 'worker started' with env. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (12)
📝 WalkthroughWalkthroughThe worker now builds an esbuild ESM bundle, installs production-only dependencies, and runs the bundle directly in a multi-stage Docker image. CI triggers and scope detection include the build script, while tests and deployment documentation validate the new runtime contract. ChangesWorker production bundle
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant DockerBuild
participant WorkerBundle
participant ProductionDependencies
participant WorkerContainer
DockerBuild->>WorkerBundle: run scripts/build-worker.mjs
DockerBuild->>ProductionDependencies: run npm ci --omit=dev
WorkerBundle-->>WorkerContainer: copy dist/worker/index.mjs
ProductionDependencies-->>WorkerContainer: copy node_modules
WorkerContainer->>WorkerContainer: run node dist/worker/index.mjs
Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Trivy (0.69.3)Trivy execution failed: 2026-07-14T05:54:34Z FATAL Fatal error run error: fs scan error: scan error: scan failed: failed analysis: post analysis error: post analysis error: cloudformation scan error: fs filter error: fs filter error: walk error range error: stat doctor.config.json: no such file or directory: range error: stat doctor.config.json: no such file or directory Comment |
Summary
Deferred audit item D1 (2026-07-13 audit, finding 12): the worker container ran
worker/index.tsthrough tsx, forcing the image to carry the full dev-inclusivenode_modules.scripts/build-worker.mjsbundlesworker/index.ts→dist/worker/index.mjs(esbuild,packages: external;server-onlyaliased to the standalone stub at build time — the same guaranteerun-tsx.mjsgave at runtime).Dockerfile.workerbecomes a 3-stage build: bundle with dev deps →npm ci --omit=dev→ runner whoseCMDruns the bundle under plainnode. Local build: 2.2GB vs 3.59GB dev-inclusive (~40% smaller); tsx/esbuild/the dev toolchain never reach the image.next/serveraliased tonext/server.jsin the bundle — next has noexportsmap, so plain-node ESM needs the explicit file (tsx papered over this).worker/index.tsnow uses the repo's namespace-import interop for CJS@next/env(same pattern asscripts/classify-documents.ts) — Node's lexer can't detect its named exports.tests/worker-bundle.test.tslocks the two container-only failure modes: every bundle external must resolve under native node ESM semantics (plain-node subprocess probe) and must survive the--omit=devprune (package-lockdevflags).tests/tsx-server-only-runner.test.tsupdated to assert the bundle CMD + the build-timeserver-onlyalias.docker-image.yml+ci-change-scope.mjsnow treatscripts/build-worker.mjsas container-affecting; runbook + architecture docs updated.Verification
npm run verify:cheapgreen (2,243 tests, incl. the two new guards)docker build -f Dockerfile.workergreenrequireServerEnvfail-fast (proves the module graph resolves against prod-onlynode_modules)Clinical KB worker startednpm run formatrunDeploy note
No Railway config change needed —
railway.worker.jsonstill points atDockerfile.worker. Next worker deploy picks the slim image up automatically.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
CI/CD
Documentation