Skip to content

App Hosting monorepo images ship the entire /workspace (~2.5 GB) — adapter omits outputFiles prune list when MONOREPO_COMMAND is set (b/432285470) #669

Description

@cyrus-droid

Environment

  • Firebase App Hosting (managed Cloud Build pipeline; buildpack chain ubuntu → preparer → google.nodejs.turborepo → build → google.nodejs.firebasebundle → publisher)
  • @apphosting/adapter-nextjs 14.0.21 (current latest)
  • pnpm 9 + Turborepo monorepo (~70 workspace packages), Next.js 15.x, output: 'standalone'
  • 4 App Hosting backends built from the same monorepo

Problem

For monorepo builds, the Next.js adapter's generateBundleYaml skips emitting outputFiles.serverApp.include whenever MONOREPO_COMMAND is set:

// packages/@apphosting/adapter-nextjs/src/utils.ts
// TODO (b/432285470) See if there is a way to also delete files for apps using Nx monorepos
if (!process.env.MONOREPO_COMMAND) {
  outputBundle.outputFiles = {
    serverApp: {
      include: [normalize(relative(cwd, opts.outputDirectoryAppPath))],
    },
  };
}

With no outputFiles allowlist in bundle.yaml, the google.nodejs.firebasebundle buildpack tars the entire /workspace into the image's app layer — even though runCommand targets the self-contained .next/standalone server that Next.js produced specifically so deployments can ship only the traced closure.

Measured impact (real production backends, 2026-07-19)

Every one of our four backends ships a 2,410–2,713 MB compressed image whose app layer (2,252–2,554 MB) uncompresses to ~6.3 GB. Inventory of one layer (a small, self-contained app in the workspace):

Content Uncompressed Needed at runtime?
node_modules/.pnpm (full workspace install, dev deps included) 2,911 MB No
.pnpm-store/v3 (pnpm download cache) 2,687 MB No
.next/standalone (the actual server, 109 traced packages) 204 MB Yes — the only part needed
.next/cache, .turbo/cache 204 MB No
other workspace apps' source, .git ~260 MB No

So ~94% of the layer is build-time material. The runtime needs ~204 MB; we ship ~2.5 GB, four times over (once per backend). Cost per deploy: the single Adding 1/1 app layer(s) export step takes 4–6 min and the registry push another 1.5–2 min — ~6–8 minutes of every deploy on every backend is spent exporting and pushing content the server never reads. It also inflates cold-start image pulls.

Notably, the layer even includes the pnpm store and .next/.turbo caches, which are pure build caches regardless of the monorepo question.

Expected behavior

Monorepo builds should get the same treatment single-app builds already get: bundle.yaml carries outputFiles.serverApp.include (per the output-bundle spec in firebase/apphosting-adapters) pointing at the app's output directory, and the bundle buildpack prunes accordingly. For Next.js standalone output the traced closure is self-contained, so at minimum the app's .next/standalone + .next/static + public would be a correct include set — our measured images would drop from ~2.5 GB to roughly 100–250 MB.

No user-side workaround exists

  • There is no apphosting.yaml / bundle.yaml knob to exclude or prune content.
  • Overriding the gate via apphosting.yaml env is impossible: the adapter's check is if (!process.env.MONOREPO_COMMAND), and App Hosting's config validation rejects value: "" (we tested — the build fails with "apphosting.yaml … is not formatted properly"), while any accepted value is truthy.
  • outputFileTracingExcludes is irrelevant — it only shrinks the 204 MB standalone trace, not the ~6 GB shipped around it.
  • Redirecting the pnpm store out of /workspace via .npmrc store-dir claws back ~950 MB compressed, but the remaining ~1.3 GB (workspace node_modules, caches, sibling apps, .git) is untouchable from user config.

Ask

The TODO already references internal bug b/432285470 — could this issue be linked to it, and monorepo pruning be prioritized? Happy to provide build IDs, full layer inventories, or test a canary adapter against our four-backend monorepo.

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