File size: 9,826 Bytes
b4d7c1c
 
 
866a8e6
 
b4d7c1c
866a8e6
 
 
b4d7c1c
866a8e6
 
 
 
 
 
 
a83da60
 
a1ea643
 
b4d7c1c
 
866a8e6
b4d7c1c
 
 
866a8e6
b4d7c1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ecb798
 
 
 
 
 
 
 
 
 
 
 
 
6c0455b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ecb798
866a8e6
 
 
2ecb798
866a8e6
2ecb798
c63d0d1
8de0b02
 
7fec572
2ecb798
 
 
b4d7c1c
c63d0d1
8de0b02
a1ea643
7fec572
8de0b02
 
866a8e6
 
8de0b02
 
 
b4d7c1c
866a8e6
b4d7c1c
 
 
 
866a8e6
b4d7c1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
866a8e6
 
b4d7c1c
 
866a8e6
0394ebe
 
 
 
 
b4d7c1c
c4c4e4c
 
 
 
 
 
 
57b8b04
 
 
 
 
0394ebe
57b8b04
0394ebe
b4d7c1c
a1ea643
b4d7c1c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5d2b465
b4d7c1c
fd4cae0
 
 
 
 
 
 
 
 
 
5d2b465
b4d7c1c
 
 
866a8e6
b4d7c1c
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# ============================================================================
# HuggingPost β€” Postiz v2.11.3 on Hugging Face Spaces
#
# Two-stage build: compile only the server-side apps (backend/workers/cron)
# during docker build. The Next.js frontend is intentionally NOT built here.
#
# Why: `next build` for Postiz needs ~4 GB RSS. The HF Space builder has a
# ~4 GB cgroup limit, so it always OOMKills the process (exit 137) regardless
# of heap tuning, parallel/single-thread settings, or multi-stage tricks.
#
# Solution: build the frontend in start.sh at container startup, where the
# runtime has 16 GB RAM. The compiled .next is included in the HF Dataset
# backup so subsequent restarts skip the build entirely.
#
# First boot:  ~5-8 min (server apps start immediately; frontend compiles in
#              background, then Postiz frontend process starts when done).
# Later boots: .next restored from backup β†’ Postiz starts normally (~90 s).
#
# Container layout at runtime:
#   - nginx (port 5000, internal)         β€” Postiz frontend + backend + uploads
#   - PM2 β†’ 4 Postiz procs (backend / frontend / workers / cron)
#   - postgres (port 5432, internal)
#   - redis    (port 6379, internal)
#   - postiz-sync.py loop                 β€” backup DB + uploads + .next
#   - health-server.js (port 7860, public) β€” dashboard + reverse proxy
# ============================================================================

# ── Stage 1: Clone, patch, install deps, build server apps ───────────────────
FROM node:22.20-alpine AS postiz-builder

WORKDIR /build

ARG NEXT_PUBLIC_VERSION=v2.11.3
ENV NEXT_PUBLIC_VERSION=$NEXT_PUBLIC_VERSION

RUN apk add --no-cache \
    git \
    g++ \
    make \
    py3-pip \
    bash

RUN npm install -g pnpm@10.6.1

# Pinned to v2.11.3 β€” last release before Temporal became a hard requirement.
RUN git clone --depth=1 --branch v2.11.3 https://github.com/gitroomhq/postiz-app.git .

ENV SENTRY_DSN="" \
    SENTRY_AUTH_TOKEN="" \
    SENTRY_ORG="" \
    SENTRY_PROJECT="" \
    NEXT_PUBLIC_SENTRY_DSN="" \
    NEXT_TELEMETRY_DISABLED=1 \
    NEXT_PRIVATE_SKIP_SIZE_MINIMIZATION=true

# Install deps BEFORE patching so the install layer is cached independently.
# Patches are sed commands on JS files β€” they don't affect node_modules.
# Changing patches won't bust the pnpm install cache layer.
RUN pnpm install --frozen-lockfile=false

# Vendor Plus Jakarta Sans β€” HF Space cannot reach fonts.gstatic.com.
# Postiz upstream imports `Plus_Jakarta_Sans` from `next/font/google` in two
# layout files, which forces a build-time fetch from gstatic. HF egress to
# gstatic is blocked/throttled, so `next build` retry-loops indefinitely.
# We ship 4 woff2 variants in the image and rewrite both layouts to use
# `next/font/local` (zero network at build).
COPY vendor/fonts/PlusJakartaSans-500-normal.woff2 \
     vendor/fonts/PlusJakartaSans-500-italic.woff2 \
     vendor/fonts/PlusJakartaSans-600-normal.woff2 \
     vendor/fonts/PlusJakartaSans-600-italic.woff2 \
     apps/frontend/src/fonts/
COPY vendor/patch-jakarta-font.js /tmp/patch-jakarta-font.js
RUN node /tmp/patch-jakarta-font.js \
    && grep -q "next/font/local" "apps/frontend/src/app/(app)/layout.tsx" \
    && grep -q "next/font/local" "apps/frontend/src/app/(extension)/layout.tsx" \
    && ! grep -rq "next/font/google" apps/frontend/src/app/ \
    || (echo "JAKARTA PATCH FAILED β€” layout.tsx shape changed upstream"; exit 1)

# Patch Next.js config (after install β€” see above).
#   1. basePath/assetPrefix=/app   β†’ Postiz UI at /app; HuggingPost dashboard owns /
#   2. productionBrowserSourceMaps: false  β†’ smaller build output
#   3. Sentry sourcemap plugin disabled   β†’ no network calls during build
#   4. swcMinify: false  β†’ Terser (pure JS) instead of native SWC binary
#   5. experimental.cpus=1 + workerThreads=false  β†’ single-thread webpack
#   6. images.unoptimized: true  β†’ skip _next/image optimizer (fails with basePath)
RUN sed -i "s|const nextConfig = {|const nextConfig = {\n  basePath: '/app',\n  assetPrefix: '/app',\n  trailingSlash: true,\n  swcMinify: false,|" apps/frontend/next.config.js \
    && sed -i "s|productionBrowserSourceMaps: true|productionBrowserSourceMaps: false|" apps/frontend/next.config.js \
    && sed -i "s|disable: false,|disable: true,|" apps/frontend/next.config.js \
    && sed -i "s|experimental: {|experimental: {\n    cpus: 1,\n    workerThreads: false,|" apps/frontend/next.config.js \
    && (grep -q 'images:' apps/frontend/next.config.js \
        && sed -i 's|images: {|images: {\n    unoptimized: true,|' apps/frontend/next.config.js \
        || sed -i "s|const nextConfig = {|const nextConfig = {\n  images: { unoptimized: true },|" apps/frontend/next.config.js) \
    && grep -q "basePath: '/app'" apps/frontend/next.config.js \
    && grep -q "trailingSlash: true" apps/frontend/next.config.js \
    && grep -q "productionBrowserSourceMaps: false" apps/frontend/next.config.js \
    && grep -q "swcMinify: false" apps/frontend/next.config.js \
    && grep -q "cpus: 1" apps/frontend/next.config.js \
    || (echo "PATCH FAILED β€” next.config.js shape changed upstream"; exit 1)

# Build server-side apps. Sequential + 3 GB heap each.
# Frontend is NOT built here β€” see start.sh.
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:backend
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:workers
RUN NODE_OPTIONS="--max-old-space-size=3072" pnpm run build:cron

# Clean up dev artefacts before Stage 2 copies this tree into the runtime image.
RUN find . -name ".git" -type d -prune -exec rm -rf {} + 2>/dev/null || true \
 && rm -rf .github reports Jenkins .devcontainer 2>/dev/null || true


# ── Stage 2: Runtime ──────────────────────────────────────────────────────────
FROM node:22.20-alpine

WORKDIR /app

RUN apk add --no-cache \
    bash \
    curl \
    ca-certificates \
    openssl \
    jq \
    nginx \
    postgresql16 \
    postgresql16-contrib \
    postgresql16-client \
    redis \
    py3-pip \
    su-exec

# nginx user β€” upstream uses 'www'. Mirror that so its nginx.conf works.
RUN adduser -D -g 'www' www \
    && mkdir -p /var/lib/nginx /var/log/nginx \
    && chown -R www:www /var/lib/nginx

RUN npm install -g pnpm@10.6.1 pm2

RUN pip install --no-cache-dir --break-system-packages \
    huggingface_hub \
    PyYAML

# Copy fully-built Postiz server apps + node_modules + patched next.config.js.
# .next is intentionally absent here; start.sh builds or restores it at boot.
COPY --from=postiz-builder /build /app

# nginx.conf: routes /api→3000, /uploads→fs, /→4200.
# Patch: re-add /app prefix before proxying to Next.js (port 4200) because:
#   health-server strips /app from incoming /app/* requests before forwarding
#   to nginx. Next.js is built with basePath="/app" so it expects /app/* paths.
#   Without the patch, nginx sends /auth/login β†’ Next.js returns 404.
#   With the patch, nginx sends /app/auth/login β†’ Next.js handles it correctly.
COPY --from=postiz-builder /build/var/docker/nginx.conf /etc/nginx/nginx.conf
# Patch 1: re-add /app basePath when proxying to Next.js (port 4200).
#   health-server strips /app before forwarding to nginx, but Next.js is built
#   with basePath="/app" so it expects paths prefixed with /app.
# Patch 2: add an explicit location = / block that redirects to /app/ so
#   the bare root doesn't return an empty 200 from nginx's default handler.
#   (health-server already short-circuits /app/ before reaching nginx, but
#    this makes nginx self-consistent for any direct curl / health checks.)
RUN sed -i \
      's|proxy_pass http://127.0.0.1:4200/;|proxy_pass http://127.0.0.1:4200/app/;|; \
       s|proxy_pass http://localhost:4200/;|proxy_pass http://localhost:4200/app/;|; \
       s|proxy_set_header X-Forwarded-Proto \$scheme;|proxy_set_header X-Forwarded-Proto \$http_x_forwarded_proto;|g' \
      /etc/nginx/nginx.conf \
    && grep -q '/app/' /etc/nginx/nginx.conf \
    && grep -q 'x_forwarded_proto' /etc/nginx/nginx.conf \
    || (echo "NGINX PATCH FAILED β€” upstream nginx.conf format changed"; cat /etc/nginx/nginx.conf; exit 1)

# Health-server outside /app to avoid pnpm workspace collisions.
RUN mkdir -p /opt/healthsrv && cd /opt/healthsrv && \
    npm init -y >/dev/null && \
    npm install --no-save --no-audit --no-fund express@4 cors morgan

RUN mkdir -p /var/run/postgresql /postiz/pgdata /postiz/redis /postiz/uploads /postiz/.secrets \
    && chown -R postgres:postgres /var/run/postgresql /postiz/pgdata \
    && chmod 700 /postiz/pgdata

RUN ln -sf /postiz/uploads /uploads

COPY start.sh /opt/start.sh
COPY health-server.js /opt/healthsrv/health-server.js
COPY postiz-sync.py /opt/postiz-sync.py
COPY cloudflare-proxy.js /opt/cloudflare-proxy.js
COPY cloudflare-proxy-setup.py /opt/cloudflare-proxy-setup.py
COPY cloudflare-keepalive-setup.py /opt/cloudflare-keepalive-setup.py

# Vendor fonts + patch script available at runtime.
# Stage 1 may be cached from before the font patch was added; start.sh applies
# the patch at container start if layout.tsx still imports next/font/google.
COPY vendor/fonts/PlusJakartaSans-500-normal.woff2 \
     vendor/fonts/PlusJakartaSans-500-italic.woff2 \
     vendor/fonts/PlusJakartaSans-600-normal.woff2 \
     vendor/fonts/PlusJakartaSans-600-italic.woff2 \
     /opt/vendor/fonts/
COPY vendor/patch-jakarta-font.js /opt/vendor/patch-jakarta-font.js

RUN chmod +x /opt/start.sh /opt/cloudflare-keepalive-setup.py

EXPOSE 7860

HEALTHCHECK --interval=30s --timeout=10s --start-period=600s --retries=5 \
    CMD curl -f http://localhost:7860/health || exit 1

CMD ["/opt/start.sh"]