{"id":51226765,"url":"https://github.com/cartyc/golden-image","last_synced_at":"2026-06-28T12:30:49.602Z","repository":{"id":366236070,"uuid":"959835454","full_name":"cartyc/golden-image","owner":"cartyc","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-20T23:09:25.000Z","size":212,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-20T23:09:56.157Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cartyc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-04-03T12:35:24.000Z","updated_at":"2026-06-20T23:09:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cartyc/golden-image","commit_stats":null,"previous_names":["cartyc/golden-image"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/cartyc/golden-image","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartyc%2Fgolden-image","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartyc%2Fgolden-image/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartyc%2Fgolden-image/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartyc%2Fgolden-image/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cartyc","download_url":"https://codeload.github.com/cartyc/golden-image/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cartyc%2Fgolden-image/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34889047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-28T12:30:46.579Z","updated_at":"2026-06-28T12:30:49.596Z","avatar_url":"https://github.com/cartyc.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chainguard Golden Images Pipeline Example\n\n## Goals\n\n- Demonstrate an ingestion pipeline for Chainguard images into a Golden Images repository\n- Assume a Platform Engineer perspective\n- Demonstrate best practices — server-side customization via Custom Assembly, signature verification before mirroring, and preserving upstream signatures/attestations\n\n## Non-Goals\n\n- Not all-encompassing; this is a \"what could be\" example of a potential pipeline\n\n## Pipeline overview\n\nTwo complementary lanes feed the golden-images registry (Google Artifact Registry). Customization happens **server-side** with Chainguard Custom Assembly (no derived Dockerfiles), and everything reaches Artifact Registry through the `cgr-sync` pass-through mirror:\n\n```mermaid\nflowchart TB\n  src[(\"cgr.dev\u003cbr/\u003eChainguard source\")]\n  gar[(\"Google Artifact Registry\u003cbr/\u003egolden images\")]\n\n  subgraph ca[\"Custom Assembly — custom-assembly/*.yaml\"]\n    direction TB\n    cfg[\"apko overlay\u003cbr/\u003epackages · cert · annotations\"] --\u003e apply[\"chainctl build apply\"]\n    apply --\u003e built[\"Chainguard builds + signs\u003cbr/\u003ecustom-python\"]\n  end\n\n  subgraph pass[\"Pass-through lane — cgr-sync\"]\n    direction TB\n    cs[\"cgr-sync\u003cbr/\u003everify · diff-by-digest\u003cbr/\u003epreserve index + signatures\"]\n  end\n\n  src --\u003e|\"customize server-side\"| cfg\n  src --\u003e|\"ship as-is\"| cs\n  built --\u003e|\"custom image on cgr.dev\"| cs\n  cs --\u003e gar\n```\n\n### 1. Custom Assembly — `custom-assembly/*.yaml`\n\nFor images that need modification (extra packages, certificates, annotations). Chainguard assembles and **signs** the customized image server-side from an apko overlay — there's no derived Dockerfile to maintain, and the customization is captured in the image's provenance. See the [Custom Assembly](#custom-assembly-custom-assembly) section below.\n\n### 2. Pass-through lane — `cgr-sync.yaml` + `.github/workflows/passthrough-mirror.yaml`\n\nMirrors images **as-is** from `cgr.dev` into the registry with [`cgr-sync`](https://github.com/cartyc/image-syncer) — including the Custom Assembly images built above:\n\n- Preserves the **multi-arch index** and the **upstream cosign signatures / attestations**.\n- **Verifies** each image's signature before copying (Chainguard's signing identity; Custom Assembly images use the org's build identity — see `cgr-sync.yaml`).\n- **Diffs by digest** — only copies what's missing or changed, so re-runs are cheap.\n- Adding an image is a one-line entry in `cgr-sync.yaml`.\n- After mirroring, a **`verify` job independently checks each golden image in Artifact Registry**: a `grype` CVE scan (fails on `critical` by default — set the `GRYPE_FAIL_ON` variable to adjust) plus a `cosign verify-attestation` SBOM check, reusing the per-image identities from `cgr-sync.yaml`.\n\nRuns on a schedule (every 6h) plus manual dispatch (timer-driven — it does not run on merge; use the manual trigger to mirror a catalog change immediately).\n\n### Which lane?\n\n| The image… | Lane |\n| --- | --- |\n| needs extra packages, certs, or other modification | **Custom Assembly** (built + signed by Chainguard) |\n| ships unmodified | **pass-through** |\n\nEither way the image lands in Artifact Registry via the pass-through mirror.\n\n## Repository layout\n\n| Path | What it is | How you use it |\n| --- | --- | --- |\n| `cgr-sync.yaml` | The **pass-through catalog** — which images/tags get mirrored as-is from `cgr.dev` into Artifact Registry, plus the signature-verify policy. | Add or remove an image by editing the `repositories:` list (one entry = a repo + its tags); shared `defaults:` cover source, destination, and verify. `${VAR}` placeholders are filled from the workflow's secrets at run time. |\n| `custom-assembly/` | Chainguard **Custom Assembly** overlays — declarative, server-side image customizations (apko). | See the table rows below; the build workflow merges the base with each per-image overlay and applies the result. |\n| \u0026nbsp;\u0026nbsp;`custom-assembly/all.yaml` | The **base** overlay, merged into **every** custom image. | Put things that should apply everywhere here — common packages, env vars, annotations, and the internal CA. Edit this to change all custom images at once. |\n| \u0026nbsp;\u0026nbsp;`custom-assembly/\u003cimage\u003e.yaml` | A **per-image** overlay (e.g. `python.yaml`, `jdk.yaml`). | Image-specific packages/config, layered on top of `all.yaml`. The filename maps to a target repo in the build workflow's matrix; to customize one image, edit its file. |\n| `scripts/` | Helper scripts the CI calls (they both read `cgr-sync.yaml`). | `list-golden-images.py` → emits the verify targets for the post-mirror check; `list-source-refs.py` → emits source refs for the pre-merge existence check. Not run by hand normally. |\n| `.github/workflows/` | The CI lanes (see the next table). | — |\n| `LICENSE` | Apache-2.0. | — |\n\n### Workflows (`.github/workflows/`)\n\n| Workflow | Triggers | What it does |\n| --- | --- | --- |\n| `passthrough-mirror.yaml` | schedule (6h) + manual | Mirrors the `cgr-sync.yaml` catalog into Artifact Registry with `cgr-sync` (verify-before-mirror, signatures/attestations preserved), then independently verifies each landed image (grype CVE gate + cosign SBOM attestation). |\n| `custom-assembly.yaml` | PR/push on `custom-assembly/**` + manual | Merges `all.yaml` with each per-image overlay and applies it via `chainctl` so Chainguard builds + signs the custom image — `--dry-run` preview on PRs, real apply on merge to `main`. |\n| `validate.yml` | every PR + push to `main` | Lints the workflows and configs (`actionlint`, `yamllint`) and confirms `cgr-sync.yaml` / overlays parse. |\n| `validate-catalog.yml` | PR touching `cgr-sync.yaml` | Pre-merge check that every source `image:tag` in the catalog actually exists at `cgr.dev`. |\n| `digestabot.yaml` | schedule (daily) + manual | Opens a PR bumping pinned image/action digests in the repo to their latest. |\n\n## Required secrets\n\n| Secret | Used by |\n| --- | --- |\n| `DEST_REGISTRY`, `REGION`, `SERVICE_ACCOUNT_KEY` | pass-through lane (Artifact Registry destination + auth) |\n| `CHAINGUARD_IDENTITY` | all workflows — the assumable Chainguard identity for `setup-chainctl` (`\u003corg-uidp\u003e/\u003cidentity-id\u003e`) |\n| `CHAINGUARD_ORG` | source namespace — your org's registry name (e.g. `your-org.com`), used as `cgr.dev/${CHAINGUARD_ORG}` and the Custom Assembly `--parent` |\n| `CHAINGUARD_ORG_UIDP` | pass-through verify policy — the org UIDP in the cosign identity regexp (the part before `/` in `CHAINGUARD_IDENTITY`) |\n\nThese were previously hard-coded; they're org identifiers (not credentials), but\nparameterizing keeps the repo portable and free of org-specific values. The\npass-through lane also pulls a pinned `cgr-sync` release image from GHCR\n(`ghcr.io/cartyc/image-syncer`) — public, so no token is needed.\n\n## To Do\n\n- Add FIPS image validation\n- Add application image validation\n- Expand the Custom Assembly and pass-through catalogs beyond Python\n\n_The docker-build \"transform\" lane (build → grype → sign → chps → incert) was retired in favor of Custom Assembly, which builds and signs customized images server-side._\n\n## Custom Assembly (`custom-assembly/`)\n\nSome customizations are better done **server-side** with [Chainguard Custom Assembly](https://edu.chainguard.dev/chainguard/chainguard-images/features/ca-docs/custom-assembly/): Chainguard assembles and signs the customized image for you, so there's no derived Dockerfile to maintain and the change is recorded in the image's provenance.\n\n`custom-assembly/python.yaml` (and `custom-assembly/jdk.yaml`) hold each image's **specific** packages (`bash`, `curl`), while `custom-assembly/all.yaml` holds the customizations common to **every** custom image (the internal CA, French Canadian locale `glibc-locale-fr` + `LANG`). Since `chainctl … apply --file` replaces a repo's whole config, `.github/workflows/custom-assembly.yaml` **merges `all.yaml` with each per-image overlay** (`yq '… *+ …'` — append arrays, per-image scalars win) and applies the merged result (a matrix over the images): `--dry-run` on PRs (drift preview), `apply --yes` on merge. To customize all images at once, edit `all.yaml`; for one image, edit its file.\n\n### Prerequisites\n\nBefore the `custom-assembly` workflow can run, complete these one-time setup steps — skipping them produces the two failures we hit on first run (`missing: repo.update` and `no repo instance found`):\n\n1. **Grant the CI identity `repo.update`.** The assumable identity in `CHAINGUARD_IDENTITY` needs the `repo.update` capability, or `apply` fails with `[PermissionDenied] ... missing: repo.update`. Bind a role that includes it to the identity, e.g.:\n\n   ```sh\n   chainctl iam role-bindings create \\\n     --identity=$CHAINGUARD_IDENTITY --role=\u003crole-with-repo.update\u003e --group=\u003cyour-org\u003e\n   ```\n\n2. **Enable the custom-certificates Beta.** The overlays bundle an internal CA under `certificates:`; this Beta must be enabled for your org first — contact your Chainguard Customer Success team.\n\n3. **Bootstrap each custom image once.** The declarative `apply` can't *create* an image (`--save-as` only works with `edit`), so create them from the committed overlays. Pass `--file` so `edit` uses the overlay instead of opening an interactive editor (there's no `--yes`, so confirm the diff when prompted):\n\n   ```sh\n   chainctl image repo build edit --parent \u003cyour-org\u003e --repo python \\\n     --file custom-assembly/python.yaml --save-as custom-python\n   chainctl image repo build edit --parent \u003cyour-org\u003e --repo jdk \\\n     --file custom-assembly/jdk.yaml    --save-as custom-jdk\n   ```\n\n   The certs are read from the overlay's inline `certificates.additional` block; alternatively supply them from a PEM file with `--with-certificates=\u003cyour-ca\u003e.pem`.\n\nAfter bootstrapping, the workflow keeps each custom image in sync with its overlay on every merge to `main`.\n\nThe result, `cgr.dev/\u003cyour-org\u003e/custom-python`, is built and signed by Chainguard — so the **pass-through lane** mirrors it to Artifact Registry like any other image (it's already wired into `cgr-sync.yaml`, with a verify policy scoped to the Custom Assembly signing identity). It only mirrors once the bootstrap above has created the image. The overlay also bundles the internal CA (defined inline in `custom-assembly/all.yaml`) into the system truststore (replacing incert).\n\n## Runbook — common tasks\n\n\u003e PRs target `main`. The pass-through mirror is timer-driven, so after merging a catalog change either wait for the next 6h run or trigger it manually.\n\n### Add a pass-through image (mirror as-is)\n\n1. Add an entry to `cgr-sync.yaml` under `repositories:`:\n   ```yaml\n   - name: redis\n     tags:\n       list: [\"latest\", \"7\"]\n   ```\n2. Open a PR. **`validate-catalog`** confirms every `image:tag` exists at `cgr.dev` before merge; **`validate`** lints the config.\n3. Merge, then mirror it now instead of waiting: `gh workflow run passthrough-mirror.yaml` (or **Actions → Passthrough mirror → Run workflow**).\n4. The `verify` job gates it (grype CVE scan + cosign SBOM attestation). A signing-identity mismatch means the tag is signed by a different identity than the policy allows — see *Verify failures* below.\n\n### Customize **all** custom images\n\nEdit `custom-assembly/all.yaml` (the base merged into every custom image), open a PR. The `custom-assembly` job posts a per-image **diff** (informational); on merge it applies and Chainguard rebuilds each image.\n\n### Customize **one** image\n\nEdit that image's `custom-assembly/\u003cimage\u003e.yaml` (e.g. `python.yaml`). It's layered on top of `all.yaml` at apply time.\n\n### Add a brand-new custom image\n\n1. **One-time bootstrap** (the declarative `apply` can't create an image):\n   ```sh\n   chainctl image repo build edit --parent \u003cyour-org\u003e --repo \u003cbase-image\u003e \\\n     --file custom-assembly/\u003cnew\u003e.yaml --save-as custom-\u003cnew\u003e\n   ```\n2. Add a matrix entry in `custom-assembly.yaml` (`file:` + `repo:`).\n3. Add `custom-\u003cnew\u003e` to `cgr-sync.yaml` so it gets mirrored to Artifact Registry.\n\n### Rotate / replace the internal CA\n\nReplace the inlined PEM in `custom-assembly/all.yaml`, open a PR, merge. The next `custom-assembly` apply rebuilds every image with the new CA.\n\n### Change the locale (or other base env/packages)\n\nEdit `custom-assembly/all.yaml` — e.g. swap `glibc-locale-fr` / `LANG` for another locale package + value. Applies to all custom images on merge.\n\n### Upgrade `cgr-sync`\n\nBump `CGR_SYNC_IMAGE: ghcr.io/cartyc/image-syncer:vX.Y.Z` in `passthrough-mirror.yaml`, open a PR, merge.\n\n### Verify failures\n\n- **`grype found findings \u003e= critical`** — a real CVE in the mirrored image. Triage upstream; to change the gate set the `GRYPE_FAIL_ON` repo variable (e.g. `high`).\n- **`no matching signatures … got subjects [chainguard-images/images-private…]`** — the tag is signed by Chainguard's **public-catalog** identity, not your org's. Either give that repo a verify policy that accepts both identities, or drop the tag.\n- **`SBOM attestation verification failed`** — check the identity regexp resolves (the `CHAINGUARD_ORG_UIDP` secret) and that the attestation exists on the mirror (`cosign tree \u003cref\u003e`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcartyc%2Fgolden-image","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcartyc%2Fgolden-image","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcartyc%2Fgolden-image/lists"}