The registries

The service serves artifacts back as registries: docker pull and apt install work directly against it, with no registry of your own to run. Read-serving over the same immutable blobs.

$ docker pull saggar.dev/val/web:staging
$ echo "deb [trusted=yes] https://saggar.dev/apt/val staging main" \
    | sudo tee /etc/apt/sources.list.d/saggar.list
$ sudo apt update && sudo apt install my-debian-package

(The [trusted=yes] spelling is today’s; release signing for the apt front-end is not built yet.)

Addresses

OCI refs are versions (<job-id> tags), channels (staging/rc/release) or digests; tags are an image’s versions plus its channel labels, sorted:

docker pull saggar.dev/[<owner>/]<name>:<ref>

A multi-arch docker group is served through the same refs: the group’s image index answers for its group-id tag and the channel labels, each per-arch manifest answers for its own digest, and the per-arch job-id tags keep serving their plain manifests; one pull address for the whole matrix (see Fan out: formats and architectures).

apt suites are channels; the indexes publish the architectures amd64, arm64, armhf, i386, riscv64 and the arch-independent all:

deb [trusted=yes] saggar.dev/apt/[<owner>] <suite> main

HTTP routes

At the server root (machine-facing; foreign clients hardcode these).

OCI (the Docker Registry API v2):

GET /v2/
GET|HEAD /v2/[<owner>/]<name>/manifests/<ref>
GET|HEAD /v2/[<owner>/]<name>/blobs/<digest>
GET      /v2/[<owner>/]<name>/tags/list?n=<max>

GET /v2/ is the protocol handshake (docker-distribution-api-version: registry/2.0). ?n= caps the tag listing (OCI pagination).

apt (dist indexes + pool):

GET /apt/[<owner>/]dists/<suite>/Release
GET /apt/[<owner>/]dists/<suite>/main/binary-<arch>/Packages[.gz]
GET /apt/[<owner>/]pool/<artifact-id>/<file>

Owner-scoped paths

Registry paths carry the owner: this is what makes two projects named web possible.

$ docker pull saggar.dev/val/web:staging
$ echo "deb [trusted=yes] saggar.dev/apt/val staging main" | …

The flat forms (…/web:staging, …/apt staging main) keep resolving for artifacts created before accounts existed. Those rows own no project, and they are the only things the flat paths serve, so the two spellings never collide. Everything submitted after the accounts upgrade is reachable under its owner’s path exclusively.

Auth and visibility

The registries require an account; there is no open-registry mode. Every pull names a subject, and reads follow the project ladder exactly like the API: a project’s artifacts are pullable by its members, by anyone once the project is public, and by no one else. Denials are indistinguishable from unknown names.

Any live sgt-… token authenticates, full or pull-only. The credential is read the way registry clients present it:

  1. Authorization: Basic (what docker login and apt’s auth.conf send): the password part is the token, the username cosmetic;

  2. Authorization: Bearer;

  3. X-Auth-Token (curl and the CLI).

No credential (or a bad one) answers 401 with WWW-Authenticate: Basic realm="saggar". That header is what makes docker start its login dance and apt fall back to auth.conf.

Client setup:

$ docker login saggar.dev -u val -p sgt-…
# /etc/apt/auth.conf.d/saggar.conf  (chmod 600)
machine saggar.dev login val password sgt-…

apt sends those credentials automatically; apt update and apt install work unchanged.

Pull-only tokens

For credentials that live in ~/.docker/config.json or auth.conf.d (long-lived, machine-readable), mint a pull-only token: POST /api/v1/auth/tokens with {"scope": "registry"}. It authenticates on the registry front-ends and is refused everywhere else; a leaked pull credential cannot read jobs or artifacts through the API (see The management API).