firebase / firebase/apphosting-adapters

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

Open
#669 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
478
Forks
1.5k
Avg merge
3d 22h
Merged PRs (30d)
2

Description

### 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:

```ts
// 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](https://github.com/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.

Contributor guide

Open the contributing guide

Research direction

Start in packages/@apphosting/adapter-nextjs/src/utils.ts at generateBundleYaml and read the output-bundle spec linked in the issue, comparing the monorepo path with the existing single-app path. Verify how the app output directory is formed under MONOREPO_COMMAND and which include entries are appropriate for Next.js standalone output. Done means bundle.yaml emits outputFiles.serverApp.include for monorepo builds and the resulting image excludes unrelated workspace content.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, typescript
Domain
build-system, devops
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.