Skip to content

feat(worker): ship the worker image as an esbuild bundle over prod-only deps#638

Merged
BigSimmo merged 1 commit into
mainfrom
claude/worker-image-slim-2026-07-14
Jul 14, 2026
Merged

feat(worker): ship the worker image as an esbuild bundle over prod-only deps#638
BigSimmo merged 1 commit into
mainfrom
claude/worker-image-slim-2026-07-14

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Deferred audit item D1 (2026-07-13 audit, finding 12): 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.tsdist/worker/index.mjs (esbuild, 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 → runner whose CMD runs the bundle under plain node. Local build: 2.2GB vs 3.59GB dev-inclusive (~40% smaller); tsx/esbuild/the dev toolchain never reach the image.
  • Two real boot bugs surfaced by the smoke run and fixed:
    • next/server aliased to next/server.js in the bundle — next has no exports map, so plain-node ESM needs the explicit file (tsx papered over this).
    • worker/index.ts now uses the repo's namespace-import interop for CJS @next/env (same pattern as scripts/classify-documents.ts) — Node's lexer can't detect its named exports.
  • tests/worker-bundle.test.ts locks 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=dev prune (package-lock dev flags). tests/tsx-server-only-runner.test.ts updated to assert the bundle CMD + the build-time server-only alias.
  • docker-image.yml + ci-change-scope.mjs now treat scripts/build-worker.mjs as container-affecting; runbook + architecture docs updated.

Verification

  • npm run verify:cheap green (2,243 tests, incl. the two new guards)
  • Local docker build -f Dockerfile.worker green
  • Container boots without secrets to the expected requireServerEnv fail-fast (proves the module graph resolves against prod-only node_modules)
  • Bundle boots with env to Clinical KB worker started
  • npm run format run

Deploy note

No Railway config change needed — railway.worker.json still points at Dockerfile.worker. Next worker deploy picks the slim image up automatically.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a production worker bundle build process for faster, slimmer container startup.
    • Worker images now run with production-only dependencies and a prebuilt bundle.
    • Added validation to ensure runtime dependencies remain available in production images.
  • CI/CD

    • Docker workflows now rebuild when worker build configuration changes.
  • Documentation

    • Updated deployment architecture and worker runbook guidance for the new build and startup process.

…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>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@supabase

supabase Bot commented Jul 14, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BigSimmo
BigSimmo enabled auto-merge (squash) July 14, 2026 05:54
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 09ff51f7-6f4b-447a-beaa-66b0f4d43ae9

📥 Commits

Reviewing files that changed from the base of the PR and between fa850f3 and 66c0f7b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • .dockerignore
  • .github/workflows/docker-image.yml
  • .gitignore
  • Dockerfile.worker
  • docs/deployment-architecture.md
  • docs/worker-deploy-runbook.md
  • package.json
  • scripts/build-worker.mjs
  • scripts/ci-change-scope.mjs
  • tests/tsx-server-only-runner.test.ts
  • tests/worker-bundle.test.ts
  • worker/index.ts

📝 Walkthrough

Walkthrough

The 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.

Changes

Worker production bundle

Layer / File(s) Summary
Bundle contract and bootstrap compatibility
package.json, scripts/build-worker.mjs, worker/index.ts, tests/worker-bundle.test.ts
Adds the worker build command and esbuild configuration, handles the bundled @next/env import shape, and validates external dependency resolution under plain Node ESM.
Production image assembly and CI wiring
Dockerfile.worker, .dockerignore, .gitignore, .github/workflows/docker-image.yml, scripts/ci-change-scope.mjs, tests/tsx-server-only-runner.test.ts
Builds and runs the bundled worker with production-only dependencies, excludes dist/ from context and version control, and updates CI triggers and image assertions.
Deployment contract documentation
docs/deployment-architecture.md, docs/worker-deploy-runbook.md
Documents the bundled worker image, production-only dependencies, build inputs, and plain-Node ESM boot requirements.

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
Loading

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/worker-image-slim-2026-07-14

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 @coderabbitai help to get the list of available commands.

@BigSimmo
BigSimmo merged commit 6f3644c into main Jul 14, 2026
18 checks passed
@BigSimmo
BigSimmo deleted the claude/worker-image-slim-2026-07-14 branch July 14, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant