API-Docker

 view release on metacpan or  search on metacpan

lib/API/Docker/API/Images.pm  view on Meta::CPAN

Tag an image with a new repository and/or tag name.

=head2 remove

    $images->remove('nginx:latest', force => 1);

Remove an image.

Options:

=over

=item * C<force> - Force removal

=item * C<noprune> - Do not delete untagged parents

=back

=head2 search

    my $results = $images->search('nginx', limit => 25);

Search Docker Hub for images. Returns ArrayRef of search results.

Options:

=over

=item * C<limit> - Maximum number of results

=item * C<filters> - HashRef of filter name to ArrayRef of string values; the
engine accepts C<is-official>, C<is-automated> and C<stars> here. The boolean
ones want the string, C<< { 'is-official' => ['true'] } >>, and C<stars> a
number written as one -- L<API::Docker::Role::Filters> takes care of both and
croaks on a shape the daemon would refuse

=back

=head2 prune

    my $result = $images->prune(filters => { dangling => ['true'] });

Delete unused images. Returns hashref with C<ImagesDeleted> and C<SpaceReclaimed>.

Options:

=over

=item * C<filters> - HashRef of filter name to ArrayRef of string values; the
engine accepts C<dangling>, C<until> and C<label> here. Shape-checked and
normalised by L<API::Docker::Role::Filters>

=back

=head2 get

    use Path::Tiny;
    my $tar = $images->get('alpine:3');
    path('alpine.tar')->spew_raw($tar);

Export one image, and the history behind it, as a tar archive -- the endpoint
behind C<docker image save>. Together with L</load> it is the only way in or
out of a daemon that does not go through a registry.

B<The return value is raw bytes, not a decoded structure.> The engine answers
with the tar stream itself, and the transport is told to hand it back
untouched (C<< raw => 1 >>), so what arrives is byte for byte what the daemon
wrote. Write it with a binary-safe file handle -- C<< path(...)->spew_raw >>,
or C<binmode> on a handle of your own. Treating it as text corrupts it, and
nothing about the value announces that it is binary.

The archive holds one tarball per layer, a config JSON per image,
C<manifest.json> and C<repositories>. Measured against Podman 5.4.2 (API
1.41): the response is chunked with
C<< Content-Type: application/octet; charset=us-ascii >>, where Docker sends
C<application/x-tar> -- the transport looks at neither, so the difference does
not reach the caller. Exporting C<alpine:3> through this method produced bytes
md5-identical to what C<curl --unix-socket> wrote for the same request, all
8705536 of them, so the chunked reader is binary-clean.

An unknown image croaks. On the same engine that is C<404 Not Found> with
C<< {"message":"failed to find image ...: image not known"} >>.

=head2 Exporting without buffering the archive

The whole archive is buffered in memory before it is returned, so exporting a
large image costs its full size in RAM. Pass C<on_chunk> and the bytes are
handed over as they arrive instead, and nothing is kept:

    use Path::Tiny;
    my $out = path('alpine.tar')->openw_raw;
    my $summary = $images->get('alpine:3',
        on_chunk => sub { print {$out} $_[0] });
    close $out;

    $summary;   # { delivered => 266, stopped => 0 }

The units are whatever the transport read, not a fixed size: a chunk boundary
carries no meaning in a tar stream, and the only guarantee is that
concatenating them in order gives the same bytes the buffered call returns.
Write them to a binary-safe handle, exactly as for the buffered value.

Measured against Podman 5.4.2 (API 1.41): exporting C<alpine:3> this way
delivered its 8705536 bytes in 266 pieces, md5-identical to what the buffered
call returns for the same request, with no more than one piece held at a time.

With a callback the return value is the summary HashRef
C<< { delivered => N, stopped => 0|1 } >>, not the archive: C<delivered> is
how many pieces went to the callback, C<stopped> is 1 when the callback ended
the transfer. Stopping leaves a B<truncated> archive behind -- the export is
one tar stream, not a sequence of independent records -- so stop only to
abandon it. See
L<API::Docker::Role::HTTP/"Streaming a response as it arrives">.

Options:

=over

=item * C<on_chunk> - CodeRef called with each piece of the archive as it
arrives, instead of the whole thing being returned

=back

=head2 get_all

    my $tar = $images->get_all('alpine:3', 'registry:2');
    my $tar = $images->get_all([ 'alpine:3', 'registry:2' ]);

Export several images into one tar archive. Takes the names as a list or as a
single ArrayRef; at least one is required.

B<The return value is raw bytes>, exactly as for L</get> -- see there for what
that means for writing it out.

C<manifest.json> inside the archive carries one entry per image, so a single
tar can be carried to another host and loaded in one L</load> call. Measured
against Podman 5.4.2 (API 1.41): asking for no names at all answers
C<400 Bad Request> with C<< {"message":"no images to download"} >>.

C<on_chunk> works here exactly as it does for L</get> -- several images make a
bigger archive, so this is where not buffering it matters most -- but it can
only be passed with the ArrayRef form:

    my $summary = $images->get_all([ 'alpine:3', 'registry:2' ],
        on_chunk => sub { print {$out} $_[0] });

The list form takes names and nothing else: a trailing option pair in it would
be indistinguishable from two more image names. Options after the ArrayRef
must come in pairs; an odd number croaks.

A transport bound is not one of those options: it goes on the resource class,
which works with either form -- C<< $docker->images->using(read_timeout => 5)
->get_all('alpine:3') >>, see L<API::Docker::Role::Using>.

Measured against Podman 5.4.2 (API 1.41): C<alpine:3> and C<registry:2>
together came to 34725888 bytes in 1060 pieces, none of which had to be held.

=head2 load

    use Path::Tiny;
    my $events = $images->load(path('alpine.tar')->slurp_raw);

    for my $event (@$events) {
        print $event->{stream} if defined $event->{stream};
    }

Import a tar archive produced by L</get> or L</get_all> -- the endpoint behind
C<docker image load>. The archive is the request body; pass it as raw bytes or
as a scalar reference to them, the way L</build> takes its context.

Returns an ArrayRef of progress events, one per object in the engine's
newline-delimited JSON stream, even when the stream carried a single object.
The last of them names what was imported:

    my ($loaded) = grep { ($_->{stream} // '') =~ /^Loaded image/ } @$events;

Options:

=over

=item * C<quiet> - Suppress the per-layer progress detail in the response
stream

=item * C<on_event> - CodeRef called with each progress event as it arrives,
instead of the ArrayRef being collected and returned. The return value is then
the summary HashRef and a stream failure croaks one event in, exactly as for
L</build>; see L</"Progress as it arrives">

=back

C<quiet> changes how much the engine says, not what this method returns: the
body stays newline-delimited JSON and the return stays an ArrayRef either way.
B<Podman ignores it entirely> -- measured against 5.4.2 (API 1.41), C<quiet>
unset, C<0> and C<1> all produce the identical single
C<< {"stream":"Loaded image: ..."} >> object. Should an engine answer a quiet
load with a body of no bytes at all, the transport still returns C<[]>, not
C<undef> -- the C<ndjson> branch in L<API::Docker::Role::HTTP/_request> runs
before the empty-body check that would return C<undef>, so a caller that
iterates the result unconditionally needs no guard for this case.

A failed load croaks, but by which route depends on the engine, the same split
L</pull> and L</push> have. Docker reports it as an C<errorDetail> object
inside a 200 stream, which croaks with an L<API::Docker::Error::Stream>
carrying the events. Podman reports it in the status line instead: measured
against 5.4.2, a body that is not an image archive answers C<500 Internal
Server Error> with C<< {"message":"failed to load image: payload does not
match any of the supported image formats: ..."} >>, and the transport's status
handling croaks with an L<API::Docker::Error::HTTP> -- which is that same
string to anything inspecting C<$@> as text -- before any stream is decoded.
Inspect C<$@> as a string rather than testing for the exception class.

The archive is sent as one buffered request body, so loading a large image
costs its full size in RAM.

=head2 commit

    my $result = $images->commit(
        container => $container_id,
        repo      => 'myapp',
        tag       => 'snapshot',
        comment   => 'after the migration ran',
    );
    my $image_id = $result->{Id};

    # With a config override and Dockerfile instructions
    $images->commit(
        container => $container_id,

lib/API/Docker/API/Images.pm  view on Meta::CPAN

        tag       => 'v2',
        config    => { Cmd => [ '/bin/sh' ], Labels => { built => 'here' } },
        changes   => [ 'EXPOSE 8080', 'LABEL stage=release' ],
    );

Create an image from a container's current filesystem. This is the one
image-producing path that does not go through a build context, and it is how a
caller snapshots a container it has been exec-ing into.

Returns the raw daemon response, a HashRef with an C<Id> key. Measured against
Podman 5.4.2 (API 1.41) the status is C<201 Created> and C<Id> is a bare hex
digest with no C<sha256:> prefix; Docker prefixes it. Do not compare it
literally against an id from C<inspect> without normalising.

Options:

=over

=item * C<container> - Container id or name to commit (required)

=item * C<repo> - Repository for the new image, e.g. C<myapp>

=item * C<tag> - Tag for the new image

=item * C<comment> - Commit message stored in the image history

=item * C<author> - Author, e.g. C<< Jane <jane@example.com> >>

=item * C<pause> - Pause the container while committing (engine default is true)

=item * C<changes> - Dockerfile instructions to apply to the new image, as a
single string or an ArrayRef of them; an ArrayRef is joined with newlines,
which is what the engine's parser expects

=item * C<config> - HashRef of container configuration to override on the new
image (C<Cmd>, C<Env>, C<Labels>, C<ExposedPorts>, ...), sent as the request
body. Measured against Podman 5.4.2: C<Cmd> replaces the container's, C<Env>
is merged onto the environment the container inherited, and a C<Labels> here
lands alongside a C<LABEL> given in C<changes> -- the two are applied
together, not one instead of the other

=back

=head2 build_prune

    my $result = $images->build_prune(all => 1);
    my $freed  = $result->{SpaceReclaimed};

    # Keep 5 GB of cache
    $images->build_prune(keep_storage => 5 * 1024 * 1024 * 1024);

Clear the BuildKit build cache. B<This is not L</prune>>, and the two are not
interchangeable: L</prune> deletes unused I<images>, this deletes the
intermediate I<build cache> that L</build> writes. Neither touches the other's
storage, and on a machine that builds often the build cache is usually the
larger of the two.

Returns the raw daemon response, a HashRef with C<CachesDeleted> and
C<SpaceReclaimed>.

B<Podman does not implement this endpoint.> Measured against 5.4.2 (API 1.41):
C<POST /build/prune> answers C<404 Not Found> with a C<text/plain> body of
C<Not Found> -- not the JSON C<< {"message":...} >> shape its other errors use
-- at every version prefix tried, and there is no C<libpod> equivalent either.
The transport croaks with C<Docker API error (404): Not Found>, the plain body
verbatim, because it is not JSON to unwrap. A caller that must work on both
engines has to treat that 404 as "no build cache to clear here" rather than as
a transport fault.

Options:

=over

=item * C<keep_storage> - Bytes of cache to keep. Sent as the engine's
C<keep-storage>, which is also accepted as the option name; the underscore
form exists because the hyphenated one has to be quoted in a Perl hash

=item * C<all> - Remove all cache, not just the dangling entries

=item * C<filters> - HashRef of filters, e.g. C<< { until => ['24h'] } >>;
values are ArrayRefs of strings, shape-checked and normalised by
L<API::Docker::Role::Filters>, and passed to the transport unencoded because
it JSON-encodes a HashRef params value itself

=back

=head1 SEE ALSO

=over

=item * L<API::Docker> - Main Docker client

=item * L<API::Docker::Role::Entity::Image> - the convenience methods the
returned objects carry

=item * L<API::Docker::Type::ImageSummary> - the fields C<list> returns

=item * L<API::Docker::Type::ImageInspect> - the fields C<inspect> returns

=item * L<API::Docker::Role::RegistryAuth> - the C<X-Registry-Auth>
encoding C<push> uses, shared with the other registry-facing endpoints

=item * L<API::Docker::Error::Stream> - Raised by C<build>, C<pull>, C<push>
and C<load>

=back

=head1 SUPPORT

=head2 Issues

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

=head1 CONTRIBUTING

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

=head1 AUTHOR

Torsten Raudssus <getty@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> L<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.

=cut



( run in 1.971 second using v1.01-cache-2.11-cpan-f9ab5d97e31 )