Spaces:
Running
Running
fix: split frontend into isolated build stage to eliminate residual RSS
Browse filesThree NestJS builds leave ~1-2 GB residual RSS in the same container even
after each exits; next build alone needs ~3-4 GB. Combined they exceed the
HF builder cgroup limit (exit 137 OOMKilled).
Stage 1 (postiz-builder): clone + patch + install + backend/workers/cron
Stage 2 (postiz-frontend): COPY from Stage 1, build Next.js in clean process
Stage 3 (runtime): COPY server build from Stage 1,
overlay .next from Stage 2
Stage 1's processes are dead before Stage 2 starts β OS reclaims all RSS.
next build gets a clean address space with no competition.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Dockerfile +64 -29
Dockerfile
CHANGED
|
@@ -1,14 +1,26 @@
|
|
| 1 |
# ============================================================================
|
| 2 |
# HuggingPost β Postiz v2.11.3 on Hugging Face Spaces
|
| 3 |
#
|
| 4 |
-
#
|
| 5 |
-
# Postiz UI mounts at /app/* and our HuggingPost dashboard owns /.
|
| 6 |
#
|
| 7 |
-
#
|
| 8 |
-
#
|
| 9 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
#
|
| 11 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
# - nginx (port 5000, internal) β Postiz frontend + backend + uploads
|
| 13 |
# - PM2 β 4 Postiz procs (backend/frontend/workers/cron)
|
| 14 |
# - postgres (port 5432, internal)
|
|
@@ -17,7 +29,7 @@
|
|
| 17 |
# - health-server.js (port 7860, public) β dashboard + reverse proxy
|
| 18 |
# ============================================================================
|
| 19 |
|
| 20 |
-
# ββ Stage 1:
|
| 21 |
FROM node:22.20-alpine AS postiz-builder
|
| 22 |
|
| 23 |
WORKDIR /build
|
|
@@ -37,15 +49,12 @@ RUN npm install -g pnpm@10.6.1
|
|
| 37 |
# Pinned to v2.11.3 β last release before Temporal became a hard requirement.
|
| 38 |
RUN git clone --depth=1 --branch v2.11.3 https://github.com/gitroomhq/postiz-app.git .
|
| 39 |
|
| 40 |
-
# Patch Next.js config
|
| 41 |
-
# 1. basePath/assetPrefix=/app β
|
| 42 |
-
# 2.
|
| 43 |
-
#
|
| 44 |
-
#
|
| 45 |
-
#
|
| 46 |
-
# N-1 webpack worker threads by default; each holds a full module graph
|
| 47 |
-
# copy in memory. Single-thread compilation trades speed for RAM.
|
| 48 |
-
# This is the primary fix for exit 137 / OOMKilled on HF builder.
|
| 49 |
RUN sed -i "s|const nextConfig = {|const nextConfig = {\n basePath: '/app',\n assetPrefix: '/app',|" apps/frontend/next.config.js \
|
| 50 |
&& sed -i "s|productionBrowserSourceMaps: true|productionBrowserSourceMaps: false|" apps/frontend/next.config.js \
|
| 51 |
&& sed -i "s|disable: false,|disable: true,|" apps/frontend/next.config.js \
|
|
@@ -55,8 +64,7 @@ RUN sed -i "s|const nextConfig = {|const nextConfig = {\n basePath: '/app',\n
|
|
| 55 |
&& grep -q "cpus: 1" apps/frontend/next.config.js \
|
| 56 |
|| (echo "PATCH FAILED β next.config.js shape changed upstream"; exit 1)
|
| 57 |
|
| 58 |
-
# Sentry env stubs β
|
| 59 |
-
# probe these. Empty values keep them from doing network calls.
|
| 60 |
ENV SENTRY_DSN="" \
|
| 61 |
SENTRY_AUTH_TOKEN="" \
|
| 62 |
SENTRY_ORG="" \
|
|
@@ -65,22 +73,48 @@ ENV SENTRY_DSN="" \
|
|
| 65 |
NEXT_TELEMETRY_DISABLED=1 \
|
| 66 |
NEXT_PRIVATE_SKIP_SIZE_MINIMIZATION=true
|
| 67 |
|
| 68 |
-
# Install all deps (
|
| 69 |
RUN pnpm install --frozen-lockfile=false
|
| 70 |
|
| 71 |
-
# Build apps
|
| 72 |
-
#
|
| 73 |
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:backend
|
| 74 |
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:workers
|
| 75 |
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:cron
|
| 76 |
-
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:frontend
|
| 77 |
|
| 78 |
-
#
|
| 79 |
RUN find . -name ".git" -type d -prune -exec rm -rf {} + 2>/dev/null || true \
|
| 80 |
&& rm -rf .github reports Jenkins .devcontainer 2>/dev/null || true
|
| 81 |
|
| 82 |
|
| 83 |
-
# ββ Stage 2:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
FROM node:22.20-alpine
|
| 85 |
|
| 86 |
WORKDIR /app
|
|
@@ -114,13 +148,14 @@ RUN pip install --no-cache-dir --break-system-packages \
|
|
| 114 |
huggingface_hub \
|
| 115 |
PyYAML
|
| 116 |
|
| 117 |
-
# Copy
|
| 118 |
COPY --from=postiz-builder /build /app
|
| 119 |
|
| 120 |
-
#
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
#
|
|
|
|
| 124 |
COPY --from=postiz-builder /build/var/docker/nginx.conf /etc/nginx/nginx.conf
|
| 125 |
|
| 126 |
# Health-server lives outside /app so its node_modules don't collide with
|
|
|
|
| 1 |
# ============================================================================
|
| 2 |
# HuggingPost β Postiz v2.11.3 on Hugging Face Spaces
|
| 3 |
#
|
| 4 |
+
# Three-stage build to beat the HF Space builder memory limit:
|
|
|
|
| 5 |
#
|
| 6 |
+
# Stage 1 (postiz-builder): clone, patch, install deps,
|
| 7 |
+
# build backend + workers + cron.
|
| 8 |
+
# Stage 2 (postiz-frontend): copy tree from Stage 1, build ONLY the
|
| 9 |
+
# Next.js frontend in a clean process.
|
| 10 |
+
# Stage 1's processes are dead β their RSS
|
| 11 |
+
# is fully freed before `next build` starts.
|
| 12 |
+
# Stage 3 (runtime): copy server build from Stage 1,
|
| 13 |
+
# overlay frontend .next from Stage 2.
|
| 14 |
#
|
| 15 |
+
# Why three stages (not two):
|
| 16 |
+
# Three NestJS builds (backend+workers+cron) leave ~1-2 GB of residual
|
| 17 |
+
# RSS in the same container even after each `pnpm run build:*` exits,
|
| 18 |
+
# because the OS hasn't reclaimed all pages. `next build` alone needs
|
| 19 |
+
# ~3-4 GB RSS (V8 heap + SWC + native addons). Together they exceed
|
| 20 |
+
# the HF builder cgroup limit β OOMKilled (exit 137).
|
| 21 |
+
# Splitting frontend into its own stage gives it a clean address space.
|
| 22 |
+
#
|
| 23 |
+
# Container layout at runtime:
|
| 24 |
# - nginx (port 5000, internal) β Postiz frontend + backend + uploads
|
| 25 |
# - PM2 β 4 Postiz procs (backend/frontend/workers/cron)
|
| 26 |
# - postgres (port 5432, internal)
|
|
|
|
| 29 |
# - health-server.js (port 7860, public) β dashboard + reverse proxy
|
| 30 |
# ============================================================================
|
| 31 |
|
| 32 |
+
# ββ Stage 1: Clone, patch, install deps, build server apps βββββββββββββββββββ
|
| 33 |
FROM node:22.20-alpine AS postiz-builder
|
| 34 |
|
| 35 |
WORKDIR /build
|
|
|
|
| 49 |
# Pinned to v2.11.3 β last release before Temporal became a hard requirement.
|
| 50 |
RUN git clone --depth=1 --branch v2.11.3 https://github.com/gitroomhq/postiz-app.git .
|
| 51 |
|
| 52 |
+
# Patch Next.js config:
|
| 53 |
+
# 1. basePath/assetPrefix=/app β Postiz UI mounts at /app; dashboard owns /
|
| 54 |
+
# 2. productionBrowserSourceMaps: false β saves ~500 MB RSS during emit
|
| 55 |
+
# 3. Sentry sourcemap plugin: disable: true β saves another ~300 MB
|
| 56 |
+
# 4. experimental.cpus=1 + workerThreads=false β single-thread webpack;
|
| 57 |
+
# no parallel worker copies of the module graph in memory
|
|
|
|
|
|
|
|
|
|
| 58 |
RUN sed -i "s|const nextConfig = {|const nextConfig = {\n basePath: '/app',\n assetPrefix: '/app',|" apps/frontend/next.config.js \
|
| 59 |
&& sed -i "s|productionBrowserSourceMaps: true|productionBrowserSourceMaps: false|" apps/frontend/next.config.js \
|
| 60 |
&& sed -i "s|disable: false,|disable: true,|" apps/frontend/next.config.js \
|
|
|
|
| 64 |
&& grep -q "cpus: 1" apps/frontend/next.config.js \
|
| 65 |
|| (echo "PATCH FAILED β next.config.js shape changed upstream"; exit 1)
|
| 66 |
|
| 67 |
+
# Sentry env stubs β keep transitive Sentry imports from doing network calls.
|
|
|
|
| 68 |
ENV SENTRY_DSN="" \
|
| 69 |
SENTRY_AUTH_TOKEN="" \
|
| 70 |
SENTRY_ORG="" \
|
|
|
|
| 73 |
NEXT_TELEMETRY_DISABLED=1 \
|
| 74 |
NEXT_PRIVATE_SKIP_SIZE_MINIMIZATION=true
|
| 75 |
|
| 76 |
+
# Install all deps (shared pnpm virtual store for all workspace packages).
|
| 77 |
RUN pnpm install --frozen-lockfile=false
|
| 78 |
|
| 79 |
+
# Build server-side apps sequentially at 3 GB heap each.
|
| 80 |
+
# Frontend is intentionally excluded β built in its own stage below.
|
| 81 |
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:backend
|
| 82 |
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:workers
|
| 83 |
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:cron
|
|
|
|
| 84 |
|
| 85 |
+
# Clean up dev artefacts before Stage 3 copies this tree into the runtime image.
|
| 86 |
RUN find . -name ".git" -type d -prune -exec rm -rf {} + 2>/dev/null || true \
|
| 87 |
&& rm -rf .github reports Jenkins .devcontainer 2>/dev/null || true
|
| 88 |
|
| 89 |
|
| 90 |
+
# ββ Stage 2: Build Next.js frontend in isolation ββββββββββββββββββββββββββββββ
|
| 91 |
+
FROM node:22.20-alpine AS postiz-frontend
|
| 92 |
+
|
| 93 |
+
WORKDIR /build
|
| 94 |
+
|
| 95 |
+
# pnpm must be present to run workspace scripts.
|
| 96 |
+
RUN npm install -g pnpm@10.6.1
|
| 97 |
+
|
| 98 |
+
# Copy the full build tree from Stage 1:
|
| 99 |
+
# - patched apps/frontend/next.config.js
|
| 100 |
+
# - node_modules (pnpm virtual store, all symlinks intact within the tree)
|
| 101 |
+
# - already-built server apps (needed for any cross-package type references)
|
| 102 |
+
# Stage 1's processes are dead here β its RSS is freed by the OS.
|
| 103 |
+
# next build therefore starts with a clean address space.
|
| 104 |
+
COPY --from=postiz-builder /build /build
|
| 105 |
+
|
| 106 |
+
ENV NEXT_TELEMETRY_DISABLED=1 \
|
| 107 |
+
NEXT_PRIVATE_SKIP_SIZE_MINIMIZATION=true \
|
| 108 |
+
SENTRY_DSN="" \
|
| 109 |
+
SENTRY_AUTH_TOKEN="" \
|
| 110 |
+
SENTRY_ORG="" \
|
| 111 |
+
SENTRY_PROJECT="" \
|
| 112 |
+
NEXT_PUBLIC_SENTRY_DSN=""
|
| 113 |
+
|
| 114 |
+
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:frontend
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# ββ Stage 3: Runtime ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 118 |
FROM node:22.20-alpine
|
| 119 |
|
| 120 |
WORKDIR /app
|
|
|
|
| 148 |
huggingface_hub \
|
| 149 |
PyYAML
|
| 150 |
|
| 151 |
+
# Copy server-side build (backend + workers + cron + node_modules, cleaned).
|
| 152 |
COPY --from=postiz-builder /build /app
|
| 153 |
|
| 154 |
+
# Overlay the compiled Next.js frontend from its isolated build stage.
|
| 155 |
+
COPY --from=postiz-frontend /build/apps/frontend/.next /app/apps/frontend/.next
|
| 156 |
+
|
| 157 |
+
# Use upstream's nginx.conf β routes /apiβ3000, /uploadsβfs, /β4200.
|
| 158 |
+
# health-server strips /app before forwarding, so nginx sees expected paths.
|
| 159 |
COPY --from=postiz-builder /build/var/docker/nginx.conf /etc/nginx/nginx.conf
|
| 160 |
|
| 161 |
# Health-server lives outside /app so its node_modules don't collide with
|