@rebasepro/agent-skills 0.0.1-canary.4829d6e

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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/package.json +21 -0
  4. package/skills/rebase-admin/SKILL.md +816 -0
  5. package/skills/rebase-api/SKILL.md +673 -0
  6. package/skills/rebase-api/references/.gitkeep +3 -0
  7. package/skills/rebase-auth/SKILL.md +1323 -0
  8. package/skills/rebase-auth/references/.gitkeep +3 -0
  9. package/skills/rebase-backend-postgres/SKILL.md +633 -0
  10. package/skills/rebase-backend-postgres/references/.gitkeep +3 -0
  11. package/skills/rebase-basics/SKILL.md +757 -0
  12. package/skills/rebase-basics/references/.gitkeep +3 -0
  13. package/skills/rebase-collections/SKILL.md +1421 -0
  14. package/skills/rebase-collections/references/.gitkeep +3 -0
  15. package/skills/rebase-cron-jobs/SKILL.md +704 -0
  16. package/skills/rebase-cron-jobs/references/.gitkeep +1 -0
  17. package/skills/rebase-custom-functions/SKILL.md +281 -0
  18. package/skills/rebase-deployment/SKILL.md +894 -0
  19. package/skills/rebase-deployment/references/.gitkeep +3 -0
  20. package/skills/rebase-design-language/SKILL.md +692 -0
  21. package/skills/rebase-email/SKILL.md +701 -0
  22. package/skills/rebase-email/references/.gitkeep +1 -0
  23. package/skills/rebase-entity-history/SKILL.md +485 -0
  24. package/skills/rebase-entity-history/references/.gitkeep +1 -0
  25. package/skills/rebase-local-env-setup/SKILL.md +189 -0
  26. package/skills/rebase-local-env-setup/references/.gitkeep +3 -0
  27. package/skills/rebase-realtime/SKILL.md +759 -0
  28. package/skills/rebase-realtime/references/.gitkeep +3 -0
  29. package/skills/rebase-sdk/SKILL.md +617 -0
  30. package/skills/rebase-sdk/references/.gitkeep +0 -0
  31. package/skills/rebase-security/SKILL.md +646 -0
  32. package/skills/rebase-storage/SKILL.md +989 -0
  33. package/skills/rebase-storage/references/.gitkeep +3 -0
  34. package/skills/rebase-studio/SKILL.md +772 -0
  35. package/skills/rebase-studio/references/.gitkeep +3 -0
  36. package/skills/rebase-ui-components/SKILL.md +1579 -0
  37. package/skills/rebase-ui-components/references/.gitkeep +3 -0
  38. package/skills/rebase-webhooks/SKILL.md +618 -0
  39. package/skills/rebase-webhooks/references/.gitkeep +1 -0
@@ -0,0 +1,894 @@
1
+ ---
2
+ name: rebase-deployment
3
+ description: Guide for deploying Rebase applications. Use this skill when the user needs to deploy to Rebase Cloud, set up Docker, self-host on AWS/GCP/Hetzner, or use a PaaS like Railway or Render.
4
+ ---
5
+
6
+ # Rebase Deployment
7
+
8
+ Rebase supports multiple deployment strategies — from fully managed Rebase Cloud to self-hosted Docker deployments.
9
+
10
+ ## Deployment Options
11
+
12
+ | Option | Best For | Complexity |
13
+ |--------|----------|------------|
14
+ | **Rebase Cloud** | Fastest setup, managed infrastructure | ⭐ Easy |
15
+ | **Docker (Self-Hosted)** | Full control on any VPS or bare metal | ⭐⭐ Medium |
16
+ | **AWS** | ECS/Fargate or EC2 with RDS PostgreSQL | ⭐⭐⭐ Advanced |
17
+ | **GCP** | Cloud Run with Cloud SQL PostgreSQL | ⭐⭐⭐ Advanced |
18
+ | **Azure** | Container Apps with Azure Database for PostgreSQL | ⭐⭐⭐ Advanced |
19
+ | **Scaleway** | Serverless Containers with Managed PostgreSQL (EU-only) | ⭐⭐⭐ Advanced |
20
+ | **Hetzner** | Cost-effective VPS with Docker Compose | ⭐⭐ Medium |
21
+ | **PaaS** | Railway, Render, Fly.io — Docker-based platforms | ⭐⭐ Medium |
22
+
23
+ ## Rebase Cloud
24
+
25
+ The simplest deployment path. Sign up at [app.rebase.pro](https://app.rebase.pro).
26
+
27
+ ```bash
28
+ # 1. Authenticate
29
+ rebase login
30
+
31
+ # 2. Initialize (if new project)
32
+ rebase init
33
+
34
+ # 3. Deploy
35
+ rebase deploy
36
+
37
+ # Deploy to dev environment
38
+ rebase deploy --env dev
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Docker (Self-Hosted)
44
+
45
+ ### Production Dockerfile (Multi-Stage)
46
+
47
+ The production Dockerfile is a **multi-stage build**. The build context is the **monorepo root** (where `pnpm-workspace.yaml` lives), not the app directory.
48
+
49
+ > **IMPORTANT FOR AGENTS:** The Dockerfile uses `node:22-alpine`, `corepack enable` (NOT `npm install -g pnpm`), and requires the monorepo root as the build context. Never generate a single-stage Dockerfile or use `node:20`.
50
+
51
+ ```dockerfile
52
+ # ── Stage 1: Install + Build ─────────────────────────────────────────
53
+ FROM node:22-alpine AS builder
54
+
55
+ ENV PNPM_HOME="/pnpm"
56
+ ENV PATH="$PNPM_HOME:$PATH"
57
+ ENV CI=true
58
+ RUN corepack enable
59
+
60
+ RUN apk add --no-cache python3 make g++
61
+
62
+ WORKDIR /app
63
+
64
+ # Copy workspace root
65
+ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
66
+
67
+ # Copy source packages and app
68
+ COPY packages ./packages
69
+ COPY app ./app
70
+
71
+ # Install all dependencies with flat node_modules for Docker compatibility
72
+ # (many packages rely on hoisted devDependencies like @vitejs/plugin-react)
73
+ RUN pnpm install --shamefully-hoist
74
+
75
+ # Build all packages using pnpm recursive with --no-bail.
76
+ # Some packages may fail tsc declarations — that's fine, we only need vite bundles + esbuild outputs.
77
+ RUN pnpm --filter './packages/*' -r --no-bail run build; exit 0
78
+
79
+ # Build the backend (TypeScript → JavaScript), then resolve ESM import extensions
80
+ # 1. tsc compiles TS→JS 2. tsc-alias resolves path aliases 3. sed adds .js to remaining relative imports
81
+ RUN cd app/backend && npx tsc -p tsconfig.docker.json && npx tsc-alias -p tsconfig.docker.json -f \
82
+ && find dist -name '*.js' -exec sed -i 's/from "\(\.[^"]*\)"/from "\1.js"/g; s/\.js\.js/.js/g' {} +
83
+
84
+ # Build frontend (reads .env.production for VITE_API_URL etc.)
85
+ RUN cd app/frontend && npx vite build
86
+
87
+ # Prune devDependencies to reduce image size
88
+ RUN pnpm install --shamefully-hoist --prod
89
+
90
+ # ── Stage 2: Production Runtime ──────────────────────────────────────
91
+ FROM node:22-alpine AS runtime
92
+
93
+ ENV PNPM_HOME="/pnpm"
94
+ ENV PATH="$PNPM_HOME:$PATH"
95
+ ENV NODE_ENV=production
96
+
97
+ RUN corepack enable
98
+
99
+ # Security: run as non-root
100
+ RUN addgroup -g 1001 rebase && adduser -u 1001 -G rebase -s /bin/sh -D rebase
101
+
102
+ WORKDIR /app
103
+
104
+ # Copy only production artifacts (node_modules already pruned of devDependencies)
105
+ COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
106
+ COPY --from=builder /app/node_modules ./node_modules
107
+ COPY --from=builder /app/packages ./packages
108
+ COPY --from=builder /app/app ./app
109
+
110
+ # Uploads directory with correct ownership
111
+ RUN mkdir -p /app/app/uploads && chown -R rebase:rebase /app
112
+
113
+ ENV STORAGE_PATH=/app/app/uploads
114
+
115
+ USER rebase
116
+
117
+ WORKDIR /app/app/backend
118
+ EXPOSE 3001
119
+
120
+ HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
121
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1
122
+
123
+ # Copy the entrypoint script and drizzle config for auto-migration
124
+ COPY --from=builder /app/app/backend/entrypoint.sh ./entrypoint.sh
125
+ COPY --from=builder /app/app/backend/drizzle.config.ts ./drizzle.config.ts
126
+ COPY --from=builder /app/app/backend/drizzle ./drizzle
127
+
128
+ # Auto-migrate then start the compiled JavaScript backend
129
+ CMD ["sh", "entrypoint.sh"]
130
+ ```
131
+
132
+ **Build command** (run from monorepo root):
133
+
134
+ ```bash
135
+ docker build -t rebase-backend -f app/backend/Dockerfile .
136
+ ```
137
+
138
+ ### Docker Entrypoint (Auto-Migration)
139
+
140
+ The Docker image uses an entrypoint script (`entrypoint.sh`) that **automatically runs database migrations** before starting the server. Migrations are non-fatal — if they fail (e.g. already applied), the server still starts.
141
+
142
+ ```bash
143
+ #!/bin/sh
144
+ set -e
145
+
146
+ echo "🔄 Running database migrations..."
147
+ npx drizzle-kit migrate --config=drizzle.config.ts 2>&1 || echo "⚠️ Migrations skipped or failed (non-fatal)"
148
+
149
+ echo "🚀 Starting Rebase backend..."
150
+ exec node dist/app/backend/src/index.js
151
+ ```
152
+
153
+ > **IMPORTANT FOR AGENTS:** The entrypoint runs `drizzle-kit migrate` automatically on container start. Never add manual migration steps to a Dockerfile CMD — they are handled by `entrypoint.sh`.
154
+
155
+ ### Docker Compose (Monorepo)
156
+
157
+ This is the monorepo-internal Docker Compose (`app/backend/docker-compose.yml`). Note: the build context is `../..` (monorepo root).
158
+
159
+ ```yaml
160
+ services:
161
+ db:
162
+ image: postgres:18-alpine
163
+ restart: unless-stopped
164
+ environment:
165
+ POSTGRES_USER: rebase
166
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-rebasepassword}
167
+ POSTGRES_DB: rebase
168
+ ports:
169
+ - "5432:5432"
170
+ volumes:
171
+ - postgres_data:/var/lib/postgresql/data
172
+ healthcheck:
173
+ test: ["CMD-SHELL", "pg_isready -U rebase -d rebase"]
174
+ interval: 5s
175
+ timeout: 5s
176
+ retries: 10
177
+ start_period: 10s
178
+ command:
179
+ - "postgres"
180
+ - "-c"
181
+ - "shared_buffers=256MB"
182
+ - "-c"
183
+ - "max_connections=100"
184
+ - "-c"
185
+ - "log_min_duration_statement=1000"
186
+
187
+ backend:
188
+ build:
189
+ context: ../..
190
+ dockerfile: app/backend/Dockerfile
191
+ restart: unless-stopped
192
+ ports:
193
+ - "${PORT:-3001}:3001"
194
+ environment:
195
+ DATABASE_URL: postgresql://rebase:${POSTGRES_PASSWORD:-rebasepassword}@db:5432/rebase
196
+ ADMIN_CONNECTION_STRING: postgresql://rebase:${POSTGRES_PASSWORD:-rebasepassword}@db:5432/rebase
197
+ JWT_SECRET: ${JWT_SECRET:-super-secret-jwt-key-change-in-production}
198
+ NODE_ENV: ${NODE_ENV:-development}
199
+ PORT: "3001"
200
+ ALLOW_REGISTRATION: ${ALLOW_REGISTRATION:-true}
201
+ depends_on:
202
+ db:
203
+ condition: service_healthy
204
+ volumes:
205
+ - uploads:/app/app/backend/uploads
206
+
207
+ volumes:
208
+ postgres_data:
209
+ driver: local
210
+ uploads:
211
+ driver: local
212
+ ```
213
+
214
+ > **WARNING FOR AGENTS:** The build context is `../..` (monorepo root), NOT `.` or `app/backend`. The Dockerfile path is relative to the monorepo root: `app/backend/Dockerfile`. The `depends_on` uses `condition: service_healthy` so the backend waits for postgres to be ready. **Never use `version: '3.8'`** — modern Docker Compose doesn't use the `version` key.
215
+
216
+ ### Docker Compose (User Template)
217
+
218
+ For user-generated projects (created via `rebase init`), the Docker Compose file is at the project root. It includes separate backend + frontend services:
219
+
220
+ ```yaml
221
+ services:
222
+ db:
223
+ image: postgres:18-alpine
224
+ restart: unless-stopped
225
+ environment:
226
+ POSTGRES_USER: rebase
227
+ POSTGRES_PASSWORD: ${DATABASE_PASSWORD:-changeme}
228
+ POSTGRES_DB: rebase
229
+ ports:
230
+ - "5432:5432"
231
+ volumes:
232
+ - postgres_data:/var/lib/postgresql/data
233
+ healthcheck:
234
+ test: ["CMD-SHELL", "pg_isready -U rebase -d rebase"]
235
+ interval: 5s
236
+ timeout: 5s
237
+ retries: 10
238
+ start_period: 10s
239
+ command:
240
+ - "postgres"
241
+ - "-c"
242
+ - "shared_buffers=256MB"
243
+ - "-c"
244
+ - "max_connections=100"
245
+ - "-c"
246
+ - "work_mem=4MB"
247
+ - "-c"
248
+ - "effective_cache_size=768MB"
249
+ - "-c"
250
+ - "log_min_duration_statement=1000"
251
+
252
+ backend:
253
+ build:
254
+ context: .
255
+ dockerfile: backend/Dockerfile
256
+ restart: unless-stopped
257
+ ports:
258
+ - "${PORT:-3001}:3001"
259
+ env_file: .env
260
+ environment:
261
+ DATABASE_URL: postgresql://rebase:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
262
+ ADMIN_CONNECTION_STRING: postgresql://rebase:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
263
+ NODE_ENV: production
264
+ PORT: "3001"
265
+ depends_on:
266
+ db:
267
+ condition: service_healthy
268
+ volumes:
269
+ - uploads:/app/backend/uploads
270
+
271
+ frontend:
272
+ build:
273
+ context: .
274
+ dockerfile: frontend/Dockerfile
275
+ restart: unless-stopped
276
+ ports:
277
+ - "80:80"
278
+ depends_on:
279
+ - backend
280
+
281
+ volumes:
282
+ postgres_data:
283
+ driver: local
284
+ uploads:
285
+ driver: local
286
+ ```
287
+
288
+ Key differences from the monorepo compose:
289
+ - Build context is `.` (project root), dockerfile is `backend/Dockerfile`
290
+ - Uses `env_file: .env` to load all env vars
291
+ - Sets `NODE_ENV: production` explicitly
292
+ - Includes a separate `frontend` service (nginx)
293
+ - Adds Postgres tuning for `work_mem` and `effective_cache_size`
294
+
295
+ ---
296
+
297
+ ## Environment Variables
298
+
299
+ ### `loadEnv()` — Validated Environment Loading
300
+
301
+ All environment variables are validated at startup via a Zod schema in `loadEnv()` from `@rebasepro/server`. The server **fails immediately** if required variables are missing or invalid.
302
+
303
+ ```typescript
304
+ import dotenv from "dotenv";
305
+ import { loadEnv } from "@rebasepro/server";
306
+
307
+ dotenv.config({ path: "../../.env" });
308
+
309
+ // Basic — just Rebase env vars:
310
+ export const env = loadEnv();
311
+ ```
312
+
313
+ > **IMPORTANT FOR AGENTS:** `loadEnv()` does NOT load `.env` files itself. You MUST call `dotenv.config()` (or use `--env-file`, container injection, etc.) **before** calling `loadEnv()`. This is a deployment concern, not a framework concern.
314
+
315
+ ### Extending with Custom Variables
316
+
317
+ Use the `extend` option to add your own typed env variables on top of the base Rebase schema:
318
+
319
+ ```typescript
320
+ import dotenv from "dotenv";
321
+ import { loadEnv, z } from "@rebasepro/server";
322
+
323
+ dotenv.config({ path: "../../.env" });
324
+
325
+ export const env = loadEnv({
326
+ extend: z.object({
327
+ SMTP_HOST: z.string().optional(),
328
+ SMTP_PORT: z.string().default("587").transform(Number),
329
+ STRIPE_SECRET_KEY: z.string(),
330
+ })
331
+ });
332
+ // env.SMTP_HOST → string | undefined (fully typed)
333
+ // env.STRIPE_SECRET_KEY → string (validated, required)
334
+ ```
335
+
336
+ The `extend` schema is merged with the base `rebaseEnvSchema` using Zod's `.merge()`. All base variables remain available alongside your custom ones.
337
+
338
+ ### `loadEnv()` Function Signature
339
+
340
+ ```typescript
341
+ function loadEnv(): RebaseEnv;
342
+ function loadEnv<E extends z.AnyZodObject>(options: { extend: E }): RebaseEnv & z.infer<E>;
343
+ ```
344
+
345
+ ### Complete Environment Variable Reference
346
+
347
+ All variables recognized by the base `rebaseEnvSchema`:
348
+
349
+ | Variable | Type | Required | Default | Description |
350
+ |----------|------|----------|---------|-------------|
351
+ | `DATABASE_URL` | `string` (URL) | ✅ Yes | — | PostgreSQL connection string |
352
+ | `ADMIN_CONNECTION_STRING` | `string` (URL) | No | — | Separate admin connection for migrations |
353
+ | `JWT_SECRET` | `string` (≥32 chars) | ✅ Yes | Auto-generated (dev only) | JWT signing secret |
354
+ | `JWT_ACCESS_EXPIRES_IN` | `string` | No | `"1h"` | JWT access token expiry (e.g. `"1h"`, `"30m"`) |
355
+ | `JWT_REFRESH_EXPIRES_IN` | `string` | No | `"30d"` | JWT refresh token expiry |
356
+ | `NODE_ENV` | `enum` | No | `"development"` | `"development"`, `"production"`, or `"test"` |
357
+ | `PORT` | `string` → `number` | No | `"3001"` → `3001` | Server port (string, transformed to number) |
358
+ | `CORS_ORIGINS` | `string` | ⚠️ Prod | — | Comma-separated allowed origins |
359
+ | `FRONTEND_URL` | `string` | ⚠️ Prod | — | Frontend URL (CORS fallback, email links) |
360
+ | `ALLOW_REGISTRATION` | `boolString` | No | `"false"` → `false` | Enable new user self-registration |
361
+ | `ALLOW_LOCALHOST_IN_PRODUCTION` | `optionalBoolString` | No | `undefined` → `false` | Allow localhost URLs in production |
362
+ | `GOOGLE_CLIENT_ID` | `string` | No | — | Google OAuth client ID |
363
+ | `GOOGLE_CLIENT_SECRET` | `string` | No | — | Google OAuth client secret |
364
+ | `REBASE_SERVICE_KEY` | `string` | No | Auto-generated (dev only) | Service-to-service auth key (≥32 chars) |
365
+ | `DB_POOL_MAX` | `string` → `number` | No | `"20"` → `20` | Max database pool connections |
366
+ | `DB_POOL_IDLE_TIMEOUT` | `string` → `number` | No | `"30000"` → `30000` | Idle connection timeout (ms) |
367
+ | `DB_POOL_CONNECT_TIMEOUT` | `string` → `number` | No | `"10000"` → `10000` | Connection timeout (ms) |
368
+ | `DATABASE_DIRECT_URL` | `string` (URL) | No | — | Direct database URL (bypasses pooler) |
369
+ | `DATABASE_READ_URL` | `string` (URL) | No | — | Read replica database URL |
370
+ | `STORAGE_TYPE` | `enum` | No | `"local"` | `"local"`, `"s3"`, or `"gcs"` |
371
+ | `STORAGE_PATH` | `string` | No | — | Local file storage directory |
372
+ | `FORCE_LOCAL_STORAGE` | `optionalBoolString` | No | `undefined` → `false` | Suppress local-storage-in-production warning |
373
+ | `S3_BUCKET` | `string` | If S3 | — | S3 bucket name |
374
+ | `S3_REGION` | `string` | No | — | S3 region |
375
+ | `S3_ACCESS_KEY_ID` | `string` | If S3 | — | S3 access key |
376
+ | `S3_SECRET_ACCESS_KEY` | `string` | If S3 | — | S3 secret key |
377
+ | `S3_ENDPOINT` | `string` (URL) | No | — | Custom S3 endpoint (MinIO, R2) |
378
+ | `S3_FORCE_PATH_STYLE` | `optionalBoolString` | No | `undefined` → `false` | Use path-style S3 URLs (required for MinIO) |
379
+ | `GCS_BUCKET` | `string` | If GCS | — | Google Cloud Storage bucket name |
380
+ | `GCS_PROJECT_ID` | `string` | If GCS | — | GCP project ID for GCS |
381
+ | `GOOGLE_APPLICATION_CREDENTIALS` | `string` | If GCS (non-GCP) | — | Path to GCP service account JSON key file (not needed on GCP with default credentials) |
382
+
383
+ ### Zod Type Helpers
384
+
385
+ | Helper | Accepts | Result |
386
+ |--------|---------|--------|
387
+ | `boolString` | `"true"`, `"false"`, `""` | Defaults to `"false"`, transforms to `boolean` |
388
+ | `optionalBoolString` | `"true"`, `"false"`, `""`, `undefined` | Transforms `"true"` → `true`, everything else → `false` |
389
+
390
+ ### Auto-Generated Secrets (Dev Only)
391
+
392
+ In non-production mode, `loadEnv()` auto-generates ephemeral secrets for:
393
+ - `JWT_SECRET` — if not set
394
+ - `REBASE_SERVICE_KEY` — if not set
395
+
396
+ These are random hex strings generated via `crypto.randomBytes(48)`. A warning is logged:
397
+
398
+ ```
399
+ ⚠️ Auto-generated secrets for: JWT_SECRET, REBASE_SERVICE_KEY. These are ephemeral — existing tokens will be invalidated on restart. Set them explicitly in .env for persistent sessions.
400
+ ```
401
+
402
+ > **WARNING FOR AGENTS:** Auto-generated secrets are **blocked in production**. The server will crash with a Zod validation error if `JWT_SECRET` or `REBASE_SERVICE_KEY` are not explicitly set when `NODE_ENV=production`.
403
+
404
+ ### Production Validations
405
+
406
+ When `NODE_ENV=production`, `loadEnv()` enforces these additional rules via Zod `.superRefine()`:
407
+
408
+ | Rule | Error if violated |
409
+ |------|-------------------|
410
+ | **CORS required** | At least one of `CORS_ORIGINS` or `FRONTEND_URL` must be set |
411
+ | **No auto-generated secrets** | `JWT_SECRET` and `REBASE_SERVICE_KEY` must be explicitly set |
412
+ | **No localhost URLs** | All URL-type env vars are scanned — any containing `localhost`, `127.0.0.1`, or `::1` will fail validation (unless `ALLOW_LOCALHOST_IN_PRODUCTION=true`) |
413
+
414
+ The localhost check applies to **all** string env vars (except `CORS_ORIGINS`) using a helper that parses URLs, custom protocols (e.g. `postgres://`), and plain `host:port` formats.
415
+
416
+ ### `.env.example` Reference
417
+
418
+ Copy the `.env.example` file as a starting point:
419
+
420
+ ```bash
421
+ cp .env.example .env
422
+ ```
423
+
424
+ The `.env.example` includes all variables with inline documentation. See `app/.env.example` in the repository.
425
+
426
+ ### `.env.production` (Frontend Build)
427
+
428
+ The `.env.production` file is read by Vite at build time to bake frontend config into the client bundle:
429
+
430
+ ```bash
431
+ # API URL: empty string = same-origin (backend + frontend share the same URL)
432
+ VITE_API_URL=
433
+
434
+ # Google OAuth Client ID (public — embedded in client bundle)
435
+ VITE_GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
436
+ ```
437
+
438
+ > **IMPORTANT FOR AGENTS:** `VITE_API_URL=` (empty) means same-origin — the frontend assumes the API is served from the same host. This works when using `serveSPA()` or a reverse proxy. Only set a full URL when the API is on a different domain.
439
+
440
+ ---
441
+
442
+ ## SPA Serving with `serveSPA()`
443
+
444
+ In production, the backend can serve the frontend SPA directly, eliminating the need for a separate web server or CDN.
445
+
446
+ ### `serveSPA()` Function Signature
447
+
448
+ ```typescript
449
+ import { serveSPA } from "@rebasepro/server";
450
+
451
+ function serveSPA<E extends Env>(app: Hono<E>, config: ServeSPAConfig): void;
452
+ ```
453
+
454
+ ### `ServeSPAConfig` Options
455
+
456
+ | Property | Type | Default | Description |
457
+ |----------|------|---------|-------------|
458
+ | `frontendPath` | `string` | — | **Required.** Absolute path to the frontend build directory |
459
+ | `apiBasePath` | `string` | `"/api"` | Base path for API routes (excluded from SPA handling) |
460
+ | `excludePaths` | `string[]` | `[]` | Additional paths to exclude from SPA handling (e.g. `["/health", "/ws", "/metrics"]`) |
461
+ | `indexFile` | `string` | `"index.html"` | Index file to serve for SPA routes |
462
+
463
+ ### How It Works
464
+
465
+ 1. Serves static files from `frontendPath` using `@hono/node-server/serve-static`
466
+ 2. For any GET request not matching `apiBasePath` or `excludePaths`, returns `index.html` (SPA fallback)
467
+ 3. If `frontendPath` doesn't exist, logs a warning and **disables SPA serving** (does not crash)
468
+ 4. If `index.html` is missing, passes through to the next handler
469
+
470
+ ### Usage Example
471
+
472
+ ```typescript
473
+ import { serveSPA } from "@rebasepro/server";
474
+ import path from "path";
475
+
476
+ const isProduction = env.NODE_ENV === "production";
477
+
478
+ // ... after initializeRebaseBackend() ...
479
+
480
+ if (isProduction) {
481
+ serveSPA(app, {
482
+ frontendPath: path.resolve(process.cwd(), "../frontend/dist"),
483
+ // apiBasePath defaults to "/api" — matches the default Rebase basePath
484
+ // excludePaths: ["/health", "/ws"],
485
+ });
486
+ }
487
+ ```
488
+
489
+ > **IMPORTANT FOR AGENTS:** Call `serveSPA()` **after** `initializeRebaseBackend()` and after mounting the `/health` endpoint. The SPA catch-all route (`*`) must be the last route registered to avoid intercepting API or health check requests.
490
+
491
+ ---
492
+
493
+ ## AWS (ECS/Fargate + RDS)
494
+
495
+ Deploy the Rebase Docker image to AWS using ECS (Fargate) with a managed PostgreSQL database via RDS.
496
+
497
+ ### Architecture
498
+
499
+ ```
500
+ Internet → ALB (HTTPS) → ECS Fargate (Rebase container) → RDS PostgreSQL
501
+ → S3 (file storage)
502
+ ```
503
+
504
+ ### Key Steps
505
+
506
+ 1. **PostgreSQL** — Create an RDS PostgreSQL 16+ instance (or Aurora Serverless v2). Enable automated backups. Place in a private subnet.
507
+ 2. **Docker Image** — Push the Rebase Docker image to ECR:
508
+ ```bash
509
+ # Build and tag
510
+ docker build -t rebase-backend -f app/backend/Dockerfile .
511
+ docker tag rebase-backend:latest <account-id>.dkr.ecr.<region>.amazonaws.com/rebase-backend:latest
512
+
513
+ # Push
514
+ aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
515
+ docker push <account-id>.dkr.ecr.<region>.amazonaws.com/rebase-backend:latest
516
+ ```
517
+ 3. **ECS Task Definition** — Define a Fargate task with the container image, port `3001`, and environment variables (see env var reference above). Use AWS Secrets Manager for `JWT_SECRET`, `REBASE_SERVICE_KEY`, and `DATABASE_URL`.
518
+ 4. **ALB** — Create an Application Load Balancer with HTTPS listener (ACM certificate). Target group pointing to the ECS service on port `3001`. Configure the health check path to `/health`.
519
+ 5. **File Storage** — Set `STORAGE_TYPE=s3` with an S3 bucket. Grant the ECS task role `s3:GetObject`, `s3:PutObject`, `s3:DeleteObject` permissions.
520
+
521
+ ### Key Environment Variables
522
+
523
+ ```bash
524
+ DATABASE_URL=postgresql://rebase:<password>@<rds-endpoint>:5432/rebase
525
+ JWT_SECRET=<your-secret-min-32-chars>
526
+ REBASE_SERVICE_KEY=<your-service-key-min-32-chars>
527
+ CORS_ORIGINS=https://yourdomain.com
528
+ FRONTEND_URL=https://yourdomain.com
529
+ NODE_ENV=production
530
+ STORAGE_TYPE=s3
531
+ S3_BUCKET=your-rebase-uploads
532
+ S3_REGION=us-east-1
533
+ S3_ACCESS_KEY_ID=<iam-key-or-use-task-role>
534
+ S3_SECRET_ACCESS_KEY=<iam-secret>
535
+ ```
536
+
537
+ > **TIP:** Use IAM task roles instead of static access keys for S3. Attach the policy to the ECS task execution role and omit `S3_ACCESS_KEY_ID` / `S3_SECRET_ACCESS_KEY` — the AWS SDK will use the role credentials automatically.
538
+
539
+ ---
540
+
541
+ ## GCP (Cloud Run + Cloud SQL)
542
+
543
+ Deploy the Rebase Docker image to Cloud Run with Cloud SQL for PostgreSQL.
544
+
545
+ ### Architecture
546
+
547
+ ```
548
+ Internet → Cloud Run (HTTPS, auto-scaling) → Cloud SQL PostgreSQL
549
+ → GCS or S3-compatible (file storage)
550
+ ```
551
+
552
+ ### Key Steps
553
+
554
+ 1. **PostgreSQL** — Create a Cloud SQL for PostgreSQL 16+ instance. Enable private IP or use the Cloud SQL Auth Proxy.
555
+ 2. **Docker Image** — Push to Artifact Registry:
556
+ ```bash
557
+ # Build and tag
558
+ docker build -t rebase-backend -f app/backend/Dockerfile .
559
+ docker tag rebase-backend:latest <region>-docker.pkg.dev/<project-id>/rebase/backend:latest
560
+
561
+ # Push
562
+ gcloud auth configure-docker <region>-docker.pkg.dev
563
+ docker push <region>-docker.pkg.dev/<project-id>/rebase/backend:latest
564
+ ```
565
+ 3. **Cloud Run Service** — Deploy with the Cloud SQL connection:
566
+ ```bash
567
+ gcloud run deploy rebase-backend \
568
+ --image <region>-docker.pkg.dev/<project-id>/rebase/backend:latest \
569
+ --region <region> \
570
+ --port 3001 \
571
+ --add-cloudsql-instances <project-id>:<region>:<instance-name> \
572
+ --set-env-vars "NODE_ENV=production,CORS_ORIGINS=https://yourdomain.com,FRONTEND_URL=https://yourdomain.com" \
573
+ --set-secrets "DATABASE_URL=rebase-db-url:latest,JWT_SECRET=rebase-jwt-secret:latest,REBASE_SERVICE_KEY=rebase-service-key:latest" \
574
+ --min-instances 1 \
575
+ --memory 512Mi \
576
+ --allow-unauthenticated
577
+ ```
578
+ 4. **Custom Domain** — Map a custom domain in Cloud Run settings. Cloud Run provides HTTPS automatically.
579
+ 5. **File Storage** — Use native GCS support with `STORAGE_TYPE=gcs`:
580
+ ```bash
581
+ STORAGE_TYPE=gcs
582
+ GCS_BUCKET=your-rebase-uploads
583
+ GCS_PROJECT_ID=your-gcp-project-id
584
+ ```
585
+ On Cloud Run, the default service account credentials are used automatically — no key file needed. Alternatively, you can fall back to `STORAGE_TYPE=s3` with the GCS S3-compatible interop endpoint.
586
+
587
+ ### Cloud SQL Auth Proxy (Connection String)
588
+
589
+ When using the Cloud SQL connector in Cloud Run, the database is exposed via a Unix socket:
590
+
591
+ ```bash
592
+ DATABASE_URL=postgresql://rebase:<password>@localhost/rebase?host=/cloudsql/<project-id>:<region>:<instance-name>
593
+ ```
594
+
595
+ > **WARNING:** Cloud Run has a **request timeout** (default 300s, max 3600s) and can scale to zero. WebSocket connections will be terminated when instances scale down. If you rely on Rebase realtime features, set `--min-instances 1` and increase the request timeout. For heavy realtime usage, consider GCE (Compute Engine) with Docker Compose instead.
596
+
597
+ ---
598
+
599
+ ## Azure (Container Apps + PostgreSQL Flexible Server)
600
+
601
+ Deploy the Rebase Docker image to Azure Container Apps with Azure Database for PostgreSQL.
602
+
603
+ ### Architecture
604
+
605
+ ```
606
+ Internet → Azure Container Apps (HTTPS, auto-scaling) → Azure Database for PostgreSQL
607
+ → Azure Blob Storage (file storage)
608
+ ```
609
+
610
+ ### Key Steps
611
+
612
+ 1. **PostgreSQL** — Create an Azure Database for PostgreSQL Flexible Server. Choose a Burstable or General Purpose tier. Place in an EU region (West Europe, North Europe, or France Central).
613
+ 2. **Docker Image** — Push to Azure Container Registry (ACR):
614
+ ```bash
615
+ # Login to ACR
616
+ az acr login --name YourRegistryName
617
+
618
+ # Build and push
619
+ docker build -t yourregistryname.azurecr.io/rebase-backend:latest -f app/backend/Dockerfile .
620
+ docker push yourregistryname.azurecr.io/rebase-backend:latest
621
+ ```
622
+ 3. **Container App** — Create a Container Apps Environment and deploy:
623
+ - Point to your ACR image
624
+ - Set the target port to `3001`
625
+ - Enable Ingress for external traffic
626
+ - Configure environment variables (see below)
627
+ 4. **Networking** — If the PostgreSQL server uses private networking, configure VNet integration in the Container Apps Environment.
628
+ 5. **File Storage** — Use `STORAGE_TYPE=s3` with Azure Blob Storage via the S3-compatible API, or use Cloudflare R2.
629
+
630
+ ### Key Environment Variables
631
+
632
+ ```bash
633
+ DATABASE_URL=postgresql://rebase_admin:<password>@<server-name>.postgres.database.azure.com:5432/rebase
634
+ JWT_SECRET=<your-secret-min-32-chars>
635
+ REBASE_SERVICE_KEY=<your-service-key-min-32-chars>
636
+ CORS_ORIGINS=https://yourdomain.com
637
+ FRONTEND_URL=https://yourdomain.com
638
+ NODE_ENV=production
639
+ ALLOW_REGISTRATION=false
640
+ ```
641
+
642
+ > **TIP:** Azure Container Apps provides built-in HTTPS with automatic TLS certificates. Use Managed Identity instead of static credentials for accessing Azure services.
643
+
644
+ ---
645
+
646
+ ## Scaleway (Serverless Containers + Managed PostgreSQL)
647
+
648
+ Deploy the Rebase Docker image to Scaleway Serverless Containers. Scaleway is a premier European cloud provider with datacenters in Paris, Amsterdam, and Warsaw — ideal for EU data sovereignty.
649
+
650
+ ### Architecture
651
+
652
+ ```
653
+ Internet → Scaleway Serverless Container (HTTPS) → Managed PostgreSQL
654
+ → Object Storage (S3-compatible)
655
+ ```
656
+
657
+ ### Key Steps
658
+
659
+ 1. **PostgreSQL** — Create a Managed Database for PostgreSQL in your preferred EU region (e.g., Paris `fr-par`).
660
+ 2. **Docker Image** — Push to Scaleway Container Registry:
661
+ ```bash
662
+ # Build and push
663
+ docker build -t rg.fr-par.scw.cloud/rebase-apps/rebase-backend:latest -f app/backend/Dockerfile .
664
+ docker push rg.fr-par.scw.cloud/rebase-apps/rebase-backend:latest
665
+ ```
666
+ 3. **Serverless Container** — Deploy from the Scaleway Console or CLI:
667
+ - Select your image from the Container Registry
668
+ - Set the port to `3001`
669
+ - Configure environment variables (see below)
670
+ 4. **File Storage** — Use `STORAGE_TYPE=s3` with Scaleway Object Storage (natively S3-compatible):
671
+ ```bash
672
+ S3_ENDPOINT=https://s3.fr-par.scw.cloud
673
+ S3_BUCKET=your-rebase-uploads
674
+ S3_REGION=fr-par
675
+ S3_FORCE_PATH_STYLE=true
676
+ ```
677
+
678
+ ### Key Environment Variables
679
+
680
+ ```bash
681
+ DATABASE_URL=postgresql://user:<password>@<instance-ip>:5432/rebase
682
+ JWT_SECRET=<your-secret-min-32-chars>
683
+ REBASE_SERVICE_KEY=<your-service-key-min-32-chars>
684
+ CORS_ORIGINS=https://yourdomain.com
685
+ FRONTEND_URL=https://yourdomain.com
686
+ NODE_ENV=production
687
+ ALLOW_REGISTRATION=false
688
+ STORAGE_TYPE=s3
689
+ S3_ENDPOINT=https://s3.fr-par.scw.cloud
690
+ S3_BUCKET=your-rebase-uploads
691
+ S3_REGION=fr-par
692
+ S3_FORCE_PATH_STYLE=true
693
+ ```
694
+
695
+ > **TIP:** Scaleway Object Storage is natively S3-compatible — no interop layer needed. Set `S3_FORCE_PATH_STYLE=true` for compatibility.
696
+
697
+ ---
698
+
699
+ ## Hetzner (VPS + Docker Compose)
700
+
701
+ The most cost-effective production deployment. A single Hetzner VPS running Docker Compose.
702
+
703
+ ### Architecture
704
+
705
+ ```
706
+ Internet → Caddy (HTTPS, auto-TLS) → Rebase container (port 3001)
707
+ → PostgreSQL container
708
+ → Hetzner Volume (persistent storage)
709
+ ```
710
+
711
+ ### Setup
712
+
713
+ 1. **Provision a VPS** — A Hetzner CPX21 (3 vCPU, 4 GB RAM, ~€8/mo) handles most workloads. Choose Ubuntu 24.04.
714
+ 2. **Install Docker** — SSH in and install Docker + Docker Compose:
715
+ ```bash
716
+ curl -fsSL https://get.docker.com | sh
717
+ ```
718
+ 3. **Clone your project** and copy your `.env` file to the server.
719
+ 4. **Use the existing `docker-compose.yml`** — the template generated by `rebase init` works out of the box. Just update `.env` with production values:
720
+ ```bash
721
+ DATABASE_PASSWORD=<strong-random-password>
722
+ JWT_SECRET=<your-secret-min-32-chars>
723
+ REBASE_SERVICE_KEY=<your-service-key-min-32-chars>
724
+ CORS_ORIGINS=https://yourdomain.com
725
+ FRONTEND_URL=https://yourdomain.com
726
+ NODE_ENV=production
727
+ ALLOW_REGISTRATION=false
728
+ ```
729
+ 5. **Reverse Proxy (Caddy)** — Add a Caddy service for automatic HTTPS:
730
+ ```yaml
731
+ # Add to docker-compose.yml
732
+ caddy:
733
+ image: caddy:2-alpine
734
+ restart: unless-stopped
735
+ ports:
736
+ - "80:80"
737
+ - "443:443"
738
+ - "443:443/udp" # HTTP/3
739
+ volumes:
740
+ - ./Caddyfile:/etc/caddy/Caddyfile
741
+ - caddy_data:/data
742
+ - caddy_config:/config
743
+ depends_on:
744
+ - backend
745
+
746
+ # Add to volumes:
747
+ caddy_data:
748
+ driver: local
749
+ caddy_config:
750
+ driver: local
751
+ ```
752
+
753
+ Create a `Caddyfile`:
754
+ ```
755
+ yourdomain.com {
756
+ reverse_proxy backend:3001
757
+ }
758
+ ```
759
+ 6. **Start everything:**
760
+ ```bash
761
+ docker compose up -d
762
+ ```
763
+ 7. **Persistent Storage** — Attach a Hetzner Volume for the `postgres_data` and `uploads` Docker volumes if you need data to survive server rebuilds.
764
+
765
+ ### Backups
766
+
767
+ Schedule automated PostgreSQL backups via cron on the host:
768
+
769
+ ```bash
770
+ # /etc/cron.d/rebase-backup
771
+ 0 3 * * * root docker compose -f /path/to/docker-compose.yml exec -T db pg_dump -U rebase rebase | gzip > /backups/rebase-$(date +\%Y\%m\%d).sql.gz
772
+ ```
773
+
774
+ > **TIP:** Hetzner also offers managed entities (server-level) and the option to attach a Hetzner Volume for data that survives server rebuilds. For production, strongly consider both.
775
+
776
+ ---
777
+
778
+ ## PaaS (Railway, Render, Fly.io)
779
+
780
+ Docker-based PaaS platforms that deploy directly from your repo or Docker image.
781
+
782
+ ### General Pattern
783
+
784
+ All PaaS platforms follow the same workflow:
785
+ 1. Connect your Git repository or point to a Dockerfile
786
+ 2. Provision a managed PostgreSQL add-on
787
+ 3. Set environment variables (see env var reference above)
788
+ 4. Deploy — the platform builds and runs the Docker image
789
+
790
+ ### Platform-Specific Notes
791
+
792
+ | Platform | PostgreSQL | Notes |
793
+ |----------|-----------|-------|
794
+ | **Railway** | Built-in add-on | Connects via `DATABASE_URL` injected automatically. Supports persistent volumes for file uploads. |
795
+ | **Render** | Managed PostgreSQL | Use a "Web Service" with Docker runtime. Free tier has cold starts — use paid for production. |
796
+ | **Fly.io** | Fly Postgres (community) | Closest to self-hosted — Fly Postgres is a managed wrapper around standard Postgres. Supports persistent volumes via `fly volumes`. |
797
+
798
+ ### Minimum Environment Variables
799
+
800
+ ```bash
801
+ DATABASE_URL=<provided-by-platform>
802
+ JWT_SECRET=<your-secret-min-32-chars>
803
+ REBASE_SERVICE_KEY=<your-service-key-min-32-chars>
804
+ CORS_ORIGINS=https://your-app.up.railway.app # or your custom domain
805
+ FRONTEND_URL=https://your-app.up.railway.app
806
+ NODE_ENV=production
807
+ ALLOW_REGISTRATION=false
808
+ ```
809
+
810
+ > **WARNING:** Most PaaS free tiers have ephemeral filesystems — uploaded files will be lost on redeploy. Use `STORAGE_TYPE=s3` with an external object storage provider (Cloudflare R2, AWS S3, MinIO) for production file storage.
811
+
812
+ ---
813
+
814
+ ## Health Check Endpoint
815
+
816
+ The Docker `HEALTHCHECK` instruction hits `/health`. Mount it in your backend entry:
817
+
818
+ ```typescript
819
+ app.get("/health", async (c) => {
820
+ const result = await backend.healthCheck();
821
+ const status = result.healthy ? 200 : 503;
822
+ return c.json({
823
+ status: result.healthy ? "ok" : "degraded",
824
+ latencyMs: result.latencyMs,
825
+ ...(result.details ? { details: result.details } : {})
826
+ }, status);
827
+ });
828
+ ```
829
+
830
+ `healthCheck()` runs `SELECT 1` against the database and returns latency. Returns `{ healthy: boolean, latencyMs: number, details?: { error: string } }`.
831
+
832
+ ---
833
+
834
+ ## Graceful Shutdown
835
+
836
+ The `RebaseBackendInstance` exposes a `shutdown()` method for clean container termination:
837
+
838
+ ```typescript
839
+ const backend = await initializeRebaseBackend({ ... });
840
+
841
+ const gracefulShutdown = async (signal: string) => {
842
+ await backend.shutdown(); // Stops cron, realtime, drains HTTP (15s timeout)
843
+ await postgresResources.pool.end(); // Close database pool
844
+ process.exit(0);
845
+ };
846
+
847
+ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
848
+ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
849
+ ```
850
+
851
+ `shutdown(timeoutMs?: number)`:
852
+ - Default timeout: `15000` ms
853
+ - Stops cron scheduler
854
+ - Destroys realtime services (LISTEN clients, debounce timers)
855
+ - Closes HTTP server and drains in-flight requests
856
+ - Pass `0` to skip the force-exit timer (useful in tests)
857
+
858
+ > **WARNING FOR AGENTS:** Do NOT call `server.close()` separately when using `backend.shutdown()` — it already closes the HTTP server internally. Calling `server.close()` twice will deadlock.
859
+
860
+ ---
861
+
862
+ ## ⛔ Agent Deployment Rules
863
+
864
+ **Agents should NEVER deploy or run deployment commands unless explicitly asked by the user in the current conversation.** This includes:
865
+ - `rebase deploy` (any variant)
866
+ - `gcloud run deploy`
867
+ - `terraform apply` (any variant that deploys resources)
868
+ - `docker compose up` in production
869
+ - Any command targeting staging or production environments
870
+
871
+ **What agents CAN do:**
872
+ - Edit source code
873
+ - Run builds (`pnpm run build`)
874
+ - Run tests (`pnpm test`)
875
+ - Run local dev server (`pnpm dev`)
876
+ - Check logs (read-only)
877
+ - Create or edit Dockerfiles and docker-compose files
878
+ - Create or edit `.env` files
879
+ - Run deployment commands *only* if the user explicitly asks you to deploy in the current conversation. Otherwise, provide the exact commands for the user to run.
880
+
881
+ ---
882
+
883
+ ## References
884
+
885
+ - **Documentation:** [rebase.pro/docs](https://rebase.pro/docs)
886
+ - **GitHub:** [github.com/rebasepro/rebase](https://github.com/rebasepro/rebase)
887
+ - **Dockerfile:** `app/backend/Dockerfile`
888
+ - **Docker Compose (monorepo):** `app/backend/docker-compose.yml`
889
+ - **Docker Compose (template):** `packages/cli/templates/template/docker-compose.yml`
890
+ - **Entrypoint:** `app/backend/entrypoint.sh`
891
+ - **Env Schema:** `packages/server/src/env.ts`
892
+ - **serveSPA:** `packages/server/src/serve-spa.ts`
893
+ - **Backend Entry:** `app/backend/src/index.ts`
894
+ - **.env.example:** `app/.env.example`