Mirroring
peryx mirror pulls each configured image's manifest and referenced blobs. A manifest list expands into its platform
manifests.
[index.prefetch]
images = ["library/alpine:latest", "library/nginx:1.27"]
packages remains an accepted alias for deployments created before the ecosystem-owned config split. New config should
use images.
Command-line overrides are TOML values interpreted by the OCI implementation:
peryx mirror plan root/oci --option 'images=["library/alpine:latest"]'
peryx mirror sync root/oci --option 'images=["library/alpine:latest","library/nginx:1.27"]'
peryx mirror verify root/oci --option 'images=["library/alpine:latest"]'
At least one configured or command-line image is required. Repository rewriting follows the index's
[index.settings].library_prefix.
Manifest validation
Every manifest is checked against the schema its media type declares before it is stored. An image manifest needs
schemaVersion: 2, a config descriptor, and a layers array; an index needs schemaVersion: 2 and a manifests array.
Either array may be empty, so an artifact manifest with no layers is accepted. A media type peryx models no schema for
is stored as it came and names no dependencies.
A body that breaks a rule is reported as an error row naming the rule, and neither the manifest nor its tag is cached.
verify applies the same check to what the store already holds, so a run never calls an image complete when its layers
could not be read.
Failure handling
Anything the upstream governs is one error row naming the repository, the reference, and the reason, and the run carries
on: an image the registry does not have, a registry that refuses or cannot be reached, a manifest above the
four-megabyte ceiling, a connection that stops part way through a body, layer bytes that hash to something other than
the digest the manifest named. The references selected after it and the siblings scheduled beside it are independent
work and are mirrored regardless. A fault on peryx's own side is not the same thing. When the metadata store or the blob
store fails, every later synced and cached row would be a claim nothing has checked, so the run ends there and
reports the fault instead of finishing a report it cannot stand behind.
Both kinds reach the same call site while a layer is written, so the split is read off the failure rather than off where it was raised. A stream that broke, and bytes that hash to something other than the digest the manifest named, describe what the registry sent and leave the store intact. Every other fault the store reports is peryx's own, and a run that meets one publishes no report at all.
The closing summary row states the verdict in its status column: synced when every selected reference is mirrored,
partial when some are and some failed, error when none are. Its reason carries the counts.
sync and verify exit non-zero whenever anything failed, a partial run included, so a zero exit always means the
mirror holds everything the run selected. That does cost a pipeline a failure when one blob out of many is missing,
which is the trade peryx takes: a mirror is asked for offline completeness, and a run that dropped content while exiting
zero would surface as an unexplained pull failure long afterwards. Read the error rows to decide what to retry, since
each names its own reference.
Concurrency
A run overlaps the work one level of the manifest graph makes available rather than waiting out each descriptor in turn:
the platform manifests an index names move together, and so do the config and layers of an image manifest. The ceiling
is the index's upstream_concurrency, or three while the index is uncapped, which
is what containerd pulls at once by default. Selected images share that one budget, so one image with many layers costs
what many images with one layer cost.
The next level is scheduled only after every manifest above it has been parsed, which is what keeps the graph deduplicated and the node and depth bounds exact: a digest two parents name is fetched once, and a graph over the bounds still stops before the fetch. A layer two manifests share is transferred once and reported cached under the second. An image and each sibling carries its own rows, so the report reads in selection then descriptor order however the transfers finish.
Repository scope
Manifest bytes are content-addressed, so two repositories mirroring the same image share one copy. The right to serve
them is recorded per repository. peryx answers a pull by digest only under a repository the manifest was recorded for,
and verify reads by the same rule. A digest another repository cached reports
manifest not mirrored for this repository, including a child manifest reached from an index, so a run never calls an
image ready for offline use that a pull rejects.
Mirroring the image under the second repository with sync records that membership and reuses the manifest and blobs
already stored, so nothing downloads twice.