Summary
For TypeScript apps, encore build docker bundles the entire workspace into /workspace in the image, with only .git excluded. There is no public configuration to filter what gets included — node_modules ships as-is (devDependencies + dev tooling), and arbitrary repo files (docs/, scripts/, README.md, tsconfig.tsbuildinfo, …) ship too.
This inflates image size dramatically and slows down build / push / Cloud Run cold-start, with no warning to the user.
Reproduce
git clone any TS Encore app that has heavy devDependencies (e.g. typescript, vitest, @biomejs/biome, promptfoo, firebase-tools).
pnpm install (or npm install — both pull devDeps by default).
encore build docker --base node:24-bookworm app:test.
docker run --rm --entrypoint sh app:test -c 'ls /workspace && du -sh /workspace/node_modules'.
Observed
/workspace/node_modules contains every devDependency.
/workspace/ contains every non-.git file from the repo, regardless of whether it's needed at runtime.
In our case the image carried ~50+ MB of pure devDeps (typescript 31 MB, vitest, @biomejs/biome, firebase-tools, etc.) plus a 26 MB promptfoo. Verified inside an image: pnpm why typescript --prod returns empty in the host repo, yet /workspace/node_modules/typescript exists in the image.
Expected
For TS apps, either:
- Default to production-only installs (
npm/pnpm/yarn install --prod), or
- Provide a documented opt-out via
encore.app, e.g.:
Source references
So the user-facing bundle_source flag in encore.app has no effect for TS apps — the bundling happens regardless.
Real-world impact
We observed a ~2× deploy-time regression on Cloud Run after a normal dependency-bump cycle:
| Phase |
Before |
After |
| Encore compile |
67 s |
142 s |
| "saving image to local daemon" |
36 s |
102 s |
| Final layer push to GCR |
4 s |
61 s |
| Cloud Run revision rollout |
40 s |
124 s |
| Total deploy |
~4.5 min |
~10 min |
Root cause was a single new 26 MB devDependency silently shipping to production. A user has no signal this would happen.
Workaround
Run `pnpm install --prod --frozen-lockfile` (or `npm ci --omit=dev`) in CI before `encore build docker`, so the workspace's `node_modules` only contains production deps when Encore copies it. Confirmed effective.
Ask
- Document the current bundling behavior for TS apps in docs/ts/self-host/build.
- Either default to prod-only installs, or expose `exclude_paths` / `prod_only` in `encore.app` build config.
Encore version: v1.57.4
Host: macOS 25.3.0 (arm64), GitHub Actions `ubuntu-24.04` runner
Package manager: pnpm 10
Summary
For TypeScript apps,
encore build dockerbundles the entire workspace into/workspacein the image, with only.gitexcluded. There is no public configuration to filter what gets included —node_modulesships as-is (devDependencies + dev tooling), and arbitrary repo files (docs/,scripts/,README.md,tsconfig.tsbuildinfo, …) ship too.This inflates image size dramatically and slows down build / push / Cloud Run cold-start, with no warning to the user.
Reproduce
git cloneany TS Encore app that has heavy devDependencies (e.g.typescript,vitest,@biomejs/biome,promptfoo,firebase-tools).pnpm install(ornpm install— both pull devDeps by default).encore build docker --base node:24-bookworm app:test.docker run --rm --entrypoint sh app:test -c 'ls /workspace && du -sh /workspace/node_modules'.Observed
/workspace/node_modulescontains every devDependency./workspace/contains every non-.gitfile from the repo, regardless of whether it's needed at runtime.In our case the image carried ~50+ MB of pure devDeps (
typescript31 MB,vitest,@biomejs/biome,firebase-tools, etc.) plus a 26 MBpromptfoo. Verified inside an image:pnpm why typescript --prodreturns empty in the host repo, yet/workspace/node_modules/typescriptexists in the image.Expected
For TS apps, either:
npm/pnpm/yarn install --prod), orencore.app, e.g.:{ "build": { "docker": { "exclude_paths": ["node_modules/.cache", "docs", "scripts"], "prod_only_node_modules": true } } }Source references
pkg/dockerbuild/spec.go→DetermineIncludes— copiesnode_modulesandpackage.jsonfrom every ancestor between workspace root and app root.cli/daemon/export/export.go—if buildSettings.Docker.BundleSource || appLang == appfile.LangTSforces source bundling for TS apps;ExcludeSourceis hardcoded to[".git"].So the user-facing
bundle_sourceflag inencore.apphas no effect for TS apps — the bundling happens regardless.Real-world impact
We observed a ~2× deploy-time regression on Cloud Run after a normal dependency-bump cycle:
Root cause was a single new 26 MB devDependency silently shipping to production. A user has no signal this would happen.
Workaround
Run `pnpm install --prod --frozen-lockfile` (or `npm ci --omit=dev`) in CI before `encore build docker`, so the workspace's `node_modules` only contains production deps when Encore copies it. Confirmed effective.
Ask
Encore version: v1.57.4
Host: macOS 25.3.0 (arm64), GitHub Actions `ubuntu-24.04` runner
Package manager: pnpm 10