@plumbus/core 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/ai/__tests__/ai-service.test.js +27 -0
- package/dist/ai/__tests__/ai-service.test.js.map +1 -1
- package/dist/ai/__tests__/validation.test.js +33 -0
- package/dist/ai/__tests__/validation.test.js.map +1 -1
- package/dist/ai/ai-service.d.ts.map +1 -1
- package/dist/ai/ai-service.js +87 -77
- package/dist/ai/ai-service.js.map +1 -1
- package/dist/ai/validation.d.ts.map +1 -1
- package/dist/ai/validation.js +7 -1
- package/dist/ai/validation.js.map +1 -1
- package/dist/api/route-generator.d.ts.map +1 -1
- package/dist/api/route-generator.js +8 -0
- package/dist/api/route-generator.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +3 -0
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +3 -0
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/execution/__tests__/context-factory.test.js +16 -0
- package/dist/execution/__tests__/context-factory.test.js.map +1 -1
- package/dist/execution/context-factory.d.ts +2 -1
- package/dist/execution/context-factory.d.ts.map +1 -1
- package/dist/execution/context-factory.js +4 -0
- package/dist/execution/context-factory.js.map +1 -1
- package/dist/server/__tests__/bootstrap.test.js +13 -0
- package/dist/server/__tests__/bootstrap.test.js.map +1 -1
- package/dist/server/bootstrap.d.ts +2 -0
- package/dist/server/bootstrap.d.ts.map +1 -1
- package/dist/server/bootstrap.js +2 -1
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/testing/context.d.ts.map +1 -1
- package/dist/testing/context.js +15 -0
- package/dist/testing/context.js.map +1 -1
- package/dist/types/context.d.ts +22 -0
- package/dist/types/context.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/instructions/deployment.md +304 -68
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Deployment
|
|
2
2
|
|
|
3
|
-
Plumbus applications deploy as up to three services — a **backend** (Fastify API server), a **frontend** (Next.js), and optionally an **admin dashboard** (Next.js) — backed by PostgreSQL and Redis.
|
|
3
|
+
Plumbus applications deploy as up to three services — a **backend** (Fastify API server), a **frontend** (Next.js), and optionally an **admin dashboard** (Next.js) — backed by PostgreSQL and Redis.
|
|
4
4
|
|
|
5
5
|
## Service Topology
|
|
6
6
|
|
|
@@ -19,14 +19,116 @@ Plumbus applications deploy as up to three services — a **backend** (Fastify A
|
|
|
19
19
|
+-------+ +-------+
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
- **Frontend** and **admin** proxy API calls to the backend
|
|
22
|
+
- **Frontend** and **admin** proxy API calls to the backend server-side. Browsers never connect directly to the backend.
|
|
23
23
|
- **Backend** connects to PostgreSQL (data) and Redis (job queue, event dispatch).
|
|
24
24
|
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Docker Rules — MUST follow all of these
|
|
28
|
+
|
|
29
|
+
These rules are critical. Violating any one causes build failures or bloated images.
|
|
30
|
+
|
|
31
|
+
### Rule 1: Generate `.plumbus/` inside Docker
|
|
32
|
+
|
|
33
|
+
- **DO**: Run `plumbus generate` in a Docker build stage.
|
|
34
|
+
- **DO NOT**: Copy `.plumbus/` from the host. It may not exist in CI/fresh clones.
|
|
35
|
+
|
|
36
|
+
### Rule 2: Always copy `.npmrc` into Docker images
|
|
37
|
+
|
|
38
|
+
- **DO**: `COPY .npmrc ./` before `pnpm install`.
|
|
39
|
+
- **DO NOT**: Skip `.npmrc`. It has `public-hoist-pattern` entries required for `next build` to find `next`, `react`, `tailwindcss`.
|
|
40
|
+
- **Symptom if violated**: `Cannot find module 'next'` during build.
|
|
41
|
+
|
|
42
|
+
### Rule 3: Never copy `pnpm-workspace.yaml` with local dev overrides
|
|
43
|
+
|
|
44
|
+
- **DO**: Copy the clean `pnpm-workspace.yaml` (lists only `frontend` and `frontend-admin`).
|
|
45
|
+
- **DO NOT**: Copy a workspace yaml that contains `link:../Plumbus/...` dev overrides.
|
|
46
|
+
- **TIP**: If your project uses `dev:link`/`dev:unlink` scripts to swap workspace configs, make sure the clean version is active before building Docker images.
|
|
47
|
+
|
|
48
|
+
### Rule 4: Use `FROM deps AS build` (preserve pnpm symlinks in build stage)
|
|
49
|
+
|
|
50
|
+
- **DO**: `FROM deps AS build` — inherits the filesystem with intact symlinks.
|
|
51
|
+
- **DO NOT**: `FROM base AS build` with `COPY --from=deps /app/node_modules ./node_modules`. Docker COPY breaks pnpm symlinks.
|
|
52
|
+
|
|
53
|
+
### Rule 5: Use `pnpm deploy` to isolate frontend dependencies
|
|
54
|
+
|
|
55
|
+
- **DO**: Run `pnpm --filter {./frontend} deploy --legacy /app/deployed` to create a self-contained directory with flat `node_modules` containing ONLY the frontend's dependencies.
|
|
56
|
+
- **DO NOT**: Build Next.js from the workspace root with all monorepo dependencies visible. pnpm's symlinked store breaks Next.js file tracing, and hoisted layout includes every workspace package.
|
|
57
|
+
- **Why**: `pnpm deploy` creates isolated flat `node_modules` with just the target package's deps. Next.js standalone traces only what the frontend actually needs (~30-40 MB vs ~600+ MB).
|
|
58
|
+
|
|
59
|
+
### Rule 6: Never copy full `node_modules` into frontend runner stages
|
|
60
|
+
|
|
61
|
+
- **DO**: Copy ONLY `.next/standalone` and `.next/static` into the runner.
|
|
62
|
+
- **DO NOT**: `COPY --from=deps /app/node_modules ./node_modules` in the runner stage.
|
|
63
|
+
- **Why**: Standalone output already includes all traced modules (~60-80 MB). Copying full `node_modules` adds ~700 MB for no reason.
|
|
64
|
+
- **Target image size**: Frontend/admin images should be ~150-200 MB total, not 800 MB+.
|
|
65
|
+
|
|
66
|
+
### Rule 7: Strip frontend-only packages from backend prod deps
|
|
67
|
+
|
|
68
|
+
- **DO**: Create a separate `proddeps` stage that removes `@plumbus/ui` from `package.json` before `npm install --omit=dev`.
|
|
69
|
+
- **DO NOT**: Copy the full `node_modules` (including `@plumbus/ui`) into the backend runner.
|
|
70
|
+
- **Why**: `@plumbus/ui` pulls in `next`, `react`, `react-dom`, `tailwindcss`, `lucide-react` (~300 MB). The backend only needs `@plumbus/core` at runtime.
|
|
71
|
+
- **Target image size**: Backend should be ~500-700 MB, not 1.5 GB+.
|
|
72
|
+
|
|
73
|
+
### Rule 8: Use `ENV PATH` instead of `npx` for frontend builds
|
|
74
|
+
|
|
75
|
+
- **DO**: `ENV PATH="/app/deployed/node_modules/.bin:$PATH"` then `RUN next build`.
|
|
76
|
+
- **DO NOT**: `RUN npx next build`.
|
|
77
|
+
- **Why**: With `pnpm deploy`, binaries are in the deployed directory's `node_modules/.bin/`. Adding it to PATH ensures `next` resolves correctly.
|
|
78
|
+
|
|
79
|
+
### Rule 9: Never run migrations in the entrypoint
|
|
80
|
+
|
|
81
|
+
- **DO**: Run `npx plumbus migrate apply` as a separate CI/CD step or one-off job.
|
|
82
|
+
- **DO NOT**: Put migration commands in `entrypoint.sh` or Docker init containers.
|
|
83
|
+
- **Why**: In multi-replica deployments, concurrent migrations corrupt data.
|
|
84
|
+
|
|
85
|
+
### Rule 10: Use dynamic environment detection in `app.config.ts`
|
|
86
|
+
|
|
87
|
+
- **DO**: `environment: (process.env.NODE_ENV === 'production' ? 'production' : 'development')`
|
|
88
|
+
- **DO NOT**: Hardcode `environment: 'development'`. This causes `plumbus start` to skip production safeguards in Docker.
|
|
89
|
+
|
|
90
|
+
### Rule 11: Regenerate client types before building frontends
|
|
91
|
+
|
|
92
|
+
- **DO**: Run `plumbus ui generate` before `next build` if capability schemas changed.
|
|
93
|
+
- **DO NOT**: Build frontends with stale `frontend/lib/client.ts`.
|
|
94
|
+
|
|
95
|
+
### Rule 12: Set `output: "standalone"` in all `next.config.mjs` files
|
|
96
|
+
|
|
97
|
+
- **DO**: Add `output: "standalone"` to `next.config.mjs` for every Next.js app (frontend, admin).
|
|
98
|
+
- **DO NOT**: Deploy without standalone output. The app would need full `node_modules` at runtime.
|
|
99
|
+
|
|
100
|
+
### Rule 13: Add `outputFileTracingExcludes` in `next.config.mjs`
|
|
101
|
+
|
|
102
|
+
- **DO**: Exclude build/dev tools that are transitive deps of `@plumbus/ui` but not needed at runtime:
|
|
103
|
+
```js
|
|
104
|
+
outputFileTracingExcludes: {
|
|
105
|
+
"**": [
|
|
106
|
+
"./node_modules/typescript/**",
|
|
107
|
+
"./node_modules/@swc/core*/**",
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
```
|
|
111
|
+
- **Why**: Even with `pnpm deploy`, some transitive deps (typescript, @swc/core) are present via `@plumbus/ui` but unnecessary at runtime. Excluding them saves ~50 MB.
|
|
112
|
+
|
|
113
|
+
### Rule 14: Clean up test/build tools from backend `node_modules`
|
|
114
|
+
|
|
115
|
+
- **DO**: After `npm install --omit=dev` in the backend `proddeps` stage, delete transitive deps only needed for testing/building:
|
|
116
|
+
```dockerfile
|
|
117
|
+
RUN npm install --omit=dev && \
|
|
118
|
+
rm -rf node_modules/@vitest node_modules/vitest \
|
|
119
|
+
node_modules/playwright-core node_modules/playwright \
|
|
120
|
+
node_modules/@esbuild node_modules/esbuild \
|
|
121
|
+
node_modules/typescript
|
|
122
|
+
```
|
|
123
|
+
- **Why**: `@plumbus/core` includes vitest, playwright, esbuild, and typescript as dependencies. The backend doesn't need them at runtime.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
25
127
|
## Backend Deployment
|
|
26
128
|
|
|
27
129
|
### Production Command
|
|
28
130
|
|
|
29
|
-
Use `plumbus start` for production
|
|
131
|
+
Use `plumbus start` for production:
|
|
30
132
|
|
|
31
133
|
| Behavior | `plumbus dev` | `plumbus start` |
|
|
32
134
|
|----------|---------------|------------------|
|
|
@@ -43,7 +145,7 @@ npx plumbus start --port 3000
|
|
|
43
145
|
|
|
44
146
|
### Runtime File Requirements
|
|
45
147
|
|
|
46
|
-
|
|
148
|
+
These directories **must be present** in the production image:
|
|
47
149
|
|
|
48
150
|
| Directory | Purpose |
|
|
49
151
|
|-----------|---------|
|
|
@@ -56,18 +158,20 @@ The backend uses dynamic resource discovery at startup — it scans the source t
|
|
|
56
158
|
|
|
57
159
|
### Entrypoint Pattern
|
|
58
160
|
|
|
59
|
-
**CRITICAL**: Do **not** run migrations automatically in the container entrypoint. In multi-replica deployments (Kubernetes, ECS), concurrent migration runs can corrupt data or cause extended downtime. Run migrations as a separate, deliberate step before deploying new containers.
|
|
60
|
-
|
|
61
161
|
```sh
|
|
62
|
-
#
|
|
63
|
-
npx plumbus migrate apply
|
|
64
|
-
|
|
65
|
-
# Then deploy. Container entrypoint only starts the server:
|
|
162
|
+
# entrypoint.sh — only starts the server. No migrations here.
|
|
66
163
|
#!/bin/sh
|
|
67
164
|
set -e
|
|
68
165
|
exec npx plumbus start --port 3000
|
|
69
166
|
```
|
|
70
167
|
|
|
168
|
+
Migrations run separately:
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
# Run BEFORE deploying new containers (CI/CD step or one-off job):
|
|
172
|
+
npx plumbus migrate apply
|
|
173
|
+
```
|
|
174
|
+
|
|
71
175
|
### Health Endpoints
|
|
72
176
|
|
|
73
177
|
| Endpoint | Purpose | Healthy response |
|
|
@@ -77,11 +181,13 @@ exec npx plumbus start --port 3000
|
|
|
77
181
|
|
|
78
182
|
Use `/ready` for container health checks and load balancer probes.
|
|
79
183
|
|
|
184
|
+
---
|
|
185
|
+
|
|
80
186
|
## Frontend Deployment
|
|
81
187
|
|
|
82
188
|
### Standalone Output
|
|
83
189
|
|
|
84
|
-
|
|
190
|
+
Add to `next.config.mjs`:
|
|
85
191
|
|
|
86
192
|
```js
|
|
87
193
|
const nextConfig = {
|
|
@@ -97,18 +203,12 @@ const nextConfig = {
|
|
|
97
203
|
npx next build
|
|
98
204
|
|
|
99
205
|
# Run standalone server
|
|
100
|
-
node .next/standalone/<app-dir>/server.js
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Set `PORT` and `HOSTNAME` environment variables to control the listening address:
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
206
|
PORT=3001 HOSTNAME=0.0.0.0 node .next/standalone/frontend/server.js
|
|
107
207
|
```
|
|
108
208
|
|
|
109
209
|
### Static Assets
|
|
110
210
|
|
|
111
|
-
After a standalone build, static files
|
|
211
|
+
After a standalone build, copy static files alongside the server:
|
|
112
212
|
|
|
113
213
|
```
|
|
114
214
|
.next/standalone/ # Server code
|
|
@@ -117,9 +217,11 @@ After a standalone build, static files live in `.next/static/`. Copy them to the
|
|
|
117
217
|
|
|
118
218
|
### Environment Variables
|
|
119
219
|
|
|
120
|
-
- `NEXT_PUBLIC_*`
|
|
121
|
-
- `API_BASE_URL`
|
|
122
|
-
- `AUTH_SECRET`
|
|
220
|
+
- `NEXT_PUBLIC_*` — inlined at **build time**. Set before `next build`, not at runtime.
|
|
221
|
+
- `API_BASE_URL` — set at **runtime**. Internal Docker network address (e.g., `http://backend:3000`).
|
|
222
|
+
- `AUTH_SECRET` — required for cookie signing.
|
|
223
|
+
|
|
224
|
+
---
|
|
123
225
|
|
|
124
226
|
## Environment Variables Reference
|
|
125
227
|
|
|
@@ -140,6 +242,7 @@ After a standalone build, static files live in `.next/static/`. Copy them to the
|
|
|
140
242
|
| `AI_OPENAI_API_KEY` | If using OpenAI | — | OpenAI API key |
|
|
141
243
|
| `AI_OPENAI_MODEL` | No | `gpt-4o-mini` | Default OpenAI model |
|
|
142
244
|
| `AI_DEFAULT_MODEL` | No | `gpt-4o` | Fallback model |
|
|
245
|
+
| `TRUST_PROXY` | No | — | Set to `true` behind a reverse proxy so `request.ip` reflects `X-Forwarded-For` |
|
|
143
246
|
|
|
144
247
|
### Frontend / Admin
|
|
145
248
|
|
|
@@ -148,89 +251,147 @@ After a standalone build, static files live in `.next/static/`. Copy them to the
|
|
|
148
251
|
| `NEXT_PUBLIC_API_BASE_URL` | Build | Public API URL (empty string if using server-side proxy) |
|
|
149
252
|
| `API_BASE_URL` | Runtime | Backend URL for server-side proxy (e.g., `http://backend:3000`) |
|
|
150
253
|
| `AUTH_SECRET` | Runtime | Cookie signing secret |
|
|
254
|
+
| `AUTH_COOKIE_SECURE` | Runtime | Set to `"true"` for TLS, `"false"` for HTTP-only deployments |
|
|
255
|
+
|
|
256
|
+
---
|
|
151
257
|
|
|
152
258
|
## Docker
|
|
153
259
|
|
|
154
|
-
###
|
|
260
|
+
### Expected Image Sizes
|
|
261
|
+
|
|
262
|
+
| Image | Target Size | Too Large If |
|
|
263
|
+
|-------|-------------|--------------|
|
|
264
|
+
| Frontend | ~150-200 MB | > 400 MB |
|
|
265
|
+
| Admin | ~150-200 MB | > 400 MB |
|
|
266
|
+
| Backend | ~500-700 MB | > 1 GB |
|
|
267
|
+
|
|
268
|
+
If images exceed these sizes, you are likely not using `pnpm deploy` (Rule 5) or not stripping backend deps (Rule 7, Rule 14).
|
|
269
|
+
|
|
270
|
+
### Backend Dockerfile
|
|
155
271
|
|
|
156
272
|
```dockerfile
|
|
273
|
+
# ==============================================================
|
|
274
|
+
# BACKEND DOCKERFILE — Reference template
|
|
275
|
+
# See "Docker Rules" section above for the rationale behind each step.
|
|
276
|
+
# ==============================================================
|
|
277
|
+
|
|
157
278
|
# ---- Base ----
|
|
158
279
|
FROM node:22-alpine AS base
|
|
159
280
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
160
281
|
WORKDIR /app
|
|
161
282
|
|
|
162
|
-
# ----
|
|
283
|
+
# ---- Full dependencies (needed for plumbus generate and pnpm build) ----
|
|
163
284
|
FROM base AS deps
|
|
164
|
-
#
|
|
165
|
-
# (link:../Plumbus/...) that don't exist in Docker. Packages resolve from npm.
|
|
285
|
+
# DO NOT copy pnpm-workspace.yaml — it may have local dev overrides.
|
|
166
286
|
COPY package.json pnpm-lock.yaml ./
|
|
167
287
|
RUN pnpm install
|
|
168
288
|
|
|
289
|
+
# ---- Production dependencies only ----
|
|
290
|
+
# Strip @plumbus/ui and devDependencies — backend doesn't need them at runtime.
|
|
291
|
+
# This saves ~300 MB (next, react, tailwindcss, etc. are excluded).
|
|
292
|
+
# After install, remove test/build tools from @plumbus/core transitive deps.
|
|
293
|
+
FROM node:22-alpine AS proddeps
|
|
294
|
+
WORKDIR /app
|
|
295
|
+
COPY package.json ./
|
|
296
|
+
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
|
297
|
+
RUN node -e "\
|
|
298
|
+
const p = JSON.parse(require('fs').readFileSync('package.json','utf8'));\
|
|
299
|
+
for (const k of ['@plumbus/ui'])\
|
|
300
|
+
delete (p.dependencies||{})[k];\
|
|
301
|
+
delete p.devDependencies;\
|
|
302
|
+
require('fs').writeFileSync('package.json', JSON.stringify(p, null, 2));" && \
|
|
303
|
+
npm install --omit=dev && \
|
|
304
|
+
rm -rf node_modules/@vitest node_modules/vitest \
|
|
305
|
+
node_modules/playwright-core node_modules/playwright \
|
|
306
|
+
node_modules/@esbuild node_modules/esbuild \
|
|
307
|
+
node_modules/typescript
|
|
308
|
+
|
|
309
|
+
# ---- Generate .plumbus/ ----
|
|
310
|
+
# DO: Generate inside Docker. DO NOT: Copy from host.
|
|
311
|
+
FROM deps AS generate
|
|
312
|
+
COPY app/ ./app/
|
|
313
|
+
COPY config/ ./config/
|
|
314
|
+
COPY tsconfig.json ./
|
|
315
|
+
RUN npx plumbus generate
|
|
316
|
+
|
|
169
317
|
# ---- Build ----
|
|
170
|
-
FROM
|
|
171
|
-
|
|
318
|
+
# Uses "FROM deps" to preserve pnpm symlinks (Rule 4).
|
|
319
|
+
FROM deps AS build
|
|
320
|
+
COPY --from=generate /app/.plumbus/ ./.plumbus/
|
|
172
321
|
COPY package.json tsconfig.json ./
|
|
173
322
|
COPY app/ ./app/
|
|
174
323
|
COPY config/ ./config/
|
|
175
|
-
COPY .plumbus/ ./.plumbus/
|
|
176
324
|
COPY drizzle/ ./drizzle/
|
|
177
325
|
RUN pnpm build
|
|
178
326
|
|
|
179
327
|
# ---- Runner ----
|
|
180
328
|
FROM node:22-alpine AS runner
|
|
181
|
-
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
182
329
|
WORKDIR /app
|
|
183
330
|
ENV NODE_ENV=production
|
|
184
331
|
RUN addgroup --system --gid 1001 plumbus && \
|
|
185
332
|
adduser --system --uid 1001 plumbus
|
|
186
|
-
|
|
333
|
+
# Production-only node_modules from proddeps (no @plumbus/ui, no devDeps).
|
|
334
|
+
COPY --from=proddeps /app/node_modules ./node_modules
|
|
187
335
|
COPY --from=build /app/dist ./dist
|
|
336
|
+
COPY --from=generate /app/.plumbus/ ./.plumbus/
|
|
188
337
|
COPY package.json ./
|
|
189
338
|
COPY app/ ./app/
|
|
190
339
|
COPY config/ ./config/
|
|
191
|
-
COPY .plumbus/ ./.plumbus/
|
|
192
340
|
COPY drizzle/ ./drizzle/
|
|
193
|
-
# Entrypoint starts the production server
|
|
194
341
|
USER plumbus
|
|
195
342
|
EXPOSE 3000
|
|
196
|
-
HEALTHCHECK CMD
|
|
343
|
+
HEALTHCHECK CMD wget -q -O /dev/null http://localhost:3000/ready || exit 1
|
|
197
344
|
ENTRYPOINT ["./entrypoint.sh"]
|
|
198
345
|
```
|
|
199
346
|
|
|
200
|
-
### Frontend Dockerfile
|
|
347
|
+
### Frontend Dockerfile
|
|
348
|
+
|
|
349
|
+
For the **admin dashboard**, replace `{./frontend}` with `{./frontend-admin}`, change port 3001 to 3002, and adjust the package filter accordingly.
|
|
201
350
|
|
|
202
351
|
```dockerfile
|
|
352
|
+
# ==============================================================
|
|
353
|
+
# FRONTEND DOCKERFILE — Reference template
|
|
354
|
+
# Uses pnpm deploy to isolate frontend deps from the monorepo.
|
|
355
|
+
# See "Docker Rules" section above for the rationale behind each step.
|
|
356
|
+
# ==============================================================
|
|
357
|
+
|
|
203
358
|
FROM node:22-alpine AS base
|
|
204
359
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
205
360
|
WORKDIR /app
|
|
206
361
|
|
|
362
|
+
# ---- Install full workspace (needed for pnpm deploy to resolve deps) ----
|
|
207
363
|
FROM base AS deps
|
|
208
|
-
COPY package.json pnpm-lock.yaml ./
|
|
364
|
+
COPY .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
209
365
|
COPY frontend/package.json ./frontend/
|
|
210
|
-
|
|
211
|
-
RUN printf 'packages:\n - "frontend"\n' > pnpm-workspace.yaml
|
|
366
|
+
COPY frontend-admin/package.json ./frontend-admin/
|
|
212
367
|
RUN pnpm install
|
|
213
368
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
369
|
+
# ---- Deploy + Build ----
|
|
370
|
+
# pnpm deploy creates a self-contained directory with flat node_modules
|
|
371
|
+
# containing ONLY the frontend's dependencies — no backend/root workspace deps.
|
|
372
|
+
FROM deps AS build
|
|
217
373
|
COPY frontend/ ./frontend/
|
|
218
|
-
|
|
219
|
-
|
|
374
|
+
RUN pnpm --filter {./frontend} deploy --legacy /app/deployed
|
|
375
|
+
ENV PATH="/app/deployed/node_modules/.bin:$PATH"
|
|
376
|
+
WORKDIR /app/deployed
|
|
377
|
+
RUN next build
|
|
220
378
|
|
|
379
|
+
# ---- Runner ----
|
|
380
|
+
# Standalone output only — includes server.js + traced node_modules (~30-40 MB).
|
|
381
|
+
# DO NOT copy node_modules from deps — standalone has everything it needs.
|
|
221
382
|
FROM node:22-alpine AS runner
|
|
222
383
|
WORKDIR /app
|
|
223
384
|
ENV NODE_ENV=production
|
|
224
385
|
RUN addgroup --system --gid 1001 nextjs && \
|
|
225
386
|
adduser --system --uid 1001 nextjs
|
|
226
|
-
COPY --from=build /app/
|
|
227
|
-
COPY --from=build /app/
|
|
387
|
+
COPY --from=build /app/deployed/.next/standalone ./
|
|
388
|
+
COPY --from=build /app/deployed/.next/static ./deployed/.next/static
|
|
228
389
|
USER nextjs
|
|
229
390
|
EXPOSE 3001
|
|
230
|
-
CMD ["node", "
|
|
391
|
+
CMD ["node", "deployed/server.js"]
|
|
231
392
|
```
|
|
232
393
|
|
|
233
|
-
### Docker Compose
|
|
394
|
+
### Docker Compose
|
|
234
395
|
|
|
235
396
|
```yaml
|
|
236
397
|
services:
|
|
@@ -251,18 +412,66 @@ services:
|
|
|
251
412
|
depends_on: [backend]
|
|
252
413
|
```
|
|
253
414
|
|
|
254
|
-
###
|
|
415
|
+
### .dockerignore
|
|
255
416
|
|
|
256
|
-
|
|
257
|
-
- **Dependency resolution**: `@plumbus/core` and `@plumbus/ui` are declared as normal dependencies in `package.json`. Without the workspace overrides, `pnpm install` resolves them from the npm registry.
|
|
258
|
-
- **Volumes**: Mount persistent volumes for PostgreSQL data and the `uploads/` directory (user-generated media and exports).
|
|
259
|
-
- **Internal networking**: Frontends connect to backend via Docker service name (e.g., `http://backend:3000`). Set `API_BASE_URL` accordingly.
|
|
260
|
-
- **`NEXT_PUBLIC_API_BASE_URL`**: Set to empty string (`""`) if the frontend uses server-side proxy routes. The proxy route handler reads `API_BASE_URL` at runtime.
|
|
261
|
-
- **File layout**: Place all Docker files (Dockerfiles, compose, entrypoint, env template) in a `docker/` subdirectory. The `.dockerignore` stays at the project root (the build context). Set `context: ..` (parent) in compose `build:` sections so COPY paths resolve from the project root.
|
|
417
|
+
Place at the project root. Must include at minimum:
|
|
262
418
|
|
|
263
|
-
|
|
419
|
+
```
|
|
420
|
+
node_modules
|
|
421
|
+
.next
|
|
422
|
+
dist
|
|
423
|
+
.turbo
|
|
424
|
+
.git
|
|
425
|
+
uploads
|
|
426
|
+
**/e2e
|
|
427
|
+
docs
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### File Layout
|
|
431
|
+
|
|
432
|
+
Place all Docker files in a `docker/` subdirectory:
|
|
433
|
+
|
|
434
|
+
```
|
|
435
|
+
docker/
|
|
436
|
+
Dockerfile.backend
|
|
437
|
+
Dockerfile.frontend
|
|
438
|
+
Dockerfile.admin
|
|
439
|
+
docker-compose.yml
|
|
440
|
+
entrypoint-backend.sh
|
|
441
|
+
.env.example
|
|
442
|
+
.dockerignore # at project root (the build context)
|
|
443
|
+
```
|
|
264
444
|
|
|
265
|
-
|
|
445
|
+
Set `context: ..` in compose `build:` sections so COPY paths resolve from the project root.
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## HTTP Deployments Without TLS
|
|
450
|
+
|
|
451
|
+
`plumbus start` sets `secure: true` on auth cookies by default. If you deploy over plain HTTP (no TLS), browsers silently drop `Secure` cookies, causing infinite login redirect loops.
|
|
452
|
+
|
|
453
|
+
### Workaround
|
|
454
|
+
|
|
455
|
+
Set `AUTH_COOKIE_SECURE=false` in the environment. Frontend auth route handlers must read it:
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
// app/api/auth/login/route.ts
|
|
459
|
+
const isSecure = process.env.AUTH_COOKIE_SECURE === "true";
|
|
460
|
+
|
|
461
|
+
cookies().set("auth_token", token, {
|
|
462
|
+
httpOnly: true,
|
|
463
|
+
secure: isSecure,
|
|
464
|
+
sameSite: isSecure ? "strict" : "lax",
|
|
465
|
+
path: "/",
|
|
466
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
467
|
+
});
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Security trade-off** — always use TLS in production and set `AUTH_COOKIE_SECURE=true`.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Kubernetes / Helm
|
|
266
475
|
|
|
267
476
|
### Required Resources
|
|
268
477
|
|
|
@@ -276,11 +485,9 @@ The Docker images are self-contained and orchestrator-agnostic. When deploying t
|
|
|
276
485
|
| `Secret` | `DB_PASSWORD`, `AUTH_SECRET`, AI provider keys |
|
|
277
486
|
| `ConfigMap` | Non-sensitive config: `DB_HOST`, `AI_DEFAULT_PROVIDER`, etc. |
|
|
278
487
|
|
|
279
|
-
### Migrations
|
|
488
|
+
### Migrations — Critical
|
|
280
489
|
|
|
281
|
-
**NEVER** run migrations in the
|
|
282
|
-
|
|
283
|
-
Run migrations as a **separate Kubernetes Job** before rolling out new pods:
|
|
490
|
+
**NEVER** run migrations in the entrypoint or in init containers that run per-replica. Run as a **separate Job** before rolling out new pods:
|
|
284
491
|
|
|
285
492
|
```yaml
|
|
286
493
|
apiVersion: batch/v1
|
|
@@ -312,21 +519,50 @@ readinessProbe:
|
|
|
312
519
|
periodSeconds: 10
|
|
313
520
|
```
|
|
314
521
|
|
|
315
|
-
### Scaling
|
|
522
|
+
### Scaling
|
|
316
523
|
|
|
317
524
|
- **Backend**: Stateless — safe to scale. All replicas must share the same `AUTH_SECRET`.
|
|
318
525
|
- **Frontends**: Stateless — scale freely behind a load balancer.
|
|
319
|
-
- **Uploads**: Replace
|
|
320
|
-
- **PostgreSQL / Redis**: Use managed services (RDS, ElastiCache
|
|
526
|
+
- **Uploads**: Replace local `uploads/` with object storage (S3, GCS) or a `ReadWriteMany` PVC.
|
|
527
|
+
- **PostgreSQL / Redis**: Use managed services (RDS, ElastiCache) — not the Docker Compose images.
|
|
528
|
+
|
|
529
|
+
---
|
|
321
530
|
|
|
322
531
|
## Production Checklist
|
|
323
532
|
|
|
324
|
-
|
|
325
|
-
|
|
533
|
+
### Server
|
|
534
|
+
|
|
535
|
+
- [ ] `plumbus start` used as production command
|
|
536
|
+
- [ ] `AUTH_SECRET` set to strong random value (32+ chars)
|
|
326
537
|
- [ ] Database SSL enabled (automatic with `plumbus start`)
|
|
327
|
-
- [ ]
|
|
328
|
-
- [ ] Health checks
|
|
538
|
+
- [ ] Migrations run separately — NOT in entrypoint
|
|
539
|
+
- [ ] Health checks on `/ready`
|
|
540
|
+
- [ ] `config/app.config.ts` uses dynamic environment detection
|
|
541
|
+
- [ ] AI provider API keys configured
|
|
542
|
+
- [ ] Redis available for job queue
|
|
543
|
+
|
|
544
|
+
### Docker — Image Size
|
|
545
|
+
|
|
329
546
|
- [ ] `output: "standalone"` in all `next.config.mjs` files
|
|
547
|
+
- [ ] Frontend/admin runner copies ONLY `.next/standalone` + `.next/static`
|
|
548
|
+
- [ ] Frontend/admin runner does NOT contain `node_modules` from deps stage
|
|
549
|
+
- [ ] Backend runner uses production-only `node_modules` with `@plumbus/ui` stripped
|
|
550
|
+
- [ ] Image sizes: frontends ~150-200 MB, backend ~500-700 MB
|
|
551
|
+
|
|
552
|
+
### Docker — Build Correctness
|
|
553
|
+
|
|
554
|
+
- [ ] `.npmrc` copied into all Docker images
|
|
555
|
+
- [ ] `pnpm deploy --legacy` used to isolate frontend deps (not hoisted full-workspace install)
|
|
556
|
+
- [ ] `ENV PATH="/app/deployed/node_modules/.bin:$PATH"` set before `next build`
|
|
557
|
+
- [ ] `outputFileTracingExcludes` configured in `next.config.mjs` (typescript, @swc/core)
|
|
558
|
+
- [ ] Backend `proddeps` cleans up vitest/playwright/esbuild/typescript after npm install
|
|
559
|
+
- [ ] `.plumbus/` generated inside Docker (not copied from host)
|
|
560
|
+
- [ ] `plumbus ui generate` run before building frontends
|
|
561
|
+
- [ ] `.dockerignore` excludes `node_modules/`, `dist/`, `.git/`, `uploads/`, `**/e2e/`
|
|
562
|
+
|
|
563
|
+
### Volumes and Networking
|
|
564
|
+
|
|
330
565
|
- [ ] Persistent volumes for PostgreSQL data and uploads
|
|
331
|
-
- [ ]
|
|
332
|
-
- [ ]
|
|
566
|
+
- [ ] `API_BASE_URL` set to Docker service name (e.g., `http://backend:3000`)
|
|
567
|
+
- [ ] `NEXT_PUBLIC_API_BASE_URL` set to empty string `""` (for server-side proxy)
|
|
568
|
+
- [ ] `AUTH_COOKIE_SECURE` set to `"true"` (or `"false"` only for HTTP-only — security trade-off)
|