Skip to content

perf: slim the Docker image (turbo prune multi-stage + cap-media layer fix) - #49

Merged
Quixomatic merged 1 commit into
mainfrom
perf/slim-docker-image
Sep 26, 2026
Merged

Quixomatic merged 1 commit into
mainfrom
perf/slim-docker-image

Conversation

@Quixomatic

@Quixomatic Quixomatic commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Fixes #48.

The self-host Docker image had grown to about 8.5GB on disk. There were two causes, both confirmed against the Dockerfile.

The first is a baked cap-media duplicate, about 438MB. The Dockerfile did COPY docker/cap-media/ /tmp, extracted it, then rm'd the copy. Docker layers are immutable, so that rm only hides the ~430MB tarball; it stays in the layer. It's now bind-mounted for the extract step instead, so the archive is never a layer and only the extracted media ships. This has been in every image since v0.6.27.

The second is the root pnpm install, about 4GB. It ran unscoped, installing every workspace app's dependencies (tv-native/Expo, tv-tauri, desktop, desktop-setup, site, mcp, roku, promo), none of which run in the container. The build is now multi-stage and runs turbo prune server web tv-web --docker, so it installs only what those three roles need.

What changed

  • Multi-stage Dockerfile: a base stage, a prune stage (turbo prune), a build stage (pruned install, Prisma generate, server build), and a final runtime stage.
  • cap-media extraction via --mount=type=bind instead of COPY plus rm.
  • No CI change needed. The workflow still builds the default target (final) and stages cap-media into docker/cap-media.

What didn't change

Same base (Bun and pnpm), same lockfile-pinned dependency versions for the three roles, same Prisma client, same tsdown server bundle and .well-known workflow handlers, same cap-media (39 files), same entrypoint and CG_ROLE behavior. web and tv-web still build with Vite at startup. Local dev is untouched; the prune only happens inside the image build.

Results

Compressed pull from the GHCR manifests, amd64:

Old 0.14.61 This branch
Compressed pull ~2506 MB ~1042 MB
node_modules layer 983 MB (all apps) 476 MB (pruned)
full-source COPY . . 497 MB gone
cap-media /tmp dup 437 MB gone

About 58% smaller compressed (2.5GB to 1.04GB), and roughly 70% on disk (8.5GB to about 2.2 to 2.6GB). The remaining ~1GB is cap-media, which is already-compressed video and won't shrink, plus the Vite toolchain that web and tv-web need to build at startup.

The CI multi-arch build passed, and the image was run on a real TrueNAS/Dockge stack: all three roles, the startup migrate, both startup Vite builds, and the cap-media diagnostic.

A separate follow-up, not in this PR: defaulting cap-media to an on-demand fetch (like the desktop supervisor) would drop the last ~437MB.

…cap-media dup

The self-host image had grown to ~8.5GB (uncompressed). Two causes:

1. `COPY docker/cap-media/ /tmp` then extract + `rm` left the ~430MB tarball baked
   into an immutable layer (the rm only hid it). Now bind-mounted for the extract
   RUN, so the archive is never a layer — only the extracted media ships.

2. `pnpm install` ran at the workspace root with no scoping, installing every app's
   deps (tv-native/Expo, tv-tauri, desktop, site, mcp, roku, promo) into the image.
   Now a multi-stage build runs `turbo prune server web tv-web --docker` and installs
   only that dependency closure. web/tv-web keep their dev toolchain on purpose (they
   vite-build at container startup); the server bundle is still built once here.

Runtime behavior is unchanged (same CG_ROLE server/web/tvweb entrypoint). Dev/local
workflows are untouched — the prune only happens inside the image build.
@vercel

vercel Bot commented Sep 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
airwave-site Ignored Ignored Sep 26, 2026 2:29am UTC

@Quixomatic
Quixomatic marked this pull request as draft September 26, 2026 02:30
@Quixomatic
Quixomatic marked this pull request as ready for review September 26, 2026 17:56
@Quixomatic
Quixomatic merged commit 2dbc864 into main Sep 26, 2026
3 checks passed
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.

Docker image is ~8.5GB (v0.14.61) — duplicated capability-media layer + possible un-pruned monorepo install

1 participant