Skip to main content

ØSTEN AI — Deployment Reference

Consolidated from the former root-level deploy docs (DEPLOYMENT_GUIDE, GITHUB_ACTIONS_DEPLOYMENT, DEPLOYMENT_SETUP_CHECKLIST, FRONTEND_DEPLOYMENT_GUIDE).

How deploys work

Push to main → path-filtered GitHub Actions workflows in .github/workflows/:

WorkflowTrigger pathsDeploys
deploy-backend.ymlbackend/**Docker build → Artifact Registry → migration Cloud Run job (osten-ai-backend-migrate, runs node dist/db/migrate.js, waits) → gcloud run deploy osten-ai-backend (europe-west1) with env + secrets
deploy-frontend.ymlfrontend/**Next.js build → Firebase Hosting (app.ostenai.com)
deploy-landing.ymllanding/**Firebase Hosting (ostenai.com)
deploy-docs.ymldocs-site/**Firebase Hosting docs site

GitHub repo secrets used by workflows: GCP_SA_KEY (service-account JSON — TODO: migrate to Workload Identity Federation), GCP_PROJECT (=ostenai), GCP_REGION (=europe-west1), CLOUD_RUN_SERVICE (=osten-ai-backend), FRONTEND_URL, CLOUD_SQL_PUBLIC_IP, Firebase tokens.

Backend runtime contract

  • Container listens on PORT (Cloud Run injects 8080)
  • npm run builddist/; entrypoint node dist/index.js
  • Migration job entrypoint: node dist/db/migrate.js (node-pg-migrate) — must keep working
  • All secrets/env injected at deploy time from Secret Manager — the --set-secrets / --set-env-vars flags in deploy-backend.yml are the authoritative mapping (note: TIKTOK_ADS_CLIENT_ID maps from secret TIKTOK_APP_ID, TIKTOK_ADS_CLIENT_SECRET from TIKTOK_SECRET)
  • Health check: GET /api/health

OAuth redirect URIs (registered in provider consoles)

Backend-served: see docs/SETUP.md. One historical quirk: GA4 has had two redirect URIs registered — the backend one (https://api.ostenai.com/api/google-analytics/auth/callback, current) and a frontend page (https://app.ostenai.com/dashboard/google-analytics/callback, legacy — page still exists in the frontend). Don't remove either from the Google console until the legacy page is deleted.

Database

v2 runs on Neon (serverless Postgres). The database-url secret holds the full connection string; there is no Cloud SQL socket. deploy-backend.yml no longer sets INSTANCE_CONNECTION_NAME / CLOUD_SQL_PUBLIC_IP or --add-cloudsql-instances.

v2 cutover runbook (one-time)

The v2-rebuild branch replaces the backend and moves the DB to Neon. Do these steps before merging v2-rebuildmain (merge is what triggers the deploy):

  1. Provision Neon. Create a project (region aws-eu-central-1 is closest to Cloud Run europe-west). Copy the pooled connection string (postgresql://…@ep-xxx-pooler.eu-central-1.aws.neon.tech/neondb?sslmode=require).

  2. Point the DB secret at Neon:

    printf '%s' 'postgresql://USER:PASS@ep-xxx-pooler.eu-central-1.aws.neon.tech/neondb?sslmode=require' \
    | gcloud secrets versions add database-url --data-file=-
  3. Create the new secrets v2 requires (deploy fails without them):

    # Cloud Scheduler shared secret (also set it on the Scheduler job header, step 7)
    openssl rand -hex 32 | gcloud secrets create scheduler-secret --data-file=-
    # Emails auto-promoted to SUPER_ADMIN on first signup (fresh DB has no super admin yet)
    printf '%s' 'dbjorck18@gmail.com' | gcloud secrets create super-admin-emails --data-file=-

    Optional: META_PAGE_ID (Meta ad creation) — add as a secret + map it in the workflow if you use Meta ad creation.

  4. Register the TikTok redirect URI https://api.ostenai.com/api/tiktok-ads/auth/callback in the TikTok developer portal (the workflow now injects TIKTOK_ADS_REDIRECT_URI). All other redirect URIs are unchanged.

  5. Merge v2-rebuildmain. The workflow runs tests → builds → runs the migration job (creates the fresh schema on Neon via 0001_initial_schema.sql) → deploys.

  6. Smoke test (see below). Because the DB is fresh, existing test users must re-register with an access code and re-connect their ad accounts via OAuth.

  7. Point Cloud Scheduler at POST https://api.ostenai.com/api/scheduler/sync-daily with header X-Scheduler-Secret: <scheduler-secret value>.

  8. Decommission once healthy: stop/delete the Cloud SQL instance ostenai:europe-west1:osten-db-instance, delete the admin-dashboard Cloud Run service, and (later) migrate CI auth from GCP_SA_KEY to Workload Identity Federation.

Rollback: revert the merge and restore the database-url secret to the Cloud SQL value — the previous Cloud Run revision still points at Cloud SQL until deleted.

Manual deploy (emergency)

cd backend
docker build -t europe-west1-docker.pkg.dev/ostenai/osten-ai-backend/osten-ai-backend .
docker push europe-west1-docker.pkg.dev/ostenai/osten-ai-backend/osten-ai-backend
gcloud run deploy osten-ai-backend --image ... --region europe-west1 # copy flags from deploy-backend.yml

Verify after deploy

curl https://api.ostenai.com/api/health # {"status":"UP","database":"Connected"}
gcloud run services logs read osten-ai-backend --region europe-west1 --limit 50