Lab β€” Full CI/CD Pipeline to Cloud Run#

One git push β†’ tests, image build, deploy, health check. Plus the parts people skip: a rollback, a cost cap, and an approval gate.

⏱ ~4–5 hours πŸ”— needs: GitHub Actions Advanced Β· Advanced Docker Β· Serverless Functions

Everything in Week 7 assembled into one working pipeline.

What you’re building#

flowchart LR
    P["git push"] --> T["Test matrix + lint"]
    T --> B["Build image<br/>(cached, multi-stage)"]
    B --> SC["Scan for CVEs + secrets"]
    SC --> ST["Deploy to STAGING"]
    ST --> H["Smoke test /health"]
    H -->|"main only"| A{"Manual approval"}
    A --> PR["Deploy to PRODUCTION"]
    PR --> V["Verify + auto-rollback on failure"]

Requirements#

1. The app. Any small FastAPI service with /health and one real endpoint. Reuse your Week 6 scraper API or the Week 7 hardened LLM service.

2. Container. Multi-stage, -slim, non-root user, .dockerignore, $PORT from the environment. Record the final image size in your README β€” under 300 MB is a reasonable target for a Python API.

3. CI on every push and PR.

  • Test matrix across two Python versions, fail-fast: false
  • Lint (ruff) and a secret scan (gitleaks)
  • Dependency caching, with a cold-vs-warm timing comparison in the README
  • permissions: contents: read by default

4. CD with two environments.

  • Every push to main deploys to staging automatically
  • Production requires a manual approval via a GitHub Environment with a required reviewer
  • Authenticate to GCP with Workload Identity Federation, not a long-lived JSON key (docs) β€” and say in your README why a committed service-account key would be worse

5. Verify and roll back. After deploying, poll /health. If it fails, the workflow must automatically route traffic back to the previous Cloud Run revision and fail the run. Demonstrate this: deliberately deploy a broken build and show the rollback in the Actions log.

6. Guard the cost. --max-instances set, plus a billing budget alert (Cost Alerting). Screenshot the budget config.

Deliverables#

#Item
1Repo with the app, Dockerfile, and workflows
2Actions history showing: a passing run, a failed deploy that rolled back, and an approved production deploy
3Live staging and production URLs (or a documented teardown)
4README: image size, cold vs warm CI timings, and the rollback explanation
5Screenshot of the budget alert and max-instances setting

Grading#

WeightCriterion
25%Pipeline works β€” push to deploy, both environments
25%Rollback demonstrated β€” a real failed deploy that recovered automatically
20%Security β€” WIF (no static keys), least-privilege permissions, image + secret scanning
15%Speed β€” layer and dependency caching with measured evidence
15%Cost control β€” max instances, budget alert, and a teardown plan

The rollback is the highest-signal deliverable. Anyone can deploy when everything works; the pipeline earns its keep on the day the build is broken.

Common failure modes#

SymptomCauseFix
Permission denied deployingMissing IAM role / WIF misconfiguredGrant Cloud Run Admin + Service Account User
Container won’t startBound 127.0.0.1 or hard-coded port0.0.0.0 + $PORT
Deploy “succeeds” but the app is brokenNo post-deploy verificationPoll /health, fail the job
Rollback doesn’t restore serviceTraffic not re-routed to the old revisiongcloud run services update-traffic --to-revisions=
Every build is slowNo cachingcache-from/to: type=gha
Production deployed by accidentNo environment gateRequired reviewers on the production environment

Stretch goals#

  • Declare the Cloud Run service and budget in Terraform instead of gcloud flags.
  • Add a canary: send 10% of traffic to the new revision, promote only if error rates hold.
  • Post the image size and CI duration as a comment on every PR.