NAME

API::Docker::API::Distribution - Docker Engine Distribution API

VERSION

version 0.004

SYNOPSIS

my $docker = API::Docker->new;

# Ask a registry about an image reference without pulling it
my $descriptor = $docker->distribution->inspect('nginx:latest');

# With registry credentials
my $descriptor = $docker->distribution->inspect('private/app:1.0',
    auth => {
        username => 'someone',
        password => 'secret',
    },
);

# The same question as a predicate: is that tag already published?
if ($docker->distribution->exists('myrepo/app:1.0', auth => $auth)) {
    die "refusing to overwrite a released tag";
}

DESCRIPTION

This module provides access to the Docker distribution endpoint (GET /distribution/{name}/json), which asks a registry for the manifest descriptor of an image reference without pulling the image.

Accessed via $docker->distribution, or through "using" in API::Docker::Role::Using for a run of calls that needs its own transport bound: $docker->distribution->using(read_timeout => 5).

The reference goes into the path unescaped, so its slashes and its tag stay readable on the wire (/distribution/myrepo/app:1.0/json) -- that is what the engine parses, and percent-encoding them breaks the reference.

A 404 means two different things

The endpoint answers 404 both when the registry does not have the reference and when the engine has no such route, and the two want opposite handling. The split here is:

  • "inspect" is the endpoint, and croaks on any error status, 404 included, the way every other method in this distribution does.

  • "exists" is the question, and answers it: true, false, or a croak when the engine could not ask the registry at all.

"exists" exists because answering "no" to everything is the failure this class was added to remove -- see the Podman note below -- and a predicate that cannot fail loudly would have reintroduced it one layer up.

Not available on Podman

Measured against the rootless Podman socket (5.4.2, API 1.41): GET /v1.41/distribution/nginx:latest/json answers 404 Not Found with {"cause":"","message":"Path /v1.41/distribution/nginx:latest/json is not supported","response":0} (the 1.41 there is this client's negotiated API version, echoed back from the request path -- it moves with negotiation, not a fixed string), and so does every other reference, escaped or not -- the compat layer has no route for this endpoint. This class therefore needs a real Docker daemon.

That 404 is exactly the one a naive predicate would read as "the registry does not have it", which is why "exists" tells the engine's own no-such-route answer apart and croaks on it instead.

What this class returns

"inspect" returns the decoded engine response -- a HashRef with Descriptor and Platforms -- not an entity object, deviating from the inspect convention the other resource classes follow, because there is no API::Docker::Distribution entity class to wrap it in.

client

Reference to API::Docker client. Weak reference to avoid circular dependencies.

inspect

my $descriptor = $distribution->inspect('nginx:latest');
my $descriptor = $distribution->inspect('private/app:1.0', auth => $auth);

Ask the registry for the manifest descriptor of an image reference. The daemon performs the lookup; nothing is pulled and no local image is touched.

Returns a HashRef with Descriptor -- MediaType, digest, size, URLs -- and Platforms, the list of { Architecture, OS, ... } the reference resolves to.

A missing reference croaks. This method is the endpoint, so it inherits the transport's rule that any status at or above 400 is an error, and the registry's "no such reference" is a 404 like any other. Use "exists" for the predicate, or eval and read the status:

my %res;
my $d = eval { $distribution->inspect($ref, response => \%res) };
# $res{status} == 404 here means the registry said no *or* the engine
# has no such route -- see L</exists>, which separates the two.

Options:

  • auth - Registry credentials, in any shape "push" in API::Docker::API::Images accepts them: a HashRef of username / password / serveraddress / identitytoken, or a pre-encoded base64 string. Sent as X-Registry-Auth. Unlike push, which always sends the header, it is omitted entirely without this option -- the lookup is then anonymous, which is what a public image needs

  • response - HashRef the status line and the response headers are written into, as for "get" in API::Docker::Role::HTTP

exists

if ($distribution->exists('myrepo/app:1.0', auth => $auth)) { ... }

Whether the registry has that image reference. Returns a true value when the lookup succeeded, a false one when the registry answered 404, and croaks otherwise -- including when the engine has no /distribution route, so that an engine which cannot answer the question never answers it with "no".

Takes the same options as "inspect". Callable without an eval: every outcome it returns is an answer from the registry, and everything else is loud.

The distinction rests on the engine's error message, which is the only thing that separates the two 404s -- is not supported from Podman, page not found from Docker's own router. A wording neither recognises is read as the registry's answer, i.e. as false, which is the behaviour a plain "404 means no" would have had anyway.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-api-docker/issues.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.