The version lists, release channels and end-of-life dates offered when you create
or upgrade a database come from a published, signed file. Nobody edits version
numbers by hand.
What the control plane does
- Ships an embedded catalog, so an offline or air-gapped install always works.
- Revalidates the published catalog every 24 hours with
If-None-Match. A 304
only updates the last-checked time.
- Verifies the ed25519 signature and the schema, then merges it over the embedded
catalog. The published file may add versions and update end-of-life dates. It can
never change
auto_max, image_revert, upgrade levels or advisory rules, and a
version is only offered when the engine’s version validation accepts it.
- Keeps the last good copy on disk. It is served for up to 90 days while the network
or upstream fails, after that the embedded catalog is used and status says so.
- Never blocks startup.
Until the owner sets a real public key (below) remote refresh stays disabled and
databases catalog status reports that, rather than trusting unsigned data.
Settings
direct mode queries Docker Hub’s OCI API and endoflife.date from every install,
once per 30 days with jitter, backoff and 429 handling. Trade-offs: it needs no trust
in the published file and no signing key, but each install calls third parties
(they see its IP address), it can hit anonymous rate limits that a single publisher
never would, and the result is only as good as the tag regexes in the build table.
It applies the same checks as the publisher: non-empty, no engine shrinking by more
than a third, version validation.
Freshness
Measured from generated_at (published) or fetched_at (live or direct):
Live check
POST /api/v1/database-versions/refresh?engine=<id> (CLI: databases versions <engine> --refresh) queries the registry’s tag list and endoflife.date for one
engine, validates and caches the result with its fetched_at. Per-engine cooldown is
10 minutes (the cached result comes back with a “checked just now” message), the
upstream timeout is 10 seconds, and a 429 is returned with Retry-After.
API and CLI
GET /api/v1/database-versions?engine=<id> (read)
POST /api/v1/database-versions/refresh?engine=<id> (write-sensitive)
GET /api/v1/database-catalog status (read), POST /api/v1/database-catalog/refresh (write-sensitive)
PUT /api/v1/settings/database-catalog with {"remote_enabled": bool} (root)
databases versions <engine> [--refresh], databases catalog status|refresh|set --remote on|off
Published schema (version 1)
channel is one of stable, lts, rc, beta, eol. Exactly one version per
engine has recommended: true: the newest stable version of the newest supported
line. Dates are YYYY-MM-DD; eol is omitted when none is announced.
Publishing
scripts/refresh-db-catalog reads the engine table in
internal/dbcatalogbuild/engines.go (docker repo, endoflife.date slug, tag regexes,
release-notes URL, allowlisted lines) and writes databases.v1.json plus a
detached .sig. The run fails on a schema mismatch, an engine with no versions, or an
engine shrinking by more than a third versus the previous file. Supporting a new
major means adding its line to the table.
The workflow .github/workflows/db-catalog.yml runs on the 1st of each month and on
manual dispatch, signs with the DB_CATALOG_SIGNING_KEY secret and force-pushes a
data branch that holds only catalog/databases.v1.json and its .sig. New patch
releases can therefore lag up to a month. Maintainers can dispatch the workflow early
for an urgent security release, and operators can use the live check.
One-time owner steps
- Generate the key pair locally:
go run ./scripts/refresh-db-catalog -gen-key.
- Store the private half as the repository secret
DB_CATALOG_SIGNING_KEY. Never commit it.
- Paste the public half into the
PublicKey constant in internal/dbupgrade/remote_key.go.
- Run the workflow once (
workflow_dispatch) to create the data branch.
Last modified on October 16, 2026