Pin the digest, never the latest tag
Every manifest in the Keenrig catalog pins a specific digest and is Ed25519-signed. Here is why, and why "just use the latest tag" is a slow-acting trap.
An image tag is not an identifier. It is a pointer, and that pointer can be moved at any moment by somebody else. Reinstall the same app six months later via the latest tag and you get different software, with nothing on your machine recording that it happened.
A Keenrig catalog manifest looks like this — note that it carries both the tag and the digest, and the digest is what actually pulls the image:
{
"id": "nginx",
"title": "Nginx",
"version": "1.27.4",
"repository": "docker.io/library/nginx",
"tag": "1.27.4-alpine",
"digest": "sha256:4ff102c5d78d254a…",
"http_port": 80,
"min_memory_mb": 64
}Tags still earn their place — just not for pulling
We keep the tag because it is the human-readable part: "1.27.4-alpine" tells you what you are running, a digest tells you nothing. But when pulling the image, only the digest is used. The two fields serve two different audiences and their roles must not blur.
Signatures solve the rest
Pinning a digest guarantees you get the right image, but not that the manifest itself survived the trip unaltered. So the whole catalog is Ed25519-signed and the instance verifies the signature before trusting a single line of it. The verification key ships inside the binary — there is no "download the key, then trust it" step.
An offline catalog is the default
A minimum catalog is embedded straight into the binary. The reason is practical: if the catalog had to be fetched at startup, "no network" would turn into "cannot install anything", and the catalog server would become a single point of failure for every instance in existence. A remote catalog still exists — it adds to the embedded set rather than replacing it.