135 lines
6.1 KiB
Markdown
135 lines
6.1 KiB
Markdown
# Deploying Cerebrus to Cloud Run
|
|
|
|
Two independent Cloud Run services, built with **Cloud Build** → **Artifact
|
|
Registry**, region **us-central1**. Each app's `.env` is **baked into its image**
|
|
(no Secret Manager).
|
|
|
|
- `cerebrus-backend` — Express/Bun API (`oven/bun` runtime, listens on `$PORT`).
|
|
- `cerebrus-frontend` — Vite SPA served by a small **Bun** server (built assets, `$PORT`).
|
|
|
|
> Secrets are baked into the images, so **keep the Artifact Registry repo private**
|
|
> — anyone who can pull an image can read its `.env`.
|
|
|
|
## Prerequisites
|
|
|
|
- `gcloud` CLI authenticated (`gcloud auth login`) with Owner/Editor on the project.
|
|
- A GCP **project id** (billing enabled).
|
|
- Supabase `DATABASE_URL`, WorkOS app, and a GitHub OAuth App (see `apps/backend/README.md`).
|
|
|
|
## One-time: create the env files
|
|
|
|
```bash
|
|
cp apps/backend/.env.production.example apps/backend/.env.production
|
|
cp apps/frontend/.env.production.example apps/frontend/.env.production
|
|
```
|
|
|
|
Fill in the secrets now (WorkOS, Supabase, GitHub, `APP_ENCRYPTION_KEY`). Leave the
|
|
URL fields at their placeholder for the first deploy — you'll set them in step 2.
|
|
These files are gitignored; they only ever exist locally and inside the images.
|
|
|
|
## Deploy (two-phase, because each service needs the other's URL)
|
|
|
|
### Phase 1 — first deploy to learn the URLs
|
|
```bash
|
|
./deploy.sh <PROJECT_ID>
|
|
```
|
|
This enables APIs, creates the `cerebrus` Artifact Registry repo, grants Cloud
|
|
Build deploy permission, builds + deploys both services, and prints their URLs, e.g.:
|
|
|
|
```
|
|
cerebrus-backend https://cerebrus-backend-abc123-uc.a.run.app
|
|
cerebrus-frontend https://cerebrus-frontend-def456-uc.a.run.app
|
|
```
|
|
|
|
> **One origin.** The browser only ever talks to the **frontend** URL. The Bun
|
|
> server in the frontend image reverse-proxies `/auth` and `/api` to the backend,
|
|
> so the session cookie stays first-party (cross-site `*.run.app` cookies are
|
|
> blocked by browsers). So every public URL you configure is the **frontend** URL —
|
|
> and the frontend needs the backend URL to proxy to (`BACKEND_URL`, set once below).
|
|
|
|
`deploy.sh` builds and deploys the backend, frontend, and scanner job. It reads
|
|
`BACKEND_URL` from `apps/frontend/.env.production` and sets it on the frontend
|
|
Cloud Run service automatically. You manage the rest of the config via the
|
|
`.env.production` files (backend config is baked into its image).
|
|
|
|
### Phase 2 — wire the URLs and redeploy
|
|
1. Edit **`apps/backend/.env.production`**:
|
|
- `NODE_ENV=production`
|
|
- `FRONTEND_URL=<frontend url>`
|
|
- `WORKOS_REDIRECT_URI=<frontend url>/auth/callback`
|
|
- `GITHUB_REDIRECT_URI=<frontend url>/api/github/callback`
|
|
- `SCAN_CALLBACK_URL=<backend url>` (must be the backend, not the frontend,
|
|
because the scanner calls `/internal/*`)
|
|
2. **`apps/frontend/.env.production`**:
|
|
- `BACKEND_URL=<backend url>`
|
|
- leave `VITE_API_URL` unset (same-origin)
|
|
3. Update the external dashboards (all on the **frontend** origin):
|
|
- **WorkOS**: add `<frontend url>/auth/callback` as a Redirect URI, and set the
|
|
sign-out redirect to `<frontend url>`.
|
|
- **GitHub OAuth App**: set the Authorization callback URL to
|
|
`<frontend url>/api/github/callback`.
|
|
4. Redeploy. `deploy.sh` now reads `BACKEND_URL` from `apps/frontend/.env.production`
|
|
and sets it on the frontend Cloud Run service automatically. It also builds and
|
|
deploys the scanner Cloud Run job used when `SCAN_RUNNER=cloudrun`:
|
|
```bash
|
|
./deploy.sh <PROJECT_ID>
|
|
```
|
|
To skip the scanner job, run with `DEPLOY_SCANNER=0 ./deploy.sh <PROJECT_ID>`.
|
|
|
|
## Database
|
|
|
|
Supabase Postgres is publicly reachable, so Cloud Run connects with no extra
|
|
networking. Apply migrations once (from your machine, against the same `DATABASE_URL`):
|
|
|
|
```bash
|
|
cd apps/backend && bun run db:migrate
|
|
```
|
|
|
|
## Cloud Run scanner permissions (when `SCAN_RUNNER=cloudrun`)
|
|
|
|
The backend Cloud Run service launches the scanner Cloud Run job. It authenticates
|
|
as the service account whose key is inlined in `GOOGLE_SERVICE_ACCOUNT_JSON`.
|
|
|
|
1. In `apps/backend/.env.production`, remove any `GOOGLE_APPLICATION_CREDENTIALS`
|
|
line and add the full service-account key JSON on one line:
|
|
```env
|
|
GOOGLE_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"..."}
|
|
```
|
|
2. Grant that service account permission to run jobs:
|
|
```bash
|
|
SA_EMAIL="cerebrus-job-exec@local-volt-486423-t2.iam.gserviceaccount.com"
|
|
PROJECT_ID=local-volt-486423-t2
|
|
|
|
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
|
--member="serviceAccount:${SA_EMAIL}" --role="roles/run.admin" --condition=None
|
|
|
|
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
|
|
--member="serviceAccount:${SA_EMAIL}" --role="roles/iam.serviceAccountUser" --condition=None
|
|
```
|
|
|
|
For local dev you can keep using `GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json`
|
|
instead of inlining the JSON.
|
|
|
|
## How env is handled
|
|
|
|
| | Where it's read | How it's configured |
|
|
| --- | --- | --- |
|
|
| Backend | runtime (`bun` auto-loads `/app/.env`) | `COPY apps/backend/.env.production ./.env` in the Dockerfile |
|
|
| Frontend build | **build time** (Vite inlines `VITE_*`) | `vite build` reads `apps/frontend/.env.production` |
|
|
| Frontend runtime | runtime (`process.env.BACKEND_URL`) | `deploy.sh` reads `BACKEND_URL` from `apps/frontend/.env.production` and sets it on the Cloud Run service |
|
|
|
|
`PORT` is **not** set in `.env.production` — Cloud Run injects `8080`, and a real
|
|
env var always wins over the file. The session cookie stays **first-party**
|
|
because the browser only talks to the frontend origin (the Bun server proxies `/auth` +
|
|
`/api` to the backend) — no cross-site/third-party cookies involved.
|
|
|
|
## Notes
|
|
|
|
- `deploy.sh` now sets the frontend's `BACKEND_URL` Cloud Run env var automatically
|
|
from `apps/frontend/.env.production` on every deploy.
|
|
- Build context is the **repo root** (the bun workspace must resolve); the
|
|
Dockerfiles live in each app but are built with `-f apps/<app>/Dockerfile .`.
|
|
- `.gcloudignore` exists so Cloud Build keeps `.env.production` in the upload
|
|
(otherwise gcloud falls back to `.gitignore`, which excludes all `.env*`).
|
|
- Services are deployed `--allow-unauthenticated` (public web app). Remove that
|
|
flag in the cloudbuild configs to require IAM auth.
|