Uh oh!
There was an error while loading. Please reload this page.
docs(docker): publish a Docker Hub Overview, and a step to keep it current - #289
Conversation
…rrent The Docker Hub repository has 446 pulls and a blank Overview, and a blank short description with it. Neither field is set by anything: `docker push` and the `skopeo copy` that promote-image uses both write images, never repository metadata, and nothing in CI touches the Hub API. GHCR looks documented for the same image because it derives its package description from the OCI labels the Dockerfile already sets (`org.opencontainers.image.title` / `.description`). Docker Hub ignores those labels for the Overview entirely, which is why one registry reads as maintained and the other reads as abandoned. `docker/README.md` was not reusable as-is. It is contributor-facing: how to generate notices, build with `--build-arg`, run the four-role Compose stack, run the smoke test. Someone pulling the image wants the tag policy, the runtime contract, the fact that PostgreSQL is not bundled, and how to verify the signature. So this adds a consumer-facing file rather than pointing the Hub at the existing one. Content is sourced from `docker/dockerhub-overview.md` so the published Overview is reviewed like any other content and cannot drift from the repository. It carries the trademark notice from the README verbatim, since the Hub page is a public front door and previously carried no notice at all, and links to extenddb.org. The workflow is dispatch-only and gated on the `dockerhub` environment, because it uses the same credential that can push images. Deliberately not wired into promote-image: the Overview is version-neutral, so a wording fix should not require a release and a release should not silently rewrite it. It also keeps this clear of #283, which is already changing that file. It verifies rather than assumes: after the PATCH it re-reads the repository anonymously and diffs the published Overview against the file, so a silent no-op or a partial write fails the run instead of looking green. Verification: - YAML parses; the `jq` payload construction was exercised locally without credentials (4945 characters, against Docker Hub's 25000 cap; short description 73 characters against its 100 cap, both asserted in the job). - Every link in the Overview was fetched: extenddb.org, the repository, the releases page, and the four linked docs all return 200. - Runtime facts were taken from the Dockerfile rather than restated from memory: port 18443, state at /var/lib/extenddb, UID/GID 10001:10001, tini entrypoint, and a healthcheck that is liveness only. The Hub API path itself cannot be exercised from a PR, since the credential exists only inside the gated job. The read-back assertion is there so the first dispatch proves it end to end rather than reporting success blindly.
Covers `extenddb/extenddb-dev` as well as the Postgres image, and turns the workflow into a per-image dispatch rather than one hardcoded repository. The dev image needs a different page, not a trimmed one. Its whole value is that it is zero-config, so the Overview leads with the one-line `docker run` and the SDK call against it, then the two storage modes. It also has to carry warnings the production page does not: plain HTTP, open authorization, loopback-only publishing, and no real data. Those are stated up front rather than in a limitations footnote, because someone skimming a registry page and copying the run command is the exact person who needs them. Both use the documented example credential verbatim, since the server seeds it and prints it, and secret scanners recognise it as an example. One image per dispatch rather than a matrix over both: a Docker Hub repository that does not exist yet should fail loudly for that image alone, not fail a run that also had valid work to do for the other. Hence the explicit repository-exists gate, which returns a clear "create the repository first" error instead of an opaque PATCH failure. Ordering, worth stating because one link depends on it: the dev Overview links `docs/dev-image.md`, which currently 404s on main because it lands with #281. That is safe rather than a broken link in production, because the dev Overview cannot be published until `extenddb/extenddb-dev` exists on Docker Hub, and that repository is created as part of shipping #281. The repository-exists gate enforces the ordering rather than relying on anyone remembering it. Verification: - YAML parses; the choice input resolves to both images. - Size caps asserted in the job and checked here: Postgres Overview 4945 characters and Dev 4742, against the 25000 cap; short descriptions 74 and 89 against the 100 cap. - Every link in the dev Overview was fetched. All 200 except `docs/dev-image.md`, explained above. - Dev facts were read from `Dockerfile.dev` and `docs/dev-image.md` on the #281 branch rather than restated: uid 65532, distroless cc-debian12 nonroot with no shell, port 18443 over plain HTTP, state at /var/lib/extenddb, `EXTENDDB__STORAGE__SQLITE__PATH=:memory:` for the ephemeral mode, and a built-in healthcheck.
…alth The first drafts read like runbooks: tag-policy tables, a runtime-contract table, verification instructions. That is reference material, and it belongs in the docs these pages link to, not on a registry front page. Reworked both to the shape a reader of a Docker Hub page actually gets value from: what it is in one sentence, why you would use it, then one command that works. Structure now mirrors the convention such pages follow — definition, benefits, getting started on Docker, where to learn more, closing note. Benefits are stated as benefits rather than as properties: works with your existing DynamoDB API calls, no internet connection needed, no provisioned throughput, storage or data transfer costs. Postgres went from 641 to 456 words, dev from 609 to 347. Both are shorter than what they replace and say more of what a reader came for. The tables are gone; tag policy and signature verification survive as two short paragraphs because pinning and provenance are decisions a reader makes on this page. Two things stay that the pages we are modelling do not need. The trademark notice, which is now a closing Note rather than a banner above the fold: it reads as a standard legal footer there instead of as the first thing the page says about itself, while remaining complete and unmissable. And on the dev page, the plain-HTTP and open-authorization warning, kept in the Note with the loopback instruction, because someone copying a run command off a registry page is precisely who needs it. It is no longer three warnings before the reader has learned what the image is. Also drops a link the first draft would have shipped broken: the Postgres page pointed at `hub.docker.com/r/extenddb/extenddb-dev`, which does not exist yet. The Postgres Overview is publishable today, so that would have been a dead link on a live page. Which is why the workflow now fails on any link in the Overview that does not return 200, loopback examples excluded. A dead link on a public front door is worse than a blank page, and this class of error should not depend on someone re-checking by hand. It has the intended effect immediately: the Postgres page passes, and the dev page fails on `docs/dev-image.md`, which lands with #281. That is correct rather than inconvenient, since the dev Overview cannot be published until `extenddb/extenddb-dev` exists, and that repository is created as part of shipping #281. Verification: - YAML parses; seven steps in order. - The link check was run exactly as the job runs it, per file: Postgres all 200, dev failing only on the #281 doc. - Sizes re-checked against Docker Hub's caps: 3462 and 2675 characters against 25000; short descriptions unchanged at 74 and 89 against 100.
The published extenddb/extenddb-dev image exposes 18080/tcp and sets EXTENDDB__SERVER__PORT=18080 (verified on the pulled 0.1.7 digest). This Overview was written before that flip landed in #281, so both quickstart commands pointed at 18443 and neither worked as printed. Both corrected commands were run verbatim against the published image: the container reaches Docker healthy and list-tables returns. The memory-mode instruction was also verified, with a durable control: the volume-backed container keeps its table across a full container recreate while the :memory: container loses it on restart. The Postgres Overview is unchanged; 18443 is correct there, that image does serve TLS on it.
…egistries Two claims in the postgres Overview had drifted from reality. The verification paragraph said each GitHub release attaches the public key and gives the exact cosign verify command. That was true of v0.1.6 and is false of v0.1.7, which carries no assets and no verify instructions. The durable home for the key is now the repository itself: #283 commits extenddb-signing.pub.pem at the root (merged 2026-08-20, link verified 200). Pointing the Overview there survives any release whose notes are thin, and per-release command specifics stay in release notes where tlog differences between pre- and post-Rekor releases belong. Restoring the v0.1.7 release assets is tracked separately. The registry list said images are mirrored to GHCR. ECR Public already serves 0.1.5, 0.1.6, latest and both signature artifacts (verified live), and #283 makes it a first-class promotion target, so the Overview now names both mirrors and notes the signatures travel too. The dev Overview needed nothing: its rewrite carries no per-release verification claim. All seven links in the postgres Overview return 200, including the new in-repo key path; 3,571 characters against Docker Hub's 25,000 cap.
robinnsc
left a comment
There was a problem hiding this comment.
Reviewed all three files and reproduced the one thing I think blocks. The design reasoning is exactly right — the GHCR-vs-Hub metadata asymmetry explanation is the kind of context that saves the next person an hour, sourcing the Overview from a reviewed file is the correct trust model, the anonymous read-back is the right verification philosophy, and the local case validation matches the convention the other workflows just converged on. Content checks out too: dev overview's port 18080 and :memory: env var match the merged #281 behavior, and the tag-policy section matches what promote-registry actually enforces as of today.
Blocking, reproduced locally: the verify step false-fails on every successful publish. GOT_FULL=$(jq -r ...) strips the trailing newline via command substitution, while cat "$FILE" keeps it (both .md files end with newlines), so diff reports a difference every time — the gate can never pass, and the first dispatch will report failure after publishing correctly, which reads as a broken publish rather than a broken check. One-line fix — compare stripped-to-stripped:
if [[ "$GOT_FULL"!="$(cat "$FILE")" ]];then(both substitutions strip trailing newlines, so the comparison is apples-to-apples; same result could be had with printf '%s\n' on the left of the diff.)
Should-fix: all four pre-publish gates (file exists, size caps, link check, repo-exists) are credential-free but live inside the environment-gated job — so a dead link or a 404 burns a reviewer approval to discover, the same shape you flagged on the sign-image dev option. Splitting them into an ungated validate job (contents: read, no secrets) with publish: needs: validate means validation runs on dispatch, and the reviewer approves knowing the content already passed.
Nits:
- The link checker has no retry, so a transient network blip fails the run (and post-split, at zero cost; pre-split, it burns an approval):
curl --retry 2covers it. - The PR body's "extenddb/extenddb-dev does not exist yet (404)" is stale — you created it last night; the repo-exists gate makes this self-resolving, just worth a body edit so the record reads right.
Happy to re-check after the comparison fix — with it, this closes the last blank-front-door problem the images have.
…e gate Review round on #289 (robinnsc), all four items. The blocker, reproduced before fixing: the verify step compared $(jq -r ...) output - trailing newline stripped by command substitution - against raw `cat` output, which keeps the newline every .md file ends with. diff therefore reported a difference on every successful publish: the gate could never pass, and the first dispatch would have reported failure after publishing correctly. Both sides of the comparison are now command substitutions, so both are stripped and the comparison is apples-to-apples. Verified against the real overview file: the old comparison fails, the new one passes. The should-fix: all four pre-publish checks (file exists, size caps, link health, repository exists) are credential-free but lived inside the environment-gated job, so a dead link burned a reviewer approval to discover - the same shape flagged on sign-image's dev option. They now run in an ungated `validate` job with no secrets; `publish` needs it and carries the gate, so the reviewer approves content that has already passed validation. Nits: the link checker and the repo-exists probe retry transient failures (--retry 2), which post-split cost nothing. The PR body's stale "extenddb/extenddb-dev does not exist yet (404)" claim is corrected in the PR description alongside this commit; the repo was created with the 0.1.7 release.
LeeroyHannigan
commented
Aug 20, 2026
All four addressed in f6e9928:
Thanks for actually running it - that comparison bug would have made the first real dispatch look like a broken publish. |
Uh oh!
There was an error while loading. Please reload this page.
What this does
Publishes a Docker Hub Overview for
extenddb/extenddb-postgres, and adds a step that keeps it in sync with the repository.The Hub page currently has 446 pulls and a blank Overview, plus a blank short description. A bare page reads as an abandoned image.
Why it is blank, and why a push will never fix it
Neither
docker pushnor theskopeo copythatpromote-imageuses touches repository metadata. The Overview isfull_descriptionon the Docker Hub repository object, not on any image or manifest, and nothing in CI calls the Hub API.The asymmetry worth knowing: GHCR looks documented for the same image because it derives its package description from the OCI labels the Dockerfile already sets at lines 47-48 (
org.opencontainers.image.titleand.description). Docker Hub ignores those labels for the Overview entirely. Same artifact, maintained on one registry and blank on the other.Why a new file rather than pointing at
docker/README.mddocker/README.mdis contributor-facing: generate notices,docker buildwith build args, the four-role Compose stack, run the smoke test. Someone pulling the image needs a different set of facts:X.Y.Zimmutable,latestonly moves forward,sha-<commit>are unpromoted candidates)tini, and that the healthcheck is liveness onlySo this adds
docker/dockerhub-overview.mdand leaves the existing doc for contributors.It also carries the trademark notice from the README verbatim. The Hub page is a public front door and previously carried no notice at all, which matters more there than in a directory README. And it links to extenddb.org.
Design of the sync step
Sourced from a file in the repo, so the published Overview is reviewed like any other content and cannot drift.
Dispatch-only, gated on the
dockerhubenvironment. It uses the same credential that can push images, so it gets the same gate.Deliberately not wired into
promote-image. The Overview is version-neutral: a wording fix should not require a release, and a release should not silently rewrite the page. It also keeps this clear of #283, which is already changing that workflow.It verifies rather than assumes. After the
PATCHit re-reads the repository anonymously and diffs the published Overview against the file, so a silent no-op or a partial write fails the run instead of reporting green. That matters because the Hub API path cannot be exercised from a PR: the credential only exists inside the gated job. The first dispatch proves it end to end.Testing done
jqpayload construction was exercised locally with no credentials involved.Dockerfilerather than restated from memory:EXPOSE 18443, state at/var/lib/extenddb,USER 10001:10001,tinientrypoint, and the healthcheck being liveness rather than backend readiness.cosigninvocation, because v0.1.6 needs--insecure-ignore-tlog=trueand releases after ci(release): sign and propagate in the publish stage #284 will not. Each release's notes already carry the right command and attachextenddb-signing.pub.pem.Follow-ups, not in scope
extenddb/extenddb-devnow exists on Docker Hub (created with the 0.1.7 release), so both Overviews are publishable; the workflow is parameterised byHUB_REPOso it is a copy of the job rather than new machinery.Checklist
ADR / RFC: n/a — no change to the wire protocol,
Storagetrait, auth model, on-disk format, or public CLI surface.Breaking changes
None. No image, tag, or digest is affected.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache License 2.0 and I agree to the Developer Certificate of Origin (DCO). See CONTRIBUTING.md for details.