fix(docker): serialize build stages so next build doesn't OOM on the 8 GB host

Coolify deploys were failing with exit 255 (kernel OOM, no Docker error)
around the 73s mark of `next build`. Root cause: docker/Dockerfile's three
build stages (web-build, collab-build, mcp-build) all `FROM deps`, and
BuildKit was running them in parallel. The web-build alone wants ~3 GB
heap (capped at 5120 MB) and was racing tsup workers + buildkit + dockerd
on an 8 GB host until the kernel reaped it.

docker-compose.coolify.yml already had a comment claiming `depends_on:
[collab, mcp]` on `web` would serialize the builds. It doesn't —
`depends_on` only orders runtime startup, not `docker compose build`.

Fix: enforce ordering inside the Dockerfile DAG by COPYing one trivial
artifact from each lighter stage into web-build. BuildKit now waits for
collab-build and mcp-build to finish before starting the heavy Next.js
compile, which then gets the host effectively to itself. The copied
files land in /tmp and are never read by the runtime web image.

Also updated the compose comment to reflect the new (and accurate)
ordering mechanism.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Randall Stillwell 2026-06-05 21:02:39 -05:00
parent 011d3eb710
commit d4e4f9dbf0
2 changed files with 19 additions and 4 deletions

View file

@ -49,6 +49,16 @@ COPY packages/database ./packages/database
COPY packages/shared ./packages/shared
COPY docker/next.config.docker.ts ./apps/web/next.config.ts
# Force BuildKit to finish the two tsup builds before starting the heavy
# Next.js compile. docker-compose's `depends_on` only orders runtime startup,
# not `docker compose build` — without these COPYs, BuildKit runs all three
# build stages in parallel and the host OOMs around the 73s mark of the web
# build (kernel reaps with exit 255 and no Docker error, matching the warning
# below about heap sizing). The copied files are throwaway markers; the
# runtime web image never reads /tmp.
COPY --from=collab-build /app/apps/collab-server/dist/index.mjs /tmp/.collab-built
COPY --from=mcp-build /app/apps/mcp-server/dist/index.js /tmp/.mcp-built
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
# Give the Next.js webpack compile enough V8 heap headroom on the Coolify host.

View file

@ -22,10 +22,15 @@
# The shared `deps` stage is hashed identically across targets, so BuildKit
# runs `pnpm install` ONCE.
#
# `web` declares depends_on collab+mcp so Compose builds the lightweight
# services first and the heavy `next build` (peak ~2.5 GB heap) gets the host
# effectively to itself instead of fighting concurrent runtime-stage COPYs
# for memory.
# The heavy `next build` for `web` (peak ~3 GB heap with 5 GB headroom)
# must not race the two tsup builds for collab+mcp on the 8 GB Coolify
# host or the kernel OOM-kills it (exit 255 with no Docker error). Build
# ordering is enforced inside docker/Dockerfile: the `web-build` stage
# has explicit `COPY --from=collab-build` / `COPY --from=mcp-build` lines
# that force BuildKit's DAG to finish those two before starting the web
# compile. The `depends_on` below only orders runtime startup, NOT image
# builds — that was a misconception from an earlier iteration of this
# file.
services:
web: