Skip to content

feat: add prod Cloud Run deploy to desktop auto-release#5826

Merged
beastoin merged 1 commit intomainfrom
feat/desktop-prod-deploy
Mar 19, 2026
Merged

feat: add prod Cloud Run deploy to desktop auto-release#5826
beastoin merged 1 commit intomainfrom
feat/desktop-prod-deploy

Conversation

@beastoin
Copy link
Copy Markdown
Collaborator

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

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-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 19, 2026

Greptile Summary

This PR extends the desktop auto-release pipeline by adding a deploy-desktop-backend-prod job that deploys the Rust backend to the production Cloud Run project (based-hardware) after the existing dev deploy succeeds, and updates tag-release to wait for both deploys before cutting the version tag.

Key observations:

  • The new job closely mirrors the dev job and uses a separate prod GitHub environment with its own GCP_CREDENTIALS, GCP_SERVICE_ACCOUNT, and GCP_PROJECT_ID — the environment isolation is correctly applied.
  • Credentials in image layers: The production GCP_SERVICE_ACCOUNT JSON is decoded and written into the Docker build context before the image is built, baking the key into the image layers. This is the most significant security concern introduced (or amplified) by this PR — see inline comment at line 115.
  • --allow-unauthenticated on prod: The production Cloud Run service is deployed with public unauthenticated access. If the desktop client can carry an identity token, removing this flag would add a meaningful IAM-level security layer.
  • Redundant needs in tag-release: deploy-desktop-backend is listed twice (directly and transitively via deploy-desktop-backend-prod); the direct entry is unnecessary.
  • Image rebuild rather than promotion: The prod job builds a new image from source rather than promoting the validated dev image, so the prod binary is technically not identical to the dev-tested one. This is a consequence of baking credentials at build time.

Confidence Score: 3/5

  • Safe to merge functionally, but the production SA credentials baked into the Docker image represent a security risk that should be addressed before this pipeline is used as the primary prod release path.
  • The pipeline logic is sound and the environment isolation (separate GitHub prod env, separate GCP credentials) is correctly structured. The main concerns are operational/security best-practices: credentials in image layers, public Cloud Run endpoint, and an image rebuild rather than a promoted artefact. None of these prevent the workflow from running, but they increase exposure in production compared to the dev environment.
  • .github/workflows/desktop_auto_release.yml — specifically the Google Service Account step (line 115) and the --allow-unauthenticated flag (line 135) in the prod job.

Important Files Changed

Filename Overview
.github/workflows/desktop_auto_release.yml Adds deploy-desktop-backend-prod job that mirrors the dev deploy against the prod GitHub environment, and updates tag-release to gate on both jobs. Key concerns: SA credentials are baked into the Docker image (elevated risk for prod), the --allow-unauthenticated Cloud Run flag is carried forward to production, a redundant needs entry exists in tag-release, and the prod image is rebuilt rather than promoted from the validated dev image.

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
Loading

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]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant needs dependency

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.

Suggested change
needs: [deploy-desktop-backend, deploy-desktop-backend-prod]
needs: [deploy-desktop-backend-prod]

Comment on lines +114 to +115
- name: Google Service Account
run: echo "${{ secrets.GCP_SERVICE_ACCOUNT }}" | base64 -d > ./desktop/Backend-Rust/google-credentials.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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:

  1. Remove the GCP_SERVICE_ACCOUNT / google-credentials.json steps.
  2. Assign a service account to the Cloud Run revision via --service-account in the deploy flags.
  3. Remove GOOGLE_APPLICATION_CREDENTIALS from env_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'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 --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.

Comment on lines +117 to +127
- 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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:

  1. Build the image once (without baked credentials) in the dev job.
  2. At deploy time, pass the SA key as a Cloud Run mounted secret or use Workload Identity (see comment on line 115).
  3. In the prod job, simply docker pull and 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.

@beastoin beastoin merged commit 7624527 into main Mar 19, 2026
2 checks passed
@beastoin beastoin deleted the feat/desktop-prod-deploy branch March 19, 2026 06:57
@beastoin
Copy link
Copy Markdown
Collaborator Author

lgtm

Glucksberg pushed a commit to Glucksberg/omi-local that referenced this pull request Apr 28, 2026
…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_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant