From c13a3f16948f65dfbbec6180e122a8b60232af18 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 27 Jun 2026 15:32:19 +0000 Subject: [PATCH] plotting-book: pull image from private ghcr instead of public DockerHub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Anca's plotting-book app now builds its image in her own GitHub repo to the private package ghcr.io/passionprojectsanca/book-plotter (off public DockerHub viktorbarzin/book-plotter). Wire the cluster to pull it: - stacks/plotting-book: point the deployment baseline image at the ghcr package and add imagePullSecrets {ghcr-credentials} so the pod can pull the private image (the live tag is still CI-owned via ignore_changes). - stacks/kyverno: add the plotting-book namespace to the ghcr-credentials allowlist so the Kyverno generate policy clones the pull secret into it. Verified the shared ghcr_pull_token (Viktor, repo-admin on Anca's repo) can read the private package before wiring this. Docs: correct ci-cd.md (it wrongly listed plotting-book as already on ghcr — it was on DockerHub) and note the special arrangement; amend ADR-0003 to record that this GitHub-first repo builds to its own org's ghcr namespace. Co-Authored-By: Claude Opus 4.8 --- ...003-keep-forgejo-canonical-complete-mirror.md | 2 +- docs/architecture/ci-cd.md | 13 ++++++++++++- .../kyverno/modules/kyverno/ghcr-credentials.tf | 4 ++++ stacks/plotting-book/main.tf | 16 ++++++++++++---- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/docs/adr/0003-keep-forgejo-canonical-complete-mirror.md b/docs/adr/0003-keep-forgejo-canonical-complete-mirror.md index 9e0e2192..67022732 100644 --- a/docs/adr/0003-keep-forgejo-canonical-complete-mirror.md +++ b/docs/adr/0003-keep-forgejo-canonical-complete-mirror.md @@ -13,7 +13,7 @@ The trigger was a proposal to swap Forgejo out for GitHub entirely. The grilling Do **not** swap to GitHub. Reaffirm and *complete* the model already in `CONTEXT.md`: - Every first-party repo has exactly **one** push target — its **Canonical repo** on Forgejo. GitHub is a one-way push-mirror (off-site backup + the source GitHub Actions builds from). **No repo is ever dual-pushed.** -- A small, explicit set of **GitHub-first repos** are the exception (canonical lives on GitHub, outside the mirror policy): third-party clones/forks where GitHub is genuinely upstream (`jsoncrack.com`, `snmp_exporter`, `SparkyFitness`, `agent-rules-books`, `Plotting-Your-Dream-Book`) and the deliberately-public first-party `health`. +- A small, explicit set of **GitHub-first repos** are the exception (canonical lives on GitHub, outside the mirror policy): third-party clones/forks where GitHub is genuinely upstream (`jsoncrack.com`, `snmp_exporter`, `SparkyFitness`, `agent-rules-books`, `Plotting-Your-Dream-Book`) and the deliberately-public first-party `health`. `Plotting-Your-Dream-Book` (owned by Anca, dev in her org) keeps its GHA build in-place and pushes the image to **its own org's ghcr** (`ghcr.io/passionprojectsanca/book-plotter`, private) via the workflow's built-in `GITHUB_TOKEN` — no Forgejo mirror, no `viktorbarzin`-namespace push, no shared PAT in her repo (2026-06-27, migrated off DockerHub). - `infra` is reconciled into the standard model: its GitHub-only `.github/workflows/build-*.yml` are brought onto Forgejo-canonical (inert on Forgejo, active on the mirror), then the mirror is enabled — ending the deliberate divergence while keeping Woodpecker on the Forgejo forge. - Enforcement is **structural**: reconciled clones keep only the Forgejo remote, so there is no GitHub remote to habitually push to; the execution rule is "push to the canonical forge only, never the mirror." diff --git a/docs/architecture/ci-cd.md b/docs/architecture/ci-cd.md index 35e041e6..9c14c606 100644 --- a/docs/architecture/ci-cd.md +++ b/docs/architecture/ci-cd.md @@ -115,9 +115,20 @@ claude-agent-service, claude-memory-mcp, kms-website, Freedify, instagram-poster, payslip-ingest, broker-sync (image name `wealthfolio-sync`), fire-planner, recruiter-responder, x402-gateway — plus **tripit** (the original pilot, 2026-06-09). Earlier public-repo apps already on GHA (Website, -k8s-portal, apple-health-data, audiblez-web, plotting-book, insta2spotify, +k8s-portal, apple-health-data, audiblez-web, insta2spotify, audiobook-search) now also land on ghcr. +**plotting-book** is a special case (a GitHub-first repo owned by Anca, +ADR-0003): the build runs in *her* GitHub repo +(`PassionProjectsAnca/Plotting-Your-Dream-Book`) and pushes to **private +`ghcr.io/passionprojectsanca/book-plotter`** — under her org's ghcr namespace, +not `viktorbarzin`, using the workflow's built-in `GITHUB_TOKEN` (no shared +PAT). The cluster pulls it via the Kyverno-synced `ghcr-credentials` secret (the +`plotting-book` namespace is on the allowlist; the shared `ghcr_pull_token` has +read access). Migrated off public DockerHub (`viktorbarzin/book-plotter`) on +2026-06-27. The Woodpecker deploy hook (repo 43, registered to Anca's repo) is +unchanged. + ### Infra-owned images (issues #29 / #30) Images owned by the infra repo build on GHA workflows **in the infra repo's own diff --git a/stacks/kyverno/modules/kyverno/ghcr-credentials.tf b/stacks/kyverno/modules/kyverno/ghcr-credentials.tf index 781f9d62..0cd85b41 100644 --- a/stacks/kyverno/modules/kyverno/ghcr-credentials.tf +++ b/stacks/kyverno/modules/kyverno/ghcr-credentials.tf @@ -34,6 +34,10 @@ locals { # goldmane-edge-aggregator: PRIVATE ghcr image pulled by the aggregate # Deployment + digest CronJob (ADR-0014, infra#58). "goldmane-edge-aggregator", + # plotting-book: image migrated from public DockerHub to PRIVATE + # ghcr.io/passionprojectsanca/book-plotter (built by GHA in Anca's repo, + # under her own org's ghcr). The deployment references the cloned secret. + "plotting-book", ] } diff --git a/stacks/plotting-book/main.tf b/stacks/plotting-book/main.tf index bb1dfa1c..e031069a 100644 --- a/stacks/plotting-book/main.tf +++ b/stacks/plotting-book/main.tf @@ -118,6 +118,12 @@ resource "kubernetes_deployment" "plotting-book" { } } spec { + # Pull the PRIVATE ghcr image. The ghcr-credentials secret is cloned + # into this namespace by the Kyverno generate policy in stacks/kyverno + # (plotting-book is on its ghcr_private_namespaces allowlist). + image_pull_secrets { + name = "ghcr-credentials" + } volume { name = "data" persistent_volume_claim { @@ -125,10 +131,12 @@ resource "kubernetes_deployment" "plotting-book" { } } container { - # Baseline only — CI owns the live tag (GHA builds viktorbarzin/book-plotter:, - # Woodpecker repo 43 set-images it; see ignore_changes above). :latest is pushed by - # the same GHA build, so a from-scratch apply starts on current code. - image = "viktorbarzin/book-plotter:latest" + # Baseline only — CI owns the live tag (GHA in Anca's repo builds + # ghcr.io/passionprojectsanca/book-plotter:vX.Y.Z, Woodpecker repo 43 + # set-images it; see ignore_changes above). :latest is pushed by the + # same GHA build, so a from-scratch apply starts on current code. + # PRIVATE package — pulled via the ghcr-credentials secret below. + image = "ghcr.io/passionprojectsanca/book-plotter:latest" name = "plotting-book" image_pull_policy = "Always" env {