@plumbus/core 0.2.3 → 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.
@@ -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. This document covers production configuration, containerization, and operational readiness.
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,46 +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 via server-side route handlers (`app/api/[...path]/route.ts`). Browsers never connect directly 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
- ## Common Docker Pitfalls
25
+ ---
26
26
 
27
- Before diving into Dockerfiles, understand these critical issues that cause the most deployment failures:
27
+ ## Docker Rules — MUST follow all of these
28
28
 
29
- ### 1. `.plumbus/` is generated — do NOT copy from host
29
+ These rules are critical. Violating any one causes build failures or bloated images.
30
30
 
31
- The `.plumbus/` directory is produced by `plumbus generate`. It may not exist on the host (CI pipelines, fresh clones). **Always run `plumbus generate` inside the Docker build** instead of copying from the host.
31
+ ### Rule 1: Generate `.plumbus/` inside Docker
32
32
 
33
- ### 2. `.npmrc` is required for frontend builds
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.
34
35
 
35
- `@plumbus/ui` provides `next`, `react`, `tailwindcss`, and other packages as transitive dependencies. pnpm does not hoist them by default — the project's `.npmrc` contains `public-hoist-pattern` entries that make these packages visible to Turbopack/Next.js. **If you don't copy `.npmrc` into your Docker image, `pnpm install` will not hoist correctly and `next build` will fail** with "Cannot find module 'next'" or similar resolution errors.
36
+ ### Rule 2: Always copy `.npmrc` into Docker images
36
37
 
37
- ### 3. pnpm symlinks break when copying `node_modules` between stages
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.
38
41
 
39
- pnpm uses symlinks inside `node_modules/`. When you `COPY --from=deps /app/node_modules ./node_modules` into a **new** Docker stage (`FROM base AS build`), the symlinks break. **Use `FROM deps AS build`** — this starts the build stage from the deps layer, preserving the symlink tree intact.
42
+ ### Rule 3: Never copy `pnpm-workspace.yaml` with local dev overrides
40
43
 
41
- ### 4. Cookie `secure` flag and HTTP-only deployments
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.
42
47
 
43
- `plumbus start` sets `secure: true` on auth cookies by default (correct for production TLS). If you deploy behind plain HTTP (no TLS), browsers will silently drop `Secure` cookies, causing infinite login redirect loops. See [HTTP Deployments Without TLS](#http-deployments-without-tls) for the workaround.
48
+ ### Rule 4: Use `FROM deps AS build` (preserve pnpm symlinks in build stage)
44
49
 
45
- ### 5. Regenerate client types before building frontends
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.
46
52
 
47
- The frontend typed client (`frontend/lib/client.ts`) is generated by `plumbus ui generate`. If capability schemas change and you don't regenerate, the frontend build will fail with type errors or the client will be out of sync with the API. **Always run `plumbus ui generate` before building frontend Docker images** — either locally before commit, or as a Docker build stage.
53
+ ### Rule 5: Use `pnpm deploy` to isolate frontend dependencies
48
54
 
49
- ### 6. `app.config.ts` must use dynamic environment detection
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).
50
58
 
51
- Do **not** hardcode `environment: 'development'` in `config/app.config.ts`. In production Docker containers where `NODE_ENV=production`, a hardcoded development environment causes `plumbus start` to skip production safeguards. Use:
59
+ ### Rule 6: Never copy full `node_modules` into frontend runner stages
52
60
 
53
- ```ts
54
- environment: (process.env.NODE_ENV === 'production' ? 'production' : 'development')
55
- ```
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
+ ---
56
126
 
57
127
  ## Backend Deployment
58
128
 
59
129
  ### Production Command
60
130
 
61
- Use `plumbus start` for production. It forces production environment, binds to `0.0.0.0`, and requires `AUTH_SECRET`:
131
+ Use `plumbus start` for production:
62
132
 
63
133
  | Behavior | `plumbus dev` | `plumbus start` |
64
134
  |----------|---------------|------------------|
@@ -75,7 +145,7 @@ npx plumbus start --port 3000
75
145
 
76
146
  ### Runtime File Requirements
77
147
 
78
- The backend uses dynamic resource discovery at startup — it scans the source tree for capabilities, entities, flows, events, and prompts. The following directories **must be present** in the production image:
148
+ These directories **must be present** in the production image:
79
149
 
80
150
  | Directory | Purpose |
81
151
  |-----------|---------|
@@ -88,18 +158,20 @@ The backend uses dynamic resource discovery at startup — it scans the source t
88
158
 
89
159
  ### Entrypoint Pattern
90
160
 
91
- **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.
92
-
93
161
  ```sh
94
- # Run migrations BEFORE deploying new containers (CI/CD step or one-off job):
95
- npx plumbus migrate apply
96
-
97
- # Then deploy. Container entrypoint only starts the server:
162
+ # entrypoint.sh — only starts the server. No migrations here.
98
163
  #!/bin/sh
99
164
  set -e
100
165
  exec npx plumbus start --port 3000
101
166
  ```
102
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
+
103
175
  ### Health Endpoints
104
176
 
105
177
  | Endpoint | Purpose | Healthy response |
@@ -109,11 +181,13 @@ exec npx plumbus start --port 3000
109
181
 
110
182
  Use `/ready` for container health checks and load balancer probes.
111
183
 
184
+ ---
185
+
112
186
  ## Frontend Deployment
113
187
 
114
188
  ### Standalone Output
115
189
 
116
- **CRITICAL**: Add `output: "standalone"` to `next.config.mjs` for containerized or self-hosted deployment. Without this, the build requires the full `node_modules` at runtime.
190
+ Add to `next.config.mjs`:
117
191
 
118
192
  ```js
119
193
  const nextConfig = {
@@ -129,18 +203,12 @@ const nextConfig = {
129
203
  npx next build
130
204
 
131
205
  # Run standalone server
132
- node .next/standalone/<app-dir>/server.js
133
- ```
134
-
135
- Set `PORT` and `HOSTNAME` environment variables to control the listening address:
136
-
137
- ```bash
138
206
  PORT=3001 HOSTNAME=0.0.0.0 node .next/standalone/frontend/server.js
139
207
  ```
140
208
 
141
209
  ### Static Assets
142
210
 
143
- After a standalone build, static files live in `.next/static/`. Copy them to the correct path in your deployment:
211
+ After a standalone build, copy static files alongside the server:
144
212
 
145
213
  ```
146
214
  .next/standalone/ # Server code
@@ -149,9 +217,11 @@ After a standalone build, static files live in `.next/static/`. Copy them to the
149
217
 
150
218
  ### Environment Variables
151
219
 
152
- - `NEXT_PUBLIC_*` variables are inlined at **build time**. Set them before `next build`, not at runtime.
153
- - `API_BASE_URL` (server-only) controls the backend URL for server-side proxy routes. Set this at runtime to the internal Docker network address (e.g., `http://backend:3000`).
154
- - `AUTH_SECRET` is required for cookie signing.
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
+ ---
155
225
 
156
226
  ## Environment Variables Reference
157
227
 
@@ -183,26 +253,61 @@ After a standalone build, static files live in `.next/static/`. Copy them to the
183
253
  | `AUTH_SECRET` | Runtime | Cookie signing secret |
184
254
  | `AUTH_COOKIE_SECURE` | Runtime | Set to `"true"` for TLS, `"false"` for HTTP-only deployments |
185
255
 
256
+ ---
257
+
186
258
  ## Docker
187
259
 
188
- ### Backend Dockerfile (multi-stage)
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
189
271
 
190
272
  ```dockerfile
273
+ # ==============================================================
274
+ # BACKEND DOCKERFILE — Reference template
275
+ # See "Docker Rules" section above for the rationale behind each step.
276
+ # ==============================================================
277
+
191
278
  # ---- Base ----
192
279
  FROM node:22-alpine AS base
193
280
  RUN corepack enable && corepack prepare pnpm@latest --activate
194
281
  WORKDIR /app
195
282
 
196
- # ---- Dependencies ----
283
+ # ---- Full dependencies (needed for plumbus generate and pnpm build) ----
197
284
  FROM base AS deps
198
- # Do NOT copy pnpm-workspace.yaml — it may contain local dev overrides
199
- # (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.
200
286
  COPY package.json pnpm-lock.yaml ./
201
287
  RUN pnpm install
202
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
+
203
309
  # ---- Generate .plumbus/ ----
204
- # .plumbus/ is generated — never copy it from the host. Run plumbus generate
205
- # inside Docker so it always exists and matches the current app/ definitions.
310
+ # DO: Generate inside Docker. DO NOT: Copy from host.
206
311
  FROM deps AS generate
207
312
  COPY app/ ./app/
208
313
  COPY config/ ./config/
@@ -210,6 +315,7 @@ COPY tsconfig.json ./
210
315
  RUN npx plumbus generate
211
316
 
212
317
  # ---- Build ----
318
+ # Uses "FROM deps" to preserve pnpm symlinks (Rule 4).
213
319
  FROM deps AS build
214
320
  COPY --from=generate /app/.plumbus/ ./.plumbus/
215
321
  COPY package.json tsconfig.json ./
@@ -220,65 +326,72 @@ RUN pnpm build
220
326
 
221
327
  # ---- Runner ----
222
328
  FROM node:22-alpine AS runner
223
- RUN corepack enable && corepack prepare pnpm@latest --activate
224
329
  WORKDIR /app
225
330
  ENV NODE_ENV=production
226
331
  RUN addgroup --system --gid 1001 plumbus && \
227
332
  adduser --system --uid 1001 plumbus
228
- COPY --from=deps /app/node_modules ./node_modules
333
+ # Production-only node_modules from proddeps (no @plumbus/ui, no devDeps).
334
+ COPY --from=proddeps /app/node_modules ./node_modules
229
335
  COPY --from=build /app/dist ./dist
230
336
  COPY --from=generate /app/.plumbus/ ./.plumbus/
231
337
  COPY package.json ./
232
338
  COPY app/ ./app/
233
339
  COPY config/ ./config/
234
340
  COPY drizzle/ ./drizzle/
235
- # Entrypoint starts the production server
236
341
  USER plumbus
237
342
  EXPOSE 3000
238
- HEALTHCHECK CMD curl -f http://localhost:3000/ready || exit 1
343
+ HEALTHCHECK CMD wget -q -O /dev/null http://localhost:3000/ready || exit 1
239
344
  ENTRYPOINT ["./entrypoint.sh"]
240
345
  ```
241
346
 
242
- ### Frontend Dockerfile (Next.js standalone)
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.
243
350
 
244
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
+
245
358
  FROM node:22-alpine AS base
246
359
  RUN corepack enable && corepack prepare pnpm@latest --activate
247
360
  WORKDIR /app
248
361
 
362
+ # ---- Install full workspace (needed for pnpm deploy to resolve deps) ----
249
363
  FROM base AS deps
250
- COPY package.json pnpm-lock.yaml ./
364
+ COPY .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml ./
251
365
  COPY frontend/package.json ./frontend/
252
- # Copy .npmrc — it contains public-hoist-pattern entries that make
253
- # next, react, tailwindcss visible to Turbopack. Without it, next build fails.
254
- COPY .npmrc ./
255
- # Generate a clean workspace config — don't copy the host's pnpm-workspace.yaml
256
- RUN printf 'packages:\n - "frontend"\n' > pnpm-workspace.yaml
366
+ COPY frontend-admin/package.json ./frontend-admin/
257
367
  RUN pnpm install
258
368
 
259
- # CRITICAL: Use "FROM deps" (NOT "FROM base") to preserve pnpm symlinks.
260
- # Copying node_modules between stages with COPY --from breaks pnpm's symlink tree.
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.
261
372
  FROM deps AS build
262
373
  COPY frontend/ ./frontend/
263
- WORKDIR /app/frontend
264
- RUN npx next build
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
265
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.
266
382
  FROM node:22-alpine AS runner
267
383
  WORKDIR /app
268
384
  ENV NODE_ENV=production
269
385
  RUN addgroup --system --gid 1001 nextjs && \
270
386
  adduser --system --uid 1001 nextjs
271
- COPY --from=build /app/frontend/.next/standalone ./
272
- COPY --from=build /app/frontend/.next/static ./frontend/.next/static
387
+ COPY --from=build /app/deployed/.next/standalone ./
388
+ COPY --from=build /app/deployed/.next/static ./deployed/.next/static
273
389
  USER nextjs
274
390
  EXPOSE 3001
275
- CMD ["node", "frontend/server.js"]
391
+ CMD ["node", "deployed/server.js"]
276
392
  ```
277
393
 
278
- > **Why `FROM deps AS build` instead of `FROM base AS build`?**
279
- > pnpm stores packages in a content-addressable store and creates symlinks inside `node_modules/`. Docker's `COPY --from` creates new files without preserving symlinks. By starting the build stage from the deps layer (`FROM deps`), the existing filesystem (with intact symlinks) is inherited, not copied.
280
-
281
- ### Docker Compose Topology
394
+ ### Docker Compose
282
395
 
283
396
  ```yaml
284
397
  services:
@@ -299,28 +412,47 @@ services:
299
412
  depends_on: [backend]
300
413
  ```
301
414
 
302
- ### Key Docker Considerations
415
+ ### .dockerignore
303
416
 
304
- - **Do NOT copy `pnpm-workspace.yaml`** into Docker images. It may contain local dev overrides (`link:../Plumbus/...`) that don't exist in the build context. The backend Dockerfile doesn't need it at all. Frontend Dockerfiles generate a minimal workspace config inline (`printf 'packages: ["frontend"]' > pnpm-workspace.yaml`) to declare the sub-package.
305
- - **Always copy `.npmrc`** into Docker images (both backend and frontend). It defines `public-hoist-pattern` entries that pnpm needs to make framework-provided packages (`next`, `react`, `tailwindcss`, etc.) visible to the application. Without it, `next build` will fail with module resolution errors.
306
- - **Generate `.plumbus/` inside Docker** — never rely on it existing on the host. The backend Dockerfile should include a `generate` stage that runs `plumbus generate`. This ensures the manifests always match the current app definitions, even in CI/CD pipelines and fresh clones.
307
- - **Preserve pnpm symlinks** — use `FROM deps AS build` in frontend Dockerfiles. Do **not** use `FROM base AS build` with `COPY --from=deps /app/node_modules`. Docker's COPY command does not preserve symlinks, which breaks pnpm's package resolution and causes Turbopack/Next.js to fail.
308
- - **Regenerate client types** — run `plumbus ui generate` before building frontend Docker images. The typed client (`frontend/lib/client.ts`) must match the current capability schemas. If you changed entity or capability definitions without regenerating, the frontend build will fail.
309
- - **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.
310
- - **Volumes**: Mount persistent volumes for PostgreSQL data and the `uploads/` directory (user-generated media and exports).
311
- - **Internal networking**: Frontends connect to backend via Docker service name (e.g., `http://backend:3000`). Set `API_BASE_URL` accordingly.
312
- - **`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.
313
- - **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:
314
418
 
315
- ## HTTP Deployments Without TLS
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
+ ```
316
444
 
317
- `plumbus start` defaults to `secure: true` for auth cookies — the correct setting for production deployments behind TLS. However, some deployment scenarios (staging servers, internal tools, EC2 without a load balancer) run over plain HTTP.
445
+ Set `context: ..` in compose `build:` sections so COPY paths resolve from the project root.
318
446
 
319
- When `Secure` cookies are set over HTTP, browsers silently refuse to store them. The symptom is an infinite login redirect loop: the auth route sets the cookie, the browser drops it, and the next request redirects back to login.
447
+ ---
320
448
 
321
- ### Workaround: `AUTH_COOKIE_SECURE` Environment Variable
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
322
454
 
323
- Frontend auth route handlers should respect an `AUTH_COOKIE_SECURE` environment variable:
455
+ Set `AUTH_COOKIE_SECURE=false` in the environment. Frontend auth route handlers must read it:
324
456
 
325
457
  ```ts
326
458
  // app/api/auth/login/route.ts
@@ -335,20 +467,12 @@ cookies().set("auth_token", token, {
335
467
  });
336
468
  ```
337
469
 
338
- Set `AUTH_COOKIE_SECURE=false` in your `.env` for HTTP-only deployments. **This is a security trade-off** — always use TLS in production and set `AUTH_COOKIE_SECURE=true`.
470
+ **Security trade-off** — always use TLS in production and set `AUTH_COOKIE_SECURE=true`.
339
471
 
340
- Add to docker-compose environment:
341
-
342
- ```yaml
343
- frontend:
344
- environment:
345
- AUTH_COOKIE_SECURE: "false" # Set to "true" behind TLS
346
- ```
472
+ ---
347
473
 
348
474
  ## Kubernetes / Helm
349
475
 
350
- The Docker images are self-contained and orchestrator-agnostic. When deploying to Kubernetes:
351
-
352
476
  ### Required Resources
353
477
 
354
478
  | Resource | Purpose |
@@ -361,11 +485,9 @@ The Docker images are self-contained and orchestrator-agnostic. When deploying t
361
485
  | `Secret` | `DB_PASSWORD`, `AUTH_SECRET`, AI provider keys |
362
486
  | `ConfigMap` | Non-sensitive config: `DB_HOST`, `AI_DEFAULT_PROVIDER`, etc. |
363
487
 
364
- ### Migrations in Kubernetes — Critical
488
+ ### Migrations — Critical
365
489
 
366
- **NEVER** run migrations in the container entrypoint or as an init container that runs on every pod replica. In a rolling deployment with multiple pods starting concurrently, parallel migration runs against the same database can corrupt data or cause extended downtime.
367
-
368
- 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:
369
491
 
370
492
  ```yaml
371
493
  apiVersion: batch/v1
@@ -397,28 +519,50 @@ readinessProbe:
397
519
  periodSeconds: 10
398
520
  ```
399
521
 
400
- ### Scaling Notes
522
+ ### Scaling
401
523
 
402
524
  - **Backend**: Stateless — safe to scale. All replicas must share the same `AUTH_SECRET`.
403
525
  - **Frontends**: Stateless — scale freely behind a load balancer.
404
- - **Uploads**: Replace the local `uploads/` directory with object storage (S3, GCS) or a `ReadWriteMany` PersistentVolumeClaim.
405
- - **PostgreSQL / Redis**: Use managed services (RDS, ElastiCache, etc.) or official Helm charts — not the Docker Compose images.
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
+ ---
406
530
 
407
531
  ## Production Checklist
408
532
 
409
- - [ ] `plumbus start` used as production server command
410
- - [ ] `AUTH_SECRET` set to a strong random value (32+ characters)
533
+ ### Server
534
+
535
+ - [ ] `plumbus start` used as production command
536
+ - [ ] `AUTH_SECRET` set to strong random value (32+ chars)
411
537
  - [ ] Database SSL enabled (automatic with `plumbus start`)
412
- - [ ] `plumbus migrate apply` run as a separate step before deployment (NOT in entrypoint)
413
- - [ ] Health checks configured for `/ready` endpoint
414
- - [ ] `output: "standalone"` in all `next.config.mjs` files
415
- - [ ] Persistent volumes for PostgreSQL data and uploads
538
+ - [ ] Migrations run separately — NOT in entrypoint
539
+ - [ ] Health checks on `/ready`
540
+ - [ ] `config/app.config.ts` uses dynamic environment detection
416
541
  - [ ] AI provider API keys configured
417
- - [ ] Redis available for job queue and event dispatch
418
- - [ ] `.npmrc` copied into all Docker images (frontend and admin)
542
+ - [ ] Redis available for job queue
543
+
544
+ ### Docker — Image Size
545
+
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
419
559
  - [ ] `.plumbus/` generated inside Docker (not copied from host)
420
- - [ ] Frontend Dockerfiles use `FROM deps AS build` (not `FROM base AS build`)
421
- - [ ] `plumbus ui generate` run before building frontend images (client types up to date)
422
- - [ ] `config/app.config.ts` uses dynamic environment detection (not hardcoded `'development'`)
423
- - [ ] `AUTH_COOKIE_SECURE` set to `"true"` (or `"false"` only if no TLS — see HTTP deployment section)
424
- - [ ] `.dockerignore` at project root excludes `node_modules/`, `dist/`, `.git/`, `uploads/`
560
+ - [ ] `plumbus ui generate` run before building frontends
561
+ - [ ] `.dockerignore` excludes `node_modules/`, `dist/`, `.git/`, `uploads/`, `**/e2e/`
562
+
563
+ ### Volumes and Networking
564
+
565
+ - [ ] Persistent volumes for PostgreSQL data and uploads
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumbus/core",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Plumbus framework core — types, SDK, runtime, CLI, test utilities",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",