Compare commits

...

3 commits

Author SHA1 Message Date
varutasu
491d196384
fix(docker): reorder build stages so web-build comes after its --from= sources (#2)
Coolify's builder fails the build at parse time with:
  "cannot copy from stage 'collab-build', it needs to be defined before
   current stage 'web-build'"

The previous commit introduced forward COPY --from references that work
on newer BuildKit DAG builders but not on the legacy Docker builder
Coolify uses. Move `collab-build` and `mcp-build` above `web-build` in
lexical order so the references resolve. Functionally identical — the
DAG ordering effect is the same — just expressed in a way the older
builder accepts.

Added a NOTE comment above the stages so the next person who tries to
"clean up" the file order doesn't reintroduce the same regression.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 21:13:39 -05:00
varutasu
3fb79f6f04
Merge pull request #1 from stwl-labs/hotfix/docker-build-ordering
fix(docker): serialize build stages so next build doesn't OOM on the 8 GB host
2026-06-05 21:04:32 -05:00
Randall Stillwell
d4e4f9dbf0 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>
2026-06-05 21:02:39 -05:00
2 changed files with 45 additions and 22 deletions

View file

@ -39,24 +39,12 @@ COPY packages/ai/package.json ./packages/ai/
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
# ============================================================================ # NOTE: Stage order matters. `web-build` must be defined AFTER `collab-build`
# web-build — Next.js production build → standalone output # and `mcp-build` because it `COPY --from=`s them to force BuildKit to
# ============================================================================ # finish the light tsup builds before starting the heavy Next.js compile.
FROM deps AS web-build # Coolify's builder requires stages referenced via `--from=` to be defined
COPY apps/web ./apps/web # above the referencing stage; forward references fail with "cannot copy
COPY packages/ai ./packages/ai # from stage X, it needs to be defined before current stage Y".
COPY packages/database ./packages/database
COPY packages/shared ./packages/shared
COPY docker/next.config.docker.ts ./apps/web/next.config.ts
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
# Give the Next.js webpack compile enough V8 heap headroom on the Coolify host.
# Sized to ~60% of an 8 GB host so kernel + dockerd + buildkit + concurrent
# tsup workers still have breathing room. Below ~3 GB the compile spirals
# into GC churn and gets oom-killed (exit 255 with no Docker error).
ENV NODE_OPTIONS=--max-old-space-size=5120
RUN pnpm exec turbo build --filter=@tasks/web
# ============================================================================ # ============================================================================
@ -82,6 +70,36 @@ ENV NODE_ENV=production
RUN pnpm exec turbo build --filter=@tasks/mcp-server RUN pnpm exec turbo build --filter=@tasks/mcp-server
# ============================================================================
# web-build — Next.js production build → standalone output
# ============================================================================
FROM deps AS web-build
COPY apps/web ./apps/web
COPY packages/ai ./packages/ai
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.
# Sized to ~60% of an 8 GB host so kernel + dockerd + buildkit + concurrent
# tsup workers still have breathing room. Below ~3 GB the compile spirals
# into GC churn and gets oom-killed (exit 255 with no Docker error).
ENV NODE_OPTIONS=--max-old-space-size=5120
RUN pnpm exec turbo build --filter=@tasks/web
# ============================================================================ # ============================================================================
# web — runtime image (Next.js standalone) # web — runtime image (Next.js standalone)
# ============================================================================ # ============================================================================

View file

@ -22,10 +22,15 @@
# The shared `deps` stage is hashed identically across targets, so BuildKit # The shared `deps` stage is hashed identically across targets, so BuildKit
# runs `pnpm install` ONCE. # runs `pnpm install` ONCE.
# #
# `web` declares depends_on collab+mcp so Compose builds the lightweight # The heavy `next build` for `web` (peak ~3 GB heap with 5 GB headroom)
# services first and the heavy `next build` (peak ~2.5 GB heap) gets the host # must not race the two tsup builds for collab+mcp on the 8 GB Coolify
# effectively to itself instead of fighting concurrent runtime-stage COPYs # host or the kernel OOM-kills it (exit 255 with no Docker error). Build
# for memory. # 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: services:
web: web: