|
1 | | -# Deployment (Vercel + Cloud Run + Cloud SQL Postgres) |
| 1 | +# Deployment (Engineering Runbook) |
2 | 2 |
|
3 | | -This doc describes a simple production setup for EasyRelocate: |
4 | | -- **Frontend**: Vercel (static hosting for the Vite app) |
5 | | -- **Backend**: Google Cloud Run (FastAPI) |
6 | | -- **Database**: Cloud SQL for PostgreSQL |
7 | | -- **Auth**: **admin-created workspace tokens** (no user accounts) |
| 3 | +This doc describes two production-minded deployment options for EasyRelocate: |
8 | 4 |
|
9 | | -## 1) Create Cloud SQL Postgres |
| 5 | +**Option A (Managed GCP)** |
| 6 | +- Frontend: **Vercel** |
| 7 | +- Backend: **Cloud Run** |
| 8 | +- Database: **Cloud SQL Postgres** |
10 | 9 |
|
11 | | -Create a Cloud SQL Postgres instance and a database (example: `easyrelocate`), plus a DB user. |
| 10 | +**Option B (Self-hosted server)** |
| 11 | +- Frontend: **Vercel** |
| 12 | +- Backend + Database: **your miniPC / server** (Docker Compose) |
| 13 | +- Reverse proxy: **Caddy/Nginx** (HTTPS) |
| 14 | +- Optional exposure: **FRP** |
12 | 15 |
|
13 | | -You will need: |
| 16 | +Both options use the same app code. The only differences are environment variables (mainly `DATABASE_URL` and CORS). |
| 17 | + |
| 18 | +## Architecture / trust boundaries |
| 19 | + |
| 20 | +- The **browser** (web app + extension) talks to your backend via HTTP(S). |
| 21 | +- The backend is stateless (Cloud Run or container on a server). |
| 22 | +- All state lives in the database (SQLite for local dev, Postgres for production). |
| 23 | +- Auth is **workspace tokens**: |
| 24 | + - `Authorization: Bearer er_ws_...` |
| 25 | + - No user accounts. |
| 26 | + |
| 27 | +## Configuration checklist |
| 28 | + |
| 29 | +### Frontend (Vercel) |
| 30 | +- `VITE_API_BASE_URL` = your backend public URL (no trailing slash) |
| 31 | +- `VITE_GOOGLE_MAPS_API_KEY` = browser key (restrict by HTTP referrer to your domain) |
| 32 | + |
| 33 | +### Backend (Cloud Run or server) |
| 34 | +- `DATABASE_URL` (Postgres in production; SQLite only for local dev) |
| 35 | +- `CORS_ALLOW_ORIGINS` = comma-separated list of allowed web origins (your Vercel domain(s)) |
| 36 | +- Optional keys: |
| 37 | + - `GOOGLE_MAPS_API_KEY` (server-side geocoding) |
| 38 | + - `OPENROUTER_API_KEY` and `OPENROUTER_MODEL` |
| 39 | +- Optional self-serve onboarding: |
| 40 | + - `ENABLE_PUBLIC_WORKSPACE_ISSUE=1` |
| 41 | + - `PUBLIC_WORKSPACE_TTL_DAYS=30` |
| 42 | + |
| 43 | +## Option A — Vercel + Cloud Run + Cloud SQL Postgres |
| 44 | + |
| 45 | +### A1) Create Cloud SQL Postgres |
| 46 | + |
| 47 | +Create: |
| 48 | +- a Cloud SQL instance (Postgres), |
| 49 | +- a database (e.g. `easyrelocate`), |
| 50 | +- a DB user (e.g. `easyrelocate_user`). |
| 51 | + |
| 52 | +Record: |
| 53 | +- `DB_NAME` |
14 | 54 | - `DB_USER` |
15 | 55 | - `DB_PASSWORD` |
16 | | -- `DB_NAME` |
17 | 56 | - `INSTANCE_CONNECTION_NAME` (format: `project:region:instance`) |
18 | 57 |
|
19 | | -## 2) Deploy backend to Cloud Run |
| 58 | +### A2) Deploy backend to Cloud Run |
20 | 59 |
|
21 | | -### Backend env vars (Cloud Run) |
| 60 | +1) Containerize the backend (Docker image). |
| 61 | +2) Deploy the image to Cloud Run. |
| 62 | +3) Attach the Cloud SQL instance to the Cloud Run service (Cloud SQL connector). |
| 63 | + |
| 64 | +#### A2.1) Backend env vars (Cloud Run) |
22 | 65 |
|
23 | 66 | Set these as Cloud Run environment variables: |
24 | | -- `DATABASE_URL` (Postgres) |
25 | | -- `CORS_ALLOW_ORIGINS` (comma-separated list, usually your Vercel domain(s)) |
26 | | -- `GOOGLE_MAPS_API_KEY` (optional, for server-side geocoding) |
27 | | -- `OPENROUTER_API_KEY` (optional, for “Add selected post” extraction) |
28 | | -- `OPENROUTER_MODEL` (optional; default `z-ai/glm-4.5-air:free`) |
29 | | -- `ENABLE_PUBLIC_WORKSPACE_ISSUE` (optional; enable anonymous 30-day tokens) |
30 | | -- `PUBLIC_WORKSPACE_TTL_DAYS` (optional; default `30`) |
| 67 | +- `DATABASE_URL` |
| 68 | +- `CORS_ALLOW_ORIGINS` |
| 69 | +- optional feature keys (`GOOGLE_MAPS_API_KEY`, `OPENROUTER_API_KEY`, etc.) |
31 | 70 |
|
32 | | -#### `DATABASE_URL` examples |
| 71 | +#### A2.2) `DATABASE_URL` examples |
33 | 72 |
|
34 | | -If you connect Cloud Run to Cloud SQL using the Cloud SQL connector (recommended), you can use a Unix socket host: |
| 73 | +Cloud SQL connector / Unix socket (recommended on Cloud Run): |
35 | 74 | ```text |
36 | 75 | postgresql+psycopg://DB_USER:DB_PASSWORD@/DB_NAME?host=/cloudsql/INSTANCE_CONNECTION_NAME |
37 | 76 | ``` |
38 | 77 |
|
39 | | -If you use a private IP / direct host: |
| 78 | +Private IP / direct host: |
40 | 79 | ```text |
41 | 80 | postgresql+psycopg://DB_USER:DB_PASSWORD@DB_HOST:5432/DB_NAME |
42 | 81 | ``` |
43 | 82 |
|
44 | | -### CORS (`CORS_ALLOW_ORIGINS`) |
| 83 | +#### A2.3) CORS allowlist (`CORS_ALLOW_ORIGINS`) |
45 | 84 |
|
46 | 85 | Example: |
47 | 86 | ```text |
48 | 87 | https://your-vercel-app.vercel.app,https://easyrelocate.yourdomain.com |
49 | 88 | ``` |
50 | 89 |
|
51 | | -Notes: |
52 | | -- If you change your Vercel preview URLs often, add the stable custom domain (recommended). |
53 | | -- Local dev still defaults to `http://127.0.0.1:5173` + `http://localhost:5173` when `CORS_ALLOW_ORIGINS` is not set. |
| 90 | +Operational note: |
| 91 | +- Prefer a stable custom domain. Vercel preview URLs change frequently. |
54 | 92 |
|
55 | | -## 3) Deploy frontend to Vercel |
| 93 | +### A3) Deploy frontend to Vercel |
56 | 94 |
|
57 | | -### Frontend env vars (Vercel) |
| 95 | +Set Vercel env vars: |
| 96 | +```text |
| 97 | +VITE_API_BASE_URL=https://YOUR_CLOUD_RUN_URL |
| 98 | +VITE_GOOGLE_MAPS_API_KEY=YOUR_BROWSER_KEY |
| 99 | +``` |
58 | 100 |
|
59 | | -Set these in Vercel project settings: |
60 | | -- `VITE_API_BASE_URL` (your Cloud Run URL, without trailing slash) |
61 | | -- `VITE_GOOGLE_MAPS_API_KEY` (browser key; HTTP referrer-restricted to your Vercel domain) |
| 101 | +### A4) Create workspace tokens |
62 | 102 |
|
63 | | -Example: |
| 103 | +EasyRelocate does not ship a signup/login flow. You have two choices: |
| 104 | + |
| 105 | +#### A4.1) Admin-created tokens (recommended) |
| 106 | +Run the script against the same Postgres `DATABASE_URL` the backend uses: |
| 107 | +```bash |
| 108 | +cd backend |
| 109 | +DATABASE_URL="postgresql+psycopg://..." python scripts/create_workspace.py --ttl-days 30 |
| 110 | +``` |
| 111 | + |
| 112 | +Output: |
64 | 113 | ```text |
65 | | -VITE_API_BASE_URL=https://easyrelocate-api-xxxxx-uc.a.run.app |
| 114 | +workspace_id=... |
| 115 | +workspace_token=er_ws_... |
| 116 | +expires_at=... |
66 | 117 | ``` |
67 | 118 |
|
68 | | -## 4) Create admin workspace tokens |
| 119 | +Users paste the token into: |
| 120 | +- Web app: Compare page → Workspace panel → Save |
| 121 | +- Chrome extension: Options → Workspace token → Save (use the same token) |
69 | 122 |
|
70 | | -EasyRelocate does not ship a user signup/login flow. Instead, you (admin) create “workspace tokens” |
71 | | -and distribute them to users. All reads/writes are scoped to a workspace. |
| 123 | +#### A4.2) Self-serve tokens (optional) |
| 124 | +Enable: |
| 125 | +```text |
| 126 | +ENABLE_PUBLIC_WORKSPACE_ISSUE=1 |
| 127 | +PUBLIC_WORKSPACE_TTL_DAYS=30 |
| 128 | +``` |
72 | 129 |
|
73 | | -If you prefer a self-serve onboarding flow (no admin distribution), you can enable: |
74 | | -- `ENABLE_PUBLIC_WORKSPACE_ISSUE=1` |
75 | | -- `PUBLIC_WORKSPACE_TTL_DAYS=30` |
| 130 | +Then the web UI can call `POST /api/workspaces/issue` to mint tokens. |
76 | 131 |
|
77 | | -Then the web UI can call `POST /api/workspaces/issue` to generate a new 30-day token. |
| 132 | +Security note: |
| 133 | +- This endpoint is public. In production, add protection (rate limiting / bot protection) before |
| 134 | + sharing widely. |
78 | 135 |
|
79 | | -Production warning: this endpoint is public — protect it with rate limiting / bot protection. |
| 136 | +## Option B — Vercel + Self-hosted server (miniPC) + Postgres |
80 | 137 |
|
81 | | -### Create a token (recommended workflow) |
| 138 | +This is the lowest “monthly bill” option if you already have hardware, but you must handle: |
| 139 | +patching, backups, and uptime. |
82 | 140 |
|
83 | | -Run the script against the same `DATABASE_URL` your backend uses: |
84 | | -```bash |
85 | | -cd backend |
86 | | -DATABASE_URL="postgresql+psycopg://..." python scripts/create_workspace.py |
| 141 | +### B1) Run Postgres + backend on the server |
| 142 | + |
| 143 | +Recommended: |
| 144 | +- Use Docker Compose. |
| 145 | +- Keep Postgres private (not exposed to the internet). |
| 146 | +- Expose only the reverse proxy (80/443). |
| 147 | + |
| 148 | +Minimum you need: |
| 149 | +- A public backend URL (e.g. `https://api.example.com`) |
| 150 | +- `DATABASE_URL` pointing at the Postgres container |
| 151 | + |
| 152 | +Example `DATABASE_URL` (Compose network): |
| 153 | +```text |
| 154 | +postgresql+psycopg://easyrelocate_user:DB_PASSWORD@postgres:5432/easyrelocate |
87 | 155 | ``` |
88 | 156 |
|
89 | | -Output: |
| 157 | +### B2) Reverse proxy + HTTPS |
| 158 | + |
| 159 | +Use Caddy or Nginx to terminate HTTPS and proxy to the backend container. |
| 160 | + |
| 161 | +If you use FRP: |
| 162 | +- Prefer mapping FRP to 443 with TLS termination at the server (Caddy/Nginx). |
| 163 | +- If FRP terminates TLS upstream, ensure the backend sees correct headers and set a strict CORS allowlist. |
| 164 | + |
| 165 | +### B3) Vercel config |
| 166 | + |
| 167 | +Set: |
90 | 168 | ```text |
91 | | -workspace_id=... |
92 | | -workspace_token=er_ws_... |
| 169 | +VITE_API_BASE_URL=https://api.example.com |
93 | 170 | ``` |
94 | 171 |
|
95 | | -Treat `workspace_token` like a password. |
| 172 | +And on the backend: |
| 173 | +```text |
| 174 | +CORS_ALLOW_ORIGINS=https://your-vercel-domain.vercel.app,https://your-custom-domain.com |
| 175 | +``` |
96 | 176 |
|
97 | | -### Where users paste the token |
| 177 | +### B4) Backups (required) |
98 | 178 |
|
99 | | -- **Web app**: Compare page → **Workspace** panel → paste token → Save. |
100 | | -- **Chrome extension**: Extension options → **Workspace token** → Save (use the same token). |
| 179 | +At minimum: |
| 180 | +- daily `pg_dump` |
| 181 | +- keep 7–30 days of backups |
| 182 | +- periodically test restore |
101 | 183 |
|
102 | | -All API calls send `Authorization: Bearer <workspace_token>`. |
| 184 | +## Troubleshooting |
103 | 185 |
|
104 | | -## 5) Production notes |
| 186 | +### Frontend loads but API calls hang / abort |
| 187 | +- Confirm `VITE_API_BASE_URL` is correct and reachable from the browser. |
| 188 | +- Prefer `http://127.0.0.1:8000` for local dev (avoid IPv6-only `localhost` issues). |
105 | 189 |
|
106 | | -- Cloud Run instances are ephemeral; use Postgres in production (avoid relying on local SQLite files). |
107 | | -- You’ll eventually want migrations (e.g., Alembic) instead of `create_all`, but `create_all` is fine |
108 | | - for early versions. |
| 190 | +### 401 “Missing workspace token” |
| 191 | +- Ensure the workspace token is saved in: |
| 192 | + - Web app Compare page → Workspace panel → Save |
| 193 | + - Extension Options → Workspace token → Save |
| 194 | + |
| 195 | +### CORS errors in browser console |
| 196 | +- Set backend `CORS_ALLOW_ORIGINS` to include your frontend origin(s). |
109 | 197 |
|
110 | 198 | ## Local vs cloud DB (dev convenience) |
| 199 | + |
111 | 200 | If you want to keep both DB URLs in one `.env`, you can set: |
112 | 201 | - `DATABASE_URL_LOCAL=...` |
113 | 202 | - `DATABASE_URL_CLOUD=...` |
114 | 203 | - `EASYRELOCATE_DB=local|cloud` |
115 | 204 |
|
116 | | -For local dev, you can also use the helper script: |
| 205 | +For local dev, you can also use: |
117 | 206 | `./easyDeploy.sh --db local`. |
0 commit comments