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: readby default
4. CD with two environments.
- Every push to
maindeploys 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 |
|---|---|
| 1 | Repo with the app, Dockerfile, and workflows |
| 2 | Actions history showing: a passing run, a failed deploy that rolled back, and an approved production deploy |
| 3 | Live staging and production URLs (or a documented teardown) |
| 4 | README: image size, cold vs warm CI timings, and the rollback explanation |
| 5 | Screenshot of the budget alert and max-instances setting |
Grading#
| Weight | Criterion |
|---|---|
| 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#
| Symptom | Cause | Fix |
|---|---|---|
Permission denied deploying | Missing IAM role / WIF misconfigured | Grant Cloud Run Admin + Service Account User |
| Container won’t start | Bound 127.0.0.1 or hard-coded port | 0.0.0.0 + $PORT |
| Deploy “succeeds” but the app is broken | No post-deploy verification | Poll /health, fail the job |
| Rollback doesn’t restore service | Traffic not re-routed to the old revision | gcloud run services update-traffic --to-revisions= |
| Every build is slow | No caching | cache-from/to: type=gha |
| Production deployed by accident | No environment gate | Required reviewers on the production environment |
Stretch goals#
- Declare the Cloud Run service and budget in Terraform instead of
gcloudflags. - 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.