feat: add prod Cloud Run deploy to desktop auto-release#5826
Conversation
Adds deploy-desktop-backend-prod job using the prod GitHub environment. Builds and pushes to prod GCR, deploys to prod Cloud Run with same env vars and secret mappings as dev. Tag-release now waits for both dev and prod deploys to succeed. Requires prod GCP Secret Manager to have: DESKTOP_DEEPGRAM_API_KEY, DESKTOP_ANTHROPIC_API_KEY, DESKTOP_GOOGLE_CALENDAR_API_KEY Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR extends the desktop auto-release pipeline by adding a Key observations:
Confidence Score: 3/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[push to main\ndesktop/**] --> B[deploy-desktop-backend\nenvironment: development]
A --> W[workflow_dispatch]
W --> B
B -->|needs| C[deploy-desktop-backend-prod\nenvironment: prod ← NEW]
B -->|needs| D[tag-release\nredundant direct dep]
C -->|needs| D
D --> E[Commit CHANGELOG.json\n& push git tag]
E --> F[Codemagic builds\nSwift app]
subgraph dev_deploy [Dev Deploy]
B1[Checkout] --> B2[GCP Auth - dev creds]
B2 --> B3[Build & push image\ngcr.io/dev-project/desktop-backend:sha]
B3 --> B4[Deploy Cloud Run - dev]
end
subgraph prod_deploy [Prod Deploy ← NEW]
C1[Checkout] --> C2[GCP Auth - prod creds]
C2 --> C3[Bake SA key into image\n⚠️ credentials in layers]
C3 --> C4[Build & push image\ngcr.io/based-hardware/desktop-backend:sha]
C4 --> C5[Deploy Cloud Run - prod\n⚠️ --allow-unauthenticated]
end
B --> dev_deploy
C --> prod_deploy
Last reviewed commit: "feat: add production..." |
| GOOGLE_CALENDAR_API_KEY=DESKTOP_GOOGLE_CALENDAR_API_KEY:latest | ||
|
|
||
| tag-release: | ||
| needs: [deploy-desktop-backend, deploy-desktop-backend-prod] |
There was a problem hiding this comment.
tag-release explicitly lists deploy-desktop-backend in its needs, but deploy-desktop-backend-prod already declares needs: deploy-desktop-backend. Because GitHub Actions resolves the full DAG transitively, the explicit deploy-desktop-backend entry here is redundant and can be simplified without changing behaviour.
| needs: [deploy-desktop-backend, deploy-desktop-backend-prod] | |
| needs: [deploy-desktop-backend-prod] |
| - name: Google Service Account | ||
| run: echo "${{ secrets.GCP_SERVICE_ACCOUNT }}" | base64 -d > ./desktop/Backend-Rust/google-credentials.json |
There was a problem hiding this comment.
Production SA credentials baked into Docker image
The service account JSON is decoded and written into the build context, which means it is embedded in the resulting Docker image layers. Any principal with storage.objects.get on the prod GCR bucket (or with docker pull access) can extract the key with docker save | tar x. The same pattern exists in the dev job, but the risk is considerably higher for production credentials.
A safer alternative is to bind a dedicated Cloud Run service account at deploy time and rely on the metadata server for ADC, rather than baking a key file into the image:
- Remove the
GCP_SERVICE_ACCOUNT/google-credentials.jsonsteps. - Assign a service account to the Cloud Run revision via
--service-accountin the deploy flags. - Remove
GOOGLE_APPLICATION_CREDENTIALSfromenv_vars; the runtime will pick up ADC automatically.
If the key file must stay in the image for a short-term reason, consider at minimum verifying in CI that the file is not present in the final image layers (e.g. via a multi-stage Dockerfile that copies only the binary).
| service: ${{ env.SERVICE }} | ||
| region: ${{ env.REGION }} | ||
| image: gcr.io/${{ vars.GCP_PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }} | ||
| flags: '--allow-unauthenticated' |
There was a problem hiding this comment.
--allow-unauthenticated on production Cloud Run
The production Cloud Run service is deployed with --allow-unauthenticated, meaning the endpoint is publicly accessible to anyone on the internet without any IAM check. This matches the dev deployment, but the blast radius in production is larger.
If the desktop client is the only intended consumer and it can supply an Authorization header, consider removing this flag and having the client obtain a short-lived identity token via OAuth or a service account, which allows Cloud Run's built-in IAM layer to act as a first line of defense. If public access is intentional (e.g. the service enforces its own auth), a comment explaining the decision would help future reviewers.
| - name: Build and Push Desktop Backend Image | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: ./desktop/Backend-Rust | ||
| file: ./desktop/Backend-Rust/Dockerfile | ||
| push: true | ||
| tags: | | ||
| gcr.io/${{ vars.GCP_PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }} | ||
| gcr.io/${{ vars.GCP_PROJECT_ID }}/${{ env.SERVICE }}:latest | ||
| cache-from: type=registry,ref=gcr.io/${{ vars.GCP_PROJECT_ID }}/${{ env.SERVICE }}:buildcache | ||
| cache-to: type=registry,ref=gcr.io/${{ vars.GCP_PROJECT_ID }}/${{ env.SERVICE }}:buildcache,mode=max |
There was a problem hiding this comment.
Prod job rebuilds the image instead of promoting the dev image
The prod job authenticates to a different GCP project and pushes a fresh image build to prod GCR. Because the GCP_SERVICE_ACCOUNT key is baked in at build time (different per environment), a full rebuild is required — however it means the artefact actually running in production is not byte-for-byte identical to the artefact that was validated in dev.
A more robust promotion pattern would:
- Build the image once (without baked credentials) in the dev job.
- At deploy time, pass the SA key as a Cloud Run mounted secret or use Workload Identity (see comment on line 115).
- In the prod job, simply
docker pulland re-tag/push the dev-built digest to prod GCR, then deploy — ensuring the exact same binary is promoted.
This is a design suggestion rather than a blocking issue, but worth considering before this pipeline is used as the authoritative prod release gate.
|
lgtm |
…e#5826) ## Summary - Adds `deploy-desktop-backend-prod` job that deploys the Rust backend to production Cloud Run (`based-hardware`) - Mirrors the dev deploy but uses the `prod` GitHub environment (separate GCP credentials) - Tag-release now waits for both dev and prod deploys before creating the version tag ## Prerequisites The `prod` GitHub environment needs: - `GCP_CREDENTIALS` — service account JSON for `based-hardware` - `GCP_SERVICE_ACCOUNT` — base64-encoded SA JSON (baked into Docker image) - `GCP_PROJECT_ID` variable — `based-hardware` Prod GCP Secret Manager needs these secrets (same as dev): - `DESKTOP_DEEPGRAM_API_KEY` - `DESKTOP_ANTHROPIC_API_KEY` - `DESKTOP_GOOGLE_CALENDAR_API_KEY` - `GEMINI_API_KEY`, `ENCRYPTION_SECRET`, `REDIS_DB_*`, `FIREBASE_API_KEY`, `PINECONE_*` ## Pipeline flow after merge ``` push to main (desktop/**) → deploy-desktop-backend (dev) → deploy-desktop-backend-prod (prod) ← NEW → tag-release (waits for both) → Codemagic builds Swift app (triggered by tag) ``` --- _by AI for @beastoin_
Summary
deploy-desktop-backend-prodjob that deploys the Rust backend to production Cloud Run (based-hardware)prodGitHub environment (separate GCP credentials)Prerequisites
The
prodGitHub environment needs:GCP_CREDENTIALS— service account JSON forbased-hardwareGCP_SERVICE_ACCOUNT— base64-encoded SA JSON (baked into Docker image)GCP_PROJECT_IDvariable —based-hardwareProd GCP Secret Manager needs these secrets (same as dev):
DESKTOP_DEEPGRAM_API_KEYDESKTOP_ANTHROPIC_API_KEYDESKTOP_GOOGLE_CALENDAR_API_KEYGEMINI_API_KEY,ENCRYPTION_SECRET,REDIS_DB_*,FIREBASE_API_KEY,PINECONE_*Pipeline flow after merge
by AI for @beastoin