PreAngel’s philosophy has always been clear: simplicity and truth live in the repo. Every configuration should be declarative, versioned, and automated — never hidden in a console.
Over time, we distilled our deployment pipeline down to its purest form:
Two stages. One repo. Zero secrets.
This is the PreAngel Way — powered by GitHub Actions, Google Cloud Run, Artifact Registry, and Workload Identity Federation (WIF).
1) The Final Model — Two Stages, One Truth
| Stage | Description | Source | Target | 
|---|---|---|---|
| Development | Every commit to main builds, pushes an image to Artifact Registry, and deploys to myapp-dev with 100% traffic. | 
      Push to main | Cloud Run → myapp-dev | 
    
| Production | Every Git tag (e.g., v1.4.2) reuses the exact same image from Artifact Registry and deploys to myapp with --no-traffic. | 
      Push tag v* | 
      Cloud Run → myapp (no traffic) | 
    
This achieves full CI/CD simplicity:
- Dev = automatic → instant iteration.
 - Prod = deliberate → controlled stability.
 - Both powered by the same artifact and pipeline logic.
 
Rule: Build once. Reuse the same image everywhere.
2) Core Architecture
| Component | Role | 
|---|---|
| GitHub Actions | The single CI/CD engine. Two workflows, both fully versioned in the repo. | 
| Artifact Registry | Stores all container images by commit SHA. Example: us-central1-docker.pkg.dev/preangel/myrepo/myapp:<SHA>. | 
    
| Cloud Run (Dev) | Auto-deploys on each commit; routes 100% traffic to the newest revision. | 
| Cloud Run (Prod) | Receives tagged images with --no-traffic, then manually promoted when stable. | 
    
| Workload Identity Federation (WIF) | Connects GitHub OIDC to GCP IAM. No JSON key, no secrets, fully keyless. | 
3) Workload Identity Federation — Keyless, Direct Access
Workload Identity Federation (WIF) lets GitHub authenticate to GCP without using a service account key.
How it works:
1) GitHub Actions requests an OIDC token for each workflow run. 2) Google’s Security Token Service exchanges it for a short-lived access token. 3) GCP IAM grants permissions directly to that OIDC identity.
Why it’s best practice for PreAngel:
- Keyless & secure: Eliminates JSON keys entirely.
 - Short-lived: Credentials expire automatically.
 - Direct IAM access: Grants roles directly to the identity — fewer layers, faster audits.
 - Attribute-based: Scopes can match exact repos, branches, or tags.
 - Zero hidden state: All roles and mappings live in declarative scripts.
 
4) IAM Setup for Direct Access
Create a Workload Identity Pool and Provider, then grant access directly to your GitHub repository identity:
PROJECT_ID=preangel
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
POOL_ID=github-pool
OWNER=PreAngel
REPO=myrepo
# Create WIF pool & provider
gcloud iam workload-identity-pools create $POOL_ID \
  --project=$PROJECT_ID --location=global --display-name="GitHub Pool"
gcloud iam workload-identity-pools providers create-oidc github-provider \
  --project=$PROJECT_ID --location=global --workload-identity-pool=$POOL_ID \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.ref=assertion.ref"
# Bind IAM roles directly to the repo’s OIDC identity
REPO_MEMBER="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/attribute.repository/${OWNER}/${REPO}"
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="$REPO_MEMBER" --role="roles/run.admin"
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="$REPO_MEMBER" --role="roles/artifactregistry.writer"
Now this GitHub repository can deploy to Cloud Run and push to Artifact Registry without any service account.
5) Repository Variables
| Variable | Example | Purpose | 
|---|---|---|
| PROJECT_ID | preangel | 
      GCP project ID | 
| REGION | us-central1 | 
      Deployment region | 
| AR_REPO | myrepo | 
      Artifact Registry repo name | 
| GCP_WIP | projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider | 
      WIF Provider resource path | 
These are configured as GitHub Variables — not secrets.
6) The Two Workflows — Fully Declarative
① .github/workflows/dev.yml
Builds, pushes, and deploys automatically on every commit to main.
name: dev-deploy
on:
  push:
    branches: [ main ]
jobs:
  deploy-dev:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    env:
      REGION: $
      PROJECT_ID: $
      AR_REPO: $
      IMAGE: $-docker.pkg.dev/$/$/myapp:$
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: $
          token_format: 'access_token'
          create_credentials_file: true
      - uses: google-github-actions/setup-gcloud@v2
      - name: Build & Push Image
        run: |
          gcloud auth configure-docker $REGION-docker.pkg.dev --quiet
          docker build -t "$IMAGE" .
          docker push "$IMAGE"
      - name: Deploy to Dev
        run: |
          gcloud run deploy myapp-dev \
            --region $REGION \
            --image "$IMAGE" \
            --allow-unauthenticated \
            --revision-suffix=$ \
            --condition=None
          gcloud run services update-traffic myapp-dev \
            --region $REGION \
            --to-latest \
            --condition=None
② .github/workflows/release.yml
Uses the same image from Artifact Registry to deploy tagged releases to Prod.
name: prod-deploy
on:
  push:
    tags: [ 'v*' ]
jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    env:
      REGION: $
      PROJECT_ID: $
      AR_REPO: $
      IMAGE_TAG: $-docker.pkg.dev/$/$/myapp:$
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: $
          token_format: 'access_token'
          create_credentials_file: true
      - uses: google-github-actions/setup-gcloud@v2
      - name: Wait for Image
        run: |
          for i in {1..30}; do
            if gcloud artifacts docker images describe "$IMAGE_TAG" >/dev/null 2>&1; then break; fi
            sleep 10;
          done
      - name: Resolve Digest
        id: digest
        run: |
          DIGEST=$(gcloud artifacts docker images describe "$IMAGE_TAG" --format='value(image_summary.digest)')
          echo "digest=$DIGEST" >> $GITHUB_OUTPUT
      - name: Deploy to Prod (No Traffic)
        run: |
          REF="$REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/myapp@$"
          gcloud run deploy myapp \
            --region $REGION \
            --image "$REF" \
            --no-traffic \
            --revision-suffix=$-$ \
            --allow-unauthenticated \
            --condition=None
Manual rollout when verified:
gcloud run services update-traffic myapp --region us-central1 --to-latest --condition=None
7) Why This Is the Best Practice for Us
| Principle | Reason | 
|---|---|
| Single Source of Truth | All workflows, configs, and permissions live in Git — never in the UI. | 
| Direct Access via WIF | No service account, no keys — just short-lived credentials tied to our repo identity. | 
| Artifact Reuse | The same image tested in Dev is promoted to Prod. Guaranteed consistency. | 
| Two Stages Only | Fewer moving parts → faster deploys → fewer errors. | 
| Manual Traffic Promotion | Adds human verification at the most critical moment: production. | 
| Declarative Everything | Each workflow file fully describes how and why it runs. | 
✅ The result: secure, minimal, transparent, and automation-ready — the best practice for PreAngel’s AI-native product stack.
8) Conclusion — The PreAngel Way
PreAngel’s two-stage pipeline represents the ideal harmony between automation and human judgment:
- Dev runs continuously and fearlessly.
 - Prod deploys intentionally and safely.
 - IAM is keyless and direct.
 - Every configuration is visible, versioned, and reproducible.
 
Build once. Test once. Deploy deliberately.
Appendix A — Background and Rationale (from earlier write-up, updated to Dev/Prod)
We started like most teams — with five or six deployment environments: dev, qa, preview, staging, preprod, prod. Each served a purpose, but over time they became confusing, inconsistent, and expensive:
- Code drifted between stages.
 - Builds weren’t reproducible.
 - Deployments slowed down because approvals piled up.
 
We asked a radical question: What’s the minimum number of stages that guarantees speed and safety?
A1. Why We Simplified — From Many Stages to Two
After analyzing dozens of pipelines (Firebase, Vercel, AWS, GitHub Actions, Google Cloud Deploy), we found that two stages — Development and Production — are enough when you use Cloud Run’s built‑in rollout and rollback capabilities.
This led to the new model (now finalized in v4):
main→ auto‑deploys to Dev (continuous integration & validation).- A Git tag → promotes to Prod (controlled rollout with manual traffic).
 
A2. The Questions We Asked Ourselves
| Question | Our Finding | 
|---|---|
| Why not more stages? | Each extra environment creates cost, delay, and drift. Two are enough when rollout control is strong. | 
| Why not merge dev + prod? | Dev must remain a fast, iterative environment; Prod remains stable and audited. | 
| Why use Git tags for promotion? | Tags are immutable, human‑auditable, and integrate naturally with GitHub releases. | 
| Why not just copy dev to production? | Cloud Run doesn’t clone environments; it shifts traffic between revisions — a safer, more elegant model. | 
| How does rollback work? | Cloud Run reroutes traffic to the previous revision instantly. No redeploys, no downtime. | 
| Can we preview before rollout? | Yes. Deploy a revision with 0% traffic and access via its direct URL. | 
| What about frontend & backend sync? | If the frontend is on Firebase App Hosting, coordinate via tags; otherwise keep APIs backward‑compatible. | 
| Do we lose flexibility with two stages? | No — Cloud Run traffic splitting brings flexibility back. | 
A3. The Insight — Cloud Run Changed the Rules
In traditional CI/CD systems:
- Promotion = copy lower env → Prod.
 - Rollback = redeploy old build.
 
In Cloud Run:
- Each deploy creates an immutable revision.
 - Promotion = traffic migration.
 - Rollback = re‑routing.
 
| Legacy Thinking | Cloud Run Reality | 
|---|---|
| Copy lower env to Prod | Shift traffic to new Prod revision | 
| Rollback = Re‑deploy | Rollback = Redirect traffic instantly | 
| Multi‑env drift | Revisions are immutable snapshots | 
A4. The Model — Two‑Stage, Google‑Native Deployment
- Stage 1 (Dev): 
maindeploys automatically via GitHub Actions → Artifact Registry → Cloud Run (Dev). - Stage 2 (Prod): Reuse the same container image digest; deploy a new Prod revision with 0% traffic, then promote.
 
A5. The Reasoning — Analytics That Justify the Choice
| Challenge | Our Approach | Benefit | 
|---|---|---|
| Build drift | Build once in Dev, reuse same artifact in Prod | Consistency & reproducibility | 
| Downtime risk | Gradual rollout + instant rollback | Continuous availability | 
| Complex pipeline | 2 clear stages + tag promotion | Simplicity + speed | 
| Low observability | Cloud Run Revisions + Cloud Monitoring | Transparent release history | 
| Approval fatigue | Git tag = single promotion signal | Clarity + accountability | 
A6. Best Practices — Cloud Run (+ optional Firebase App Hosting)
Cloud Run
- Every deploy = immutable revision.
 - Supports traffic splitting for canary or gradual rollout.
 - Rollback = redirect traffic to any previous revision.
 - Supports tagging of revisions for 
candidate,active,stable. - Auto‑scales per request; great for cost efficiency.
 
Optional: Firebase App Hosting
- If your frontend is on Firebase App Hosting, use it for build history and UI‑level rollouts.
 - Coordinate frontend and backend via Git tags; keep APIs backward‑compatible during rollout windows.
 
Together
- GitHub Actions manages builds for backends; Firebase can manage frontend builds.
 - Cloud Run handles backend revisions, rollouts, and scaling.
 - Shared observability via Cloud Monitoring.
 
A7. The New Mental Model — Traffic, Not Copies
main branch → GitHub Actions → Artifact Registry → Cloud Run (Dev)
     │                               │
     │                               └─ validated image digest
     │
   Git tag (v1.4.2) ───────────────→ Cloud Run (Prod)
                                      │
                               New revision @ 0% traffic
                                      │
                              5% → 25% → 100% rollout
                                      │
                              rollback ← metrics breach
- Promotion = Traffic Migration
 - Rollback = Traffic Redirect
 - Artifact = Immutable Revision
 
A8. Policies to Enforce
| Policy | Description | 
|---|---|
| Branch rule | main auto‑deploys to Dev. | 
    
| Promotion rule | Only signed Git tags can trigger Prod rollout. | 
| Artifact immutability | Same container digest in Dev & Prod. | 
| Default rollout | Start ≤ 5%, expand if SLOs green. | 
| Rollback rule | Auto‑rollback if error rate > threshold. | 
| Audit trail | GitHub + Cloud Run track all releases. | 
A9. Why It’s Easy to Operate
Even engineers new to DevOps can reason about this:
- Code on 
main→ auto in Dev. - Validate.
 - Tag when ready.
 - Cloud Run migrates traffic → monitors → finalizes.
 - Rollback instantly if needed.
 
A10. Quick Recap — Build Once, Promote by Traffic
This 2‑Stage model achieves the trifecta:
- Speed: Continuous deployment to Dev keeps momentum high.
 - Safety: Gradual rollout + instant rollback prevent outages.
 - Simplicity: Two stages, one promotion signal.
 
✅ Build once. Test once. Promote by shifting traffic — not by redeploying.
				
  🧭 Decoding the Chaos: The Ultimate Guide to Software Environment & Release Naming