Skip to content

ci(images): publish managed-database images to GHCR and gate releases on them - #1462

Merged
dviejokfs merged 3 commits into
mainfrom
ci/database-images-ghcr
Oct 12, 2026
Merged

dviejokfs merged 3 commits into
mainfrom
ci/database-images-ghcr

Conversation

@dviejokfs

@dviejokfs dviejokfs commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The managed-database images (postgres-walg, pgvector-walg, timescaledb-walg, postgres-ha, redis-walg, mongodb-walg) were pushed to Docker Hub by hand. No workflow builds them and nothing records what was published. As a result, tags the runtime references are missing: ALLOWED_POSTGRES_DOCKER_IMAGES allows gotempsh/postgres-walg:15-bookworm and :16-bookworm, but neither exists on Docker Hub.

This PR publishes all of them to GHCR on a schedule and makes releases fail if any required tag can't be pulled.

database-images.yml (new)

What gets built is decided by .github/scripts/database_image_matrix.py, which has unit tests.

Tag kind Example Built when
Floating major postgres-walg:18-bookworm, pgvector-walg:pg18, mongodb-walg:8.0 Every run, so upstream base-image security rebuilds are picked up
Pinned PG minor postgres-walg:18.3-bookworm, postgres-ha:18.3-bookworm The newest 4 upstream minors per major, discovered from docker.io/library/postgres. The newest is rebuilt every run; older ones only when missing or when their build context changes
  • Triggers: a weekly schedule, pushes to main that touch an image, and workflow_dispatch (rebuild_all also rebuilds frozen minors). Pull requests build and verify only the images they touch and publish nothing.
  • Before push: wal-g --version must run, and PostgreSQL images must report exactly the version on their tag. This catches a dependency install quietly upgrading the server to a different minor.
  • After push: each image is built for amd64 and arm64, signed with cosign, and gets a provenance attestation and an SBOM. The pinned action SHAs match mariadb-walg-image.yml.
  • Checking which images changed: done with git diff against the base commit rather than the GitHub API, so the release can call this workflow without granting pull-requests: read.

Release gate

release.yml gets a database-images job (it needs validate-release-ref and require-tests, and create-release needs it) that calls the workflow with ensure: true:

  1. It publishes only tags missing from GHCR. A release never moves a tag under a running database; keeping images current is the weekly schedule's job.
    • An image published under some of its tags but not all (a newly added alias) is not rebuilt: a retag job points the missing tags at the existing digest with docker buildx imagetools create, so the existing signature and attestations still apply.
    • A GHCR lookup that fails (HTTP 429/5xx, network) is retried three times and then fails the run. Only an explicit 401/403/404 counts as "missing", so an outage can't cause a rebuild over a published tag.
  2. It then fails the release unless every required tag can be pulled anonymously, the way a fresh install pulls it. A private package counts as missing.

test-nightly-release-workflows.sh now asserts the gate is in place and adds the new job to its permissions allowlist.

postgres-ha

  • The postgresql-${PG_VERSION}-auto-failover package name broke for minor builds (PG_VERSION=18.3). It now uses PG_MAJOR from the upstream image's environment.
  • Each build is also tagged {major}-bookworm-walg, the tag DEFAULT_CLUSTER_IMAGE refers to. Every build includes WAL-G now, so the alias points at the same image.

Scope and load

This is CI only. No runtime code or image references change, and the runtime defaults still point at Docker Hub gotempsh/*. The first run on main publishes 47 tags (about 47 matrix jobs, at most 12 at a time). After that, a scheduled run is about 15 jobs (floating tags plus the newest minor of each major).

Test plan

  • python3 -m unittest test_database_image_matrix: 36 tests covering minor discovery (numeric sort, the missing upstream 18.5, unsupported majors), forced and missing-only plans, aliases and retags, GHCR lookups (absent, denied, rate-limited, unreachable, retried), the anonymous check, CLI validation, and that every tag the runtime references is in the catalog
  • .github/scripts/test-nightly-release-workflows.sh passes. With database-images removed from create-release.needs, it fails as expected
  • actionlint is clean on database-images.yml and release.yml
  • Planner run against live registries: it finds the upstream minors 18.6, 18.4, 18.3 and 18.2 (there is no 18.5-bookworm upstream); --check passes for a public GHCR tag and fails for an unpublished one
  • The step that detects changed images was run locally for scheduled, release, manual full-rebuild and first-push runs, and against real history
  • Local docker build --build-arg PG_VERSION=18.3 images/postgres-ha: postgres (PostgreSQL) 18.3, wal-g v3.0.8, pg_autoctl 2.2
  • scripts/source_attribution.py check passes

After merge

  1. The push to main runs the workflow and publishes every tag.
  2. An org owner must set each new package to Public: postgres-walg, postgres-ha, pgvector-walg, timescaledb-walg, redis-walg, mongodb-walg. Until then, the end-of-run check fails on both scheduled runs and releases. That's intended, because private packages also break fresh installs.

Follow-ups (not in this PR)

  • Switch the runtime defaults from Docker Hub gotempsh/* to ghcr.io/gotempsh/*. This needs its own review, because changing an image reference can recreate existing containers.
  • MariaDB: the backup capability endpoint recommends ghcr.io/gotempsh/mariadb-walg:11.4. That tag doesn't exist (the image only has commit-suffixed tags), and validate_immutable_mariadb_image rejects plain tags anyway. The recommendation should be a digest-pinned reference.
  • The third-party runtime images (rustfs/rustfs:1.0.0, mariadb:lts, mongo:7.0, busybox, alpine, python:3.13-slim, aquasec/trivy, the OpenTelemetry collector) still come straight from Docker Hub. MinIO has already withdrawn quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z (LEGACY_MINIO_IMAGE), which now returns 401 on both quay.io and Docker Hub, so older MinIO services can't re-pull their image. Proposal: copy these images byte-for-byte into ghcr.io/gotempsh/mirror/… and cover them with the same release gate.

🤖 Generated with Claude Code

… on them

The postgres/pgvector/timescaledb/postgres-ha/redis/mongodb WAL-G images
were pushed to Docker Hub by hand, with no workflow and no record of what
was published. Several tags the runtime references were never published
(postgres-walg:15-bookworm and :16-bookworm), and nothing caught it.

Add database-images.yml, driven by a tested planner script:
- floating major tags are rebuilt every run (weekly schedule, pushes that
  touch an image, manual dispatch) so upstream base-image security
  rebuilds are picked up;
- the newest 4 PostgreSQL minors per major are discovered from upstream
  and published as pinned tags (18.6/18.4/18.3/18.2-bookworm); frozen
  older minors are only built when missing or their context changed;
- every image is verified before push (wal-g runs, and postgres reports
  exactly the tagged version), then signed with cosign.

release.yml now calls it in ensure mode: it publishes only missing tags,
so a release never moves a tag under a running database, then fails the
release unless every required tag is anonymously pullable from GHCR.

postgres-ha now takes the auto-failover package name from the upstream
PG_MAJOR so it can build from a minor tag, and publishes the
-bookworm-walg alias that DEFAULT_CLUSTER_IMAGE names.

Signed-off-by: David Viejo <dviejokfs@temps.sh>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

📓 Changelog preview

This is what your commits will add to the generated CHANGELOG.md at release time (via git-cliff). Do not edit CHANGELOG.md by hand — it is generated from your Conventional Commit messages.

## [Unreleased]

### CI

- **images:** Publish managed-database images to GHCR and gate releases on them

### Fixed

- **images:** Never rebuild a published database tag on a failed lookup or missing alias
- **images:** Serialize every database-image publisher in one concurrency group

@greptile-apps

greptile-apps Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Critical impact] The PR appears safe to merge; the reported alias race is fixed and no new actionable issue remains.

Summary

This PR adds managed-database image publishing to GHCR and makes releases wait for an anonymous tag check. The latest change fixes the previously reported alias race.

  • Managed-database images are built and published to GHCR on a schedule.
  • Releases check that every required database image is publicly pullable.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Release, weekly, push, or manual publisher] --> B[Shared publishing group]
  B --> C[Plan builds and missing aliases]
  C --> D[Build and publish missing images]
  C --> E[Copy missing aliases]
  D --> F[Check required tags anonymously]
  E --> F
  F --> G[Release may continue]
  H[Pull request or dry run] --> I[Separate verification group]
  I --> J[Build without publishing]
Loading

Reviews (3) · Last reviewed commit: "fix(images): serialize every database-im..." · Reviewed by Greptile

…p or missing alias

A GHCR lookup that failed (rate limit, outage, network) read as "missing",
so a release would rebuild and push over a tag that was already published.
Lookups are now retried and then abort the plan; only an explicit
absent/denied answer counts as missing.

An image published under some of its tags but not all (a new alias) was
rebuilt in full, replacing the tags already there. The missing tags are now
pointed at the existing digest by a retag job instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: David Viejo <dviejokfs@temps.sh>
Comment thread .github/workflows/database-images.yml Resolved
…ncy group

Releases and scheduled runs were keyed on github.ref, so they got different concurrency groups and could overlap. A release's alias copy, planned from the old image, could then replace tags a scheduled build had just published. All runs that can publish now share one group; pull requests and dry runs still only supersede themselves.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: David Viejo <dviejokfs@temps.sh>
@dviejokfs
dviejokfs merged commit 8cf2e21 into main Oct 12, 2026
58 of 61 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant