4.8 KiB
ArtifactAPI Specification
Repository model
Every repository entry in remotes.yaml has two orthogonal fields:
| field | values | meaning |
|---|---|---|
type |
local, remote, virtual |
repository kind — how the repo is served |
package |
docker, rpm, alpine, generic |
package format — what protocol and caching rules to apply |
type
local— files are uploaded directly to the API and stored in S3; no upstream.remote— proxies and caches content from an upstream URL (base_url).virtual— aggregates multiple repositories (not yet implemented).
package
docker— upstream speaks the OCI Distribution API (Bearer auth, manifest/blob paths).rpm— upstream is an RPM repository; repodata files are index files.alpine— upstream is an Alpine APK repository;APKINDEX.tar.gzis an index file.generic— plain HTTP file download; no format-specific logic.
Caching
Two cache classes determine retention:
| class | stored | TTL |
|---|---|---|
| file | S3 object, no Redis entry | file_ttl — 0 means indefinite |
| index | S3 object + Redis TTL key | index_ttl — when the Redis key expires the S3 object is deleted and re-fetched |
Index files are mutable metadata that must expire. File-class objects are treated as immutable and cached indefinitely (unless file_ttl is set).
Docker package rules
URL construction
Remote URLs are prefixed with /v2/ for package: docker remotes:
{base_url}/v2/{path}
e.g. library/nginx/manifests/latest → https://registry-1.docker.io/v2/library/nginx/manifests/latest
Authentication
Docker registries use Bearer token challenges. On a 401 Unauthorized response, the API:
- Parses the
WWW-Authenticate: Bearerheader forrealm,service, andscope. - Fetches a token from the auth realm, supplying
username/passwordfrom the remote config if present. - Retries the request with
Authorization: Bearer <token>.
Tokens are cached in-memory keyed by (realm, service, scope, username) and expire 30 seconds before their stated expires_in.
Cache classification
| path pattern | mutable | class | TTL source |
|---|---|---|---|
/manifests/<tag> |
yes | index | index_ttl |
/tags/list |
yes | index | index_ttl |
/manifests/sha256:<digest> |
no | file | file_ttl |
/blobs/sha256:<digest> |
no | file | file_ttl |
Tag-based manifests and tag lists are mutable and cached as index. Digest-pinned manifests and blobs are content-addressed and cached indefinitely as files.
Blob deduplication
Blobs are stored under a digest-keyed path shared across all images on the same remote:
{remote_name}/blobs/sha256/{digest}
The same layer pulled by different images is stored once.
Accept headers
| path | Accept header sent upstream |
|---|---|
/manifests/… |
application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json |
/blobs/… |
application/octet-stream |
OCI Distribution API endpoint
The API exposes a native Docker registry interface so clients can use docker pull directly:
GET /v2/ — version ping
GET /v2/{remote}/{image}/manifests/{ref} — fetch manifest
HEAD /v2/{remote}/{image}/manifests/{ref} — manifest metadata
GET /v2/{remote}/{image}/blobs/{digest} — fetch blob
HEAD /v2/{remote}/{image}/blobs/{digest} — blob metadata
Responses include Docker-Distribution-Api-Version, Docker-Content-Digest, and the correct OCI Content-Type (detected from the manifest mediaType field).
Only remotes with package: docker are accessible via this endpoint. All other remotes return 400.
include_patterns
include_patterns is a list of Python regexes applied to every request before any upstream fetch or cache lookup.
Generic remotes (/api/v1/remote/…):
- Patterns match against the file path and the full path.
- Index files (mutable metadata) bypass pattern checks and are always allowed.
Docker remotes (/v2/…):
- Patterns match against the image name (first two path segments, e.g.
library/nginx) and the full path. - The index-file exemption does not apply — patterns restrict whole images, including their manifests and tag lists.
- No patterns configured → all images allowed.
Returns 403 when a request is blocked.
Versioning
The package version is derived from git tags via hatch-vcs. Tags follow the format v{MAJOR}.{MINOR}.{PATCH}.
Docker images are built with the version injected at build time:
SETUPTOOLS_SCM_PRETEND_VERSION=<version> uv sync --frozen
The Makefile provides patch, minor, and major targets that tag the current commit and rebuild the container image.