The Virtual Registry classifies every request twice before it does anything else: by package type, which names the ecosystem the request belongs to, and by resource type, which says whether the request is for a manifest, a package, or something else.
The pair decides how long the response is cached, whether the request is sent through the Artifact Firewall, which URLs are rewritten in the response body, and which cache tags the object is stored under.
Classification needs no configuration. A registry does not declare which ecosystem it serves, so one registry can front an upstream that serves several.
Three signals are consulted in order, and the first that matches wins.
Accept header, for the vendor media types that name their own format: application/vnd.docker.* and application/vnd.oci.* for OCI, application/vnd.npm.* for npm, application/vnd.pypi.simple.v1+* for PyPI.User-Agent, which is what identifies most clients. The table below lists what is recognised.curl of a package URL or a client whose user agent is unknown.A request that matches none of the three is typed other and cached on the general rules, with no ecosystem-specific handling.
Requests reaching a repository manager rather than a registry carry a path prefix that the package path does not. Sonatype Nexus (/repository/<repo>) and JFrog Artifactory (/artifactory/<repo> and /<context>/api/<type>/<repo>) prefixes are recognised and stripped before the package coordinates are read, so the same rules and cache keys apply either way.
| Package type | Clients recognised by user agent |
|---|---|
oci |
docker, containerd, cri-o, containers |
npm |
npm, yarn, pnpm, bun |
pypi |
pip, twine, poetry, python-requests |
maven |
Apache-Maven, Gradle |
nuget |
NuGet, dotnet, VisualStudio |
hex |
Mix, hex_core |
cargo |
cargo |
conda |
conda, mamba, micromamba |
git, git-lfs |
git, git-lfs |
helm |
Helm |
php |
Composer |
deb |
Debian APT, apt-get, aptitude |
rpm |
libdnf, yum, rpm |
rubygems |
RubyGems, bundler |
conan |
conan |
ansible |
ansible, ansible-galaxy, ansible-runner, mazer, AWX |
apk |
apk |
bower |
bower |
chef |
Chef Client |
cocoa |
CocoaPods |
cran |
R |
dart |
Dart pub |
ivy |
Apache-Ivy |
opkg |
opkg |
puppet |
Puppet |
sbt |
sbt |
swift |
swift-package-manager |
terraform |
Terraform |
vagrant |
Vagrant |
The go and github types have no user agent of their own and are recognised from the URL, as are the ecosystems served under a repository manager path such as /api/npm/.
Some package types are parsed further, which is what lets the Virtual Registry tell a manifest from a package, extract the coordinates a firewall rule matches on, and rewrite the URLs a manifest hands back to the client:
oci, git, git-lfs, go, npm, pypi, maven, nuget, hex, helm, conda, cargo, conan, ansible, php, deb and github.
The rest are classified, counted and cached, but not parsed. Their requests are typed as other resources and cached on the general rules.
Go modules are served under /@v/ and /@latest, and the Virtual Registry also serves the Go checksum database at /sumdb/<name>/, forwarding to the database itself. A client that can reach nothing but the Virtual Registry can therefore still verify checksums, with GOPROXY as the only setting it needs. Only the two databases the go command ships with are proxied, sum.golang.org and sum.golang.google.cn. Any other name gets a 404.
Manifests are cached per tag and per digest, blobs by digest. A digest-addressed object is immutable and shared, and access to it is authorized per repository, so a client with access to one image is not served a layer belonging to an image it cannot reach.
The simple index is cached and filtered in both its HTML and its PEP 691 JSON form. The PEP 658 metadata sidecars (<file>.metadata) are cached too, which matters because a resolver fetches one per candidate wheel, more often than it downloads anything.
Every request also lands in one of three resource types, each with its own default lifetime and its own TTL setting to override it:
manifest: a mutable reference, such as an npm packument, a PyPI simple index, a maven-metadata.xml, or a tag-addressed OCI manifest. Revalidated on every request by default, with requests for the same manifest coalesced into one fetch, and kept for a week so a stale copy can be served while the origin is unreachable. manifest_ttl replaces the revalidation with a fixed window.package: the artifact itself, which is immutable. Cached for a hundred years, capped by package_ttl.other: everything else, including every recognised-but-unparsed ecosystem and unclassified traffic. No lifetime of its own unless other_ttl sets one, so the origin’s cache headers decide, falling back to default_ttl.Some requests deviate from their resource type’s default because the protocol makes them immutable whatever their type: a digest-pinned OCI manifest, a Go .info or .mod, a PyPI metadata sidecar. Those are cached for a hundred years and are unaffected by manifest_ttl.