Cloud Run · Publish Pipeline — Plumbing Complete, GitHub PAT Pending
DARE.CO.UK · INFRASTRUCTURE · 12 MAY 2026
Runbook + operational chronology of today’s containerisation of the dev-reports publish pipeline. Five phases shipped; first end-to-end execution gated on a 5-minute GitHub PAT mint. Generated immediately after the canary execution validated the rest of the chain.
TL;DR
- The dev-reports publish pipeline is now containerised end-to-end. Cloud Run Job
devreports-publishexists in GCP projectdare-devreports, with imageus-central1-docker.pkg.dev/dare-devreports/devreports/publish:latest(490MB) built fromxlab-co/toolkit/cloud-run/Dockerfile. - The local launchd cron stays untouched. All five phases are additive — defaults in
dare_dev_reports_publish.pyreproduce the historical~/Downloadslayout exactly, so today’s macOS publish stack keeps working. Cloud Run is the parallel-run candidate, not a replacement (yet). - Canary executed at 18:35Z and failed at the expected guard —
GIT_TOKEN env var is required (Secret Manager binding)— proving image boots, secret binding resolves, entrypoint runs, and the failure mode is exactly the one telegraphed inentrypoint.sh. Everything from container provisioning through CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF-token secret resolution is working. - Pending: 5-minute GitHub PAT mint for
xlab-co/devreports-contentread access, stored in Secret Manager asdevreports-git-pat. Once that’s in,gcloud run jobs execute devreports-publishships a real deploy. See action plan below.
What was plumbed in
Five phases, each independently shippable. Chronology of the session:
Phase 1 — Toolkit Linux portability
Two surgical ports in xlab-co/toolkit (commit f08566b):
thumbnailer.py— new_resize_to_jpeg()helper. Prefers Pillow when importable (Linux containers always; macOS if installed); falls back tosipson macOS without Pillow. Linux without Pillow is an explicit error — container images install Pillow at build time. Closes the parked toolkit-README item; same code path on macOS and Linux once Pillow lands.dare_dev_reports_publish.py— three env-var-overridable paths:
python
REPORTS_DIR = os.environ.get("REPORTS_DIR", ~/Downloads)
STAGE_DIR = os.environ.get("STAGE_DIR", REPORTS_DIR/dev_reports_pages)
THUMB_CACHE_DIR = os.environ.get("THUMB_CACHE_DIR", REPORTS_DIR/devreports_thumbs)
Defaults reproduce the historical layout exactly. Smoke-tested locally — 49 reports staged, 49 thumb cache hits, no breakage.
Phase 2 — Content repo
Created xlab-co/devreports-content (private). Migrated 56 markdown reports + .gitignore (excludes generated HTML / JPG / staging output) + README documenting filename conventions. This becomes the source-of-truth corpus the Cloud Run Job clones at runtime, decoupling content from the Mac filesystem.
Initial commit captures today’s catalog state. The local generator scripts (dare_audit.py, dare_session_report.py, dare_404_audit.py, narrator, etc.) still write to ~/Downloads/ today; a later session adds git add + commit + push steps to those scripts so generated reports flow through this repo automatically.
Phase 3 — GCP project + Secret Manager + Artifact Registry
Mirrors today’s audrey-experiments setup pattern:
| Resource | Identifier |
|---|---|
| GCP project | dare-devreports (number 574960446082) — under the dare.co.uk org |
| Billing account | 003C9F-AC7F67-74ACF1 (My Billing Account) |
| APIs enabled | Cloud Run, Cloud Build, Artifact Registry, Secret Manager, Cloud Scheduler, IAM Credentials |
| Artifact Registry | us-central1-docker.pkg.dev/dare-devreports/devreports (Docker format) |
| Runtime service account | devreports-publish-sa@dare-devreports.iam.gserviceaccount.com |
| Secret | cf-pages-deploy (CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF Pages deploy token, version 1; same source as the launchd cron’s op-injection ref) |
| IAM bindings | devreports-publish-sa + Cloud Run service agent both have secretmanager.secretAccessor on cf-pages-deploy |
Two gotchas surfaced and were resolved during setup:
- Fresh-project IAM propagation delay — Artifact Registry create initially returned
IAM_PERMISSION_DENIEDeven thoughdan@dare.co.ukhasroles/owner. A 30s wait + retry succeeded. Standard GCP eventually-consistent behaviour; worth knowing. - Empty secret payload from a piped
op readthat silently failed auth — firstop read | gcloud secrets create cf-pages-deploy --data-file=-created the secret but populated zero versions, because op had auth-timed-out and returned empty. DownstreamSecret was not founderrors at job creation time. Fix: explicit length check on the op-read output before piping togcloud secrets versions add, plus a separate version-add step rather than create-with-version. Worth memorialising — silent empty pipes are exactly the kind of issue that’s easy to miss.
Phase 4 — Dockerfile + entrypoint + cloudbuild config
Three files in xlab-co/toolkit/cloud-run/ (commit 08e6141):