-
-
Notifications
You must be signed in to change notification settings - Fork 10
feat(backend): add Cloud Run deployment #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
cc6271e
f7384c0
f017005
233bdf5
661a662
2a1f01d
81ba1a5
5573495
cdac78f
f07535d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # API Settings | ||
| API_HOST=0.0.0.0 | ||
| API_PORT=8000 | ||
| DEBUG=true | ||
|
|
||
| # Database (SQLite) | ||
| DATABASE_URL=sqlite+aiosqlite:///./atomify.db | ||
|
|
||
| # Firebase | ||
| FIREBASE_PROJECT_ID=atomify-ee7b5 | ||
|
|
||
| # Google Cloud credentials (for GCS and Firebase Admin) | ||
| # Option 1: Use gcloud auth application-default login (no file needed) | ||
| # Option 2: Point to a service account key file | ||
| # GOOGLE_APPLICATION_CREDENTIALS=./service-account.json | ||
|
|
||
| # Google Cloud Storage | ||
| GCS_BUCKET_NAME=atomify-user-files | ||
|
|
||
| # CORS (comma-separated origins) | ||
| CORS_ORIGINS=http://localhost:3000,http://localhost:5173,https://andeplane.github.io | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,11 @@ venv/ | |
| *.sqlite | ||
|
|
||
| # Environment | ||
| .env | ||
| # .env.local # Use for local overrides if needed | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Committing
andeplane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Service account keys (NEVER commit these) | ||
| *-key.json | ||
| service-account.json | ||
|
|
||
| # IDE | ||
| .idea/ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,176 @@ | ||||||||||||||||||
| # Backend Deployment Guide | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Overview | ||||||||||||||||||
|
|
||||||||||||||||||
| The backend is deployed to Google Cloud Run automatically via GitHub Actions when changes are pushed to `main`. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Hardcoded Values (in workflow) | ||||||||||||||||||
|
|
||||||||||||||||||
| - **GCP Project ID**: `atomify-ee7b5` | ||||||||||||||||||
| - **Firebase Project ID**: `atomify-ee7b5` (same as GCP) | ||||||||||||||||||
| - **Region**: `europe-west1` | ||||||||||||||||||
| - **Service Name**: `atomify-api` | ||||||||||||||||||
| - **Database URL**: `sqlite+aiosqlite:////data/atomify.db` (SQLite file path) | ||||||||||||||||||
| - **GCS Bucket Name**: `atomify-user-files` | ||||||||||||||||||
| - **CORS Origins**: `http://localhost:3000,https://andeplane.github.io` | ||||||||||||||||||
andeplane marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CORS origins listed here (and also in the manual deployment command on line 141) are inconsistent with the
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| ## Required GitHub Secrets | ||||||||||||||||||
|
|
||||||||||||||||||
| Set these in your GitHub repository: **Settings → Secrets and variables → Actions** | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 1. `GCP_SA_KEY` | ||||||||||||||||||
| - **Description**: Service account JSON key for CI/CD deployment | ||||||||||||||||||
| - **Service account**: `[email protected]` | ||||||||||||||||||
| - **Roles**: | ||||||||||||||||||
| - `Cloud Run Admin` — deploy services | ||||||||||||||||||
| - `Service Account User` — impersonate runtime SA | ||||||||||||||||||
| - `Storage Object Admin` — push Docker images to GCR | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 2. `GCP_SERVICE_ACCOUNT_EMAIL` | ||||||||||||||||||
| - **Description**: Email of the service account that Cloud Run uses at runtime | ||||||||||||||||||
| - **Value**: `[email protected]` | ||||||||||||||||||
| - **Roles**: | ||||||||||||||||||
| - `Storage Object Admin` — access GCS buckets | ||||||||||||||||||
| - **Note**: No Firebase role needed — token verification uses public keys | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Cloud Run Setup | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 1. Enable APIs | ||||||||||||||||||
|
|
||||||||||||||||||
| ```bash | ||||||||||||||||||
| gcloud services enable \ | ||||||||||||||||||
| run.googleapis.com \ | ||||||||||||||||||
| cloudbuild.googleapis.com \ | ||||||||||||||||||
| containerregistry.googleapis.com \ | ||||||||||||||||||
| storage-api.googleapis.com | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 2. Create Service Account for Cloud Run | ||||||||||||||||||
|
|
||||||||||||||||||
| ```bash | ||||||||||||||||||
| gcloud iam service-accounts create atomify-api \ | ||||||||||||||||||
| --display-name="Atomify API Service Account" | ||||||||||||||||||
|
|
||||||||||||||||||
| gcloud projects add-iam-policy-binding atomify-ee7b5 \ | ||||||||||||||||||
| --member="serviceAccount:[email protected]" \ | ||||||||||||||||||
| --role="roles/storage.objectAdmin" | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 3. Create GCS Bucket | ||||||||||||||||||
|
|
||||||||||||||||||
| **Note**: Billing must be enabled for the project first. | ||||||||||||||||||
|
|
||||||||||||||||||
| ```bash | ||||||||||||||||||
| gcloud storage buckets create gs://atomify-user-files \ | ||||||||||||||||||
| --project=atomify-ee7b5 \ | ||||||||||||||||||
| --location=europe-west1 | ||||||||||||||||||
|
|
||||||||||||||||||
| gcloud storage buckets add-iam-policy-binding gs://atomify-user-files \ | ||||||||||||||||||
| --member="serviceAccount:[email protected]" \ | ||||||||||||||||||
| --role="roles/storage.objectAdmin" | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ### 4. Create Database Bucket (for SQLite persistence) | ||||||||||||||||||
|
|
||||||||||||||||||
| SQLite is persisted via GCS FUSE mount: | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using SQLite with GCS FUSE on Cloud Run is a risky approach, especially if the service might scale to more than one instance. SQLite is not designed for concurrent write access from multiple processes, which is what would happen with multiple Cloud Run instances. This can lead to database corruption. It is crucial to either configure Cloud Run to have a maximum of 1 instance or to prominently document this limitation and its risks. For a scalable service, consider using a managed database service like Cloud SQL. |
||||||||||||||||||
|
|
||||||||||||||||||
| ```bash | ||||||||||||||||||
| # Create bucket for SQLite database | ||||||||||||||||||
| gcloud storage buckets create gs://atomify-db \ | ||||||||||||||||||
| --project=atomify-ee7b5 \ | ||||||||||||||||||
| --location=europe-west1 | ||||||||||||||||||
|
|
||||||||||||||||||
| # Grant runtime service account access | ||||||||||||||||||
| gcloud storage buckets add-iam-policy-binding gs://atomify-db \ | ||||||||||||||||||
| --member="serviceAccount:[email protected]" \ | ||||||||||||||||||
| --role="roles/storage.objectAdmin" | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| The deploy command mounts this bucket at `/data` using Cloud Storage FUSE. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Database Migrations | ||||||||||||||||||
|
|
||||||||||||||||||
| Migrations are **NOT** run automatically in the container startup to avoid race conditions. | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Option 1: Manual Migration (Recommended) | ||||||||||||||||||
|
|
||||||||||||||||||
| Before deploying, run migrations manually: | ||||||||||||||||||
|
|
||||||||||||||||||
| ```bash | ||||||||||||||||||
| # Build and run migration container | ||||||||||||||||||
| docker build -t atomify-api:migrate ./backend | ||||||||||||||||||
| docker run --rm \ | ||||||||||||||||||
| -e DATABASE_URL="$DATABASE_URL" \ | ||||||||||||||||||
| atomify-api:migrate \ | ||||||||||||||||||
| alembic upgrade head | ||||||||||||||||||
|
Comment on lines
+102
to
+105
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This command relies on the
Suggested change
|
||||||||||||||||||
| ``` | ||||||||||||||||||
andeplane marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
|
||||||||||||||||||
| ### Option 2: Cloud Run Job | ||||||||||||||||||
|
|
||||||||||||||||||
| Create a Cloud Run Job for migrations: | ||||||||||||||||||
|
|
||||||||||||||||||
| ```bash | ||||||||||||||||||
| gcloud run jobs create atomify-api-migrate \ | ||||||||||||||||||
| --image=gcr.io/atomify-ee7b5/atomify-api:latest \ | ||||||||||||||||||
| --region=europe-west1 \ | ||||||||||||||||||
| --set-env-vars="DATABASE_URL=$DATABASE_URL" \ | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||
| --command="alembic" \ | ||||||||||||||||||
| --args="upgrade,head" | ||||||||||||||||||
|
|
||||||||||||||||||
| # Run before each deployment | ||||||||||||||||||
| gcloud run jobs execute atomify-api-migrate --region=europe-west1 --wait | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Manual Deployment | ||||||||||||||||||
|
|
||||||||||||||||||
| If you need to deploy manually: | ||||||||||||||||||
|
|
||||||||||||||||||
| ```bash | ||||||||||||||||||
| # Build and push | ||||||||||||||||||
| docker build -t gcr.io/atomify-ee7b5/atomify-api:latest ./backend | ||||||||||||||||||
| docker push gcr.io/atomify-ee7b5/atomify-api:latest | ||||||||||||||||||
|
|
||||||||||||||||||
| # Deploy | ||||||||||||||||||
| gcloud run deploy atomify-api \ | ||||||||||||||||||
| --image=gcr.io/atomify-ee7b5/atomify-api:latest \ | ||||||||||||||||||
| --region=europe-west1 \ | ||||||||||||||||||
| --platform=managed \ | ||||||||||||||||||
| --execution-environment=gen2 \ | ||||||||||||||||||
| --allow-unauthenticated \ | ||||||||||||||||||
| --service-account=atomify-api@atomify-ee7b5.iam.gserviceaccount.com \ | ||||||||||||||||||
| --set-env-vars="DATABASE_URL=sqlite+aiosqlite:////data/atomify.db,FIREBASE_PROJECT_ID=atomify-ee7b5,GCS_BUCKET_NAME=atomify-user-files,CORS_ORIGINS=http://localhost:3000,https://andeplane.github.io" \ | ||||||||||||||||||
andeplane marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||
| --add-volume=name=db-volume,type=cloud-storage,bucket=atomify-db \ | ||||||||||||||||||
| --add-volume-mount=volume=db-volume,mount-path=/data \ | ||||||||||||||||||
| --memory=512Mi \ | ||||||||||||||||||
| --cpu=1 \ | ||||||||||||||||||
| --port=8000 | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Monitoring | ||||||||||||||||||
|
|
||||||||||||||||||
| - **Logs**: `gcloud run services logs read atomify-api --region=europe-west1` | ||||||||||||||||||
| - **Metrics**: Google Cloud Console → Cloud Run → atomify-api | ||||||||||||||||||
| - **Health Check**: `https://atomify-api-xxx.run.app/health` | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Troubleshooting | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Service won't start | ||||||||||||||||||
| - Check logs: `gcloud run services logs read atomify-api --region=europe-west1` | ||||||||||||||||||
| - Verify environment variables are set correctly | ||||||||||||||||||
| - Check service account permissions | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Database connection fails | ||||||||||||||||||
| - Verify `DATABASE_URL` is correct | ||||||||||||||||||
| - For Cloud SQL: Ensure Cloud SQL Admin API is enabled | ||||||||||||||||||
| - For SQLite volume: Ensure volume is mounted at `/data` | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Firebase Auth fails | ||||||||||||||||||
| - Verify `FIREBASE_PROJECT_ID` matches your Firebase project | ||||||||||||||||||
| - Token verification uses public keys (no special permissions needed) | ||||||||||||||||||
| - Ensure Firebase Admin SDK is initialized correctly | ||||||||||||||||||
|
|
||||||||||||||||||
| ### GCS access fails | ||||||||||||||||||
| - Verify `GCS_BUCKET_NAME` exists | ||||||||||||||||||
| - Check service account has `Storage Object Admin` role | ||||||||||||||||||
| - Verify bucket IAM allows the service account | ||||||||||||||||||
|
|
||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.