Skip to content

TypeScript: encore build docker bundles full workspace (devDeps + non-runtime files) with no opt-out #2448

Description

@vendoshi

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

  1. git clone any TS Encore app that has heavy devDependencies (e.g. typescript, vitest, @biomejs/biome, promptfoo, firebase-tools).
  2. pnpm install (or npm install — both pull devDeps by default).
  3. encore build docker --base node:24-bookworm app:test.
  4. 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.:
    {
      "build": {
        "docker": {
          "exclude_paths": ["node_modules/.cache", "docs", "scripts"],
          "prod_only_node_modules": true
        }
      }
    }

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

  1. Document the current bundling behavior for TS apps in docs/ts/self-host/build.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions