NAME

Dist::Zilla::Plugin::Docker::API - Build and publish Docker images as Dist::Zilla release artifacts

VERSION

version 0.104

SYNOPSIS

[Docker::API]
image = ghcr.io/example/my-app

tag = latest
tag = %V
tag = %v

dockerfile = Dockerfile

build_load   = 1
release_push = 1

Or via the @Author::GETTY bundle:

[@Author::GETTY::Docker / runtime]
image = ghcr.io/example/my-app
tags  = latest %V %v

DESCRIPTION

This plugin builds and publishes Docker images as release artifacts derived from the Dist::Zilla-built distribution.

BEHAVIOR

| Dzil command | Docker behavior | |---|---| | dzil build | Build image, apply every tag, load into daemon (if build_load=1), no push | | dzil release | Re-tag the built image with every tag, push (if release_push=1), load (if release_load=1) |

The same tag list is used in both phases — dzil build produces local tags for verification, dzil release re-applies them (against the already-built image) and pushes if configured.

CONTAINER ENGINE

Builds and pushes go through API::Docker, which speaks the Docker Engine HTTP API over a socket. No docker binary is involved at any point, so any engine serving that API will do, and Docker itself need not be installed. Podman's rootless socket is a tested alternative:

systemctl --user enable --now podman.socket
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"

target reaches the engine unchanged, so the multi-stage builds this plugin is usually pointed at behave the same either way.

The socket is located from DOCKER_HOST, falling back to /var/run/docker.sock and nothing else. Docker contexts are not consulted, so a daemon selected with docker context use will not be picked up here; set DOCKER_HOST in the environment dzil runs in. See "CONTAINER ENGINES" in API::Docker for how that compares to other clients.

Startup precheck

Because after_build builds an image unconditionally, every dzil command that builds needs a reachable engine. The plugin therefore asks the engine for its version in before_build, before Dist::Zilla gathers a single file, and gives up there if nothing answers -- rather than letting a whole distribution be assembled and only then dying on a socket that was never there.

On success the engine is named in the build log:

[Docker::API] Docker::API engine ready: Podman Engine 5.4.2 (API 1.41)

Set DZIL_DOCKER_API_SKIP_PRECHECK=1 to skip the check and get the previous behaviour back, where an unreachable engine only surfaces once the build reaches the image. With several Docker::API plugins in one dist.ini, each runs its own precheck.

When before_build runs as part of dzil release (Dist::Zilla sets DZIL_RELEASING before it calls build_archive, which is early enough to tell a release from a plain build) and both release_enabled and release_push are true, the same hook also pre-flights the registry credential the eventual push would use -- resolved as described in "Registry credentials" below -- and hands it to the engine's POST /auth (system->auth) before anything is built. A plain dzil build never triggers this and needs no registry credentials at all. No credential resolved for the registry is not a failure -- an anonymous push is a legal thing to attempt, so nothing is checked and nothing fails. A failed check is fatal, before the build starts, and its message says only that the check failed, not that the credential was rejected: Podman answers a rejected credential and an unreachable registry with the same 500, so the two cannot be told apart from the status alone, and the engine's own text is included instead.

DZIL_DOCKER_API_SKIP_PRECHECK=1 skips this credential check along with the engine version probe above.

Set DZIL_DOCKER_API_SKIP=1 to skip the image build entirely for one run -- no engine contact, no image, one loud log line per plugin. This is for local dzil build / dzil install / dzil test while the image cannot build yet, for example while a dependency pinned in the Dockerfile's cpanm run is not released. dzil release refuses to run with the variable set: a skipped build phase means there is no image to tag and push.

Registry credentials

The release push, the fail_if_tag_exists lookup and the registry credential precheck above all resolve a credential for an image reference the same way, through auth_for_image_ref: the auths block of config.json in the directory named by DOCKER_CONFIG, or ~/.docker/config.json when that is unset. Nothing else is read -- REGISTRY_AUTH_FILE and Podman's own $XDG_RUNTIME_DIR/containers/auth.json are not consulted, regardless of which engine is at the other end of DOCKER_HOST.

Within the matching registry's entry, the first of these present wins: an identitytoken; a base64 auth field decoded to username:password; plain username / password fields. Docker Hub is matched under any of https://index.docker.io/v1/ and v2/, index.docker.io or docker.io. A credsStore or credHelpers entry that delegates the secret to an external helper is not supported -- nothing in this plugin reads either key, so such a registry resolves to no credential and any request against it goes out anonymous rather than failing.

docker login is the usual way to populate the file. podman login writes to its own auth file instead ($XDG_RUNTIME_DIR/containers/auth.json by default, overridable with REGISTRY_AUTH_FILE), which this plugin never reads; point it at the file that is read instead:

podman login --authfile ~/.docker/config.json registry.example.com

Finding no credential for a registry is never an error by itself in this plugin -- both fail_if_tag_exists and the release push treat it as "go anonymous," and only a credential that auth_for_image_ref did find and the engine then rejects (or an unreachable registry -- see above) is fatal.

CONFIGURATION

image - Full image repository (required). Example: ghcr.io/user/my-app
tag - Tags applied to the image (can be repeated, template-enabled). Default: latest, %V, and %v (e.g. latest, 0, 0.402). Applied identically in both build and release. Note: setting tag explicitly replaces the default list, it does not append to it.
dockerfile - Dockerfile name (default: Dockerfile)
build_load - Load built image into local Docker daemon (default: true)
release_push - Push to registry during release (default: true)
release_load - Load released image locally (default: false)
build_verbose - When false (default), the build log only echoes Dockerfile step headers — the legacy builder format Step N/M : ..., Podman's classic builder STEP N/M: ... and BuildKit's #N [N/M] ... — instead of the full per-command output. Set to true to see every line the daemon streams back. Errors are always surfaced regardless of this flag.
fail_if_tag_exists - Abort the release if any tag already exists on the remote registry (default: false). The check runs before anything is tagged or pushed, and only when release_push is also true. It asks the registry, not the local daemon, through API::Docker's distribution->exists (GET /distribution/{name}/json), using the credential resolved for image (see "Registry credentials"), or an anonymous request when none applies.

An engine that has no /distribution route -- rootless Podman among them -- cannot answer the question at all, and that is treated as a release-stopping failure rather than as "the tag is free": the release aborts with the engine's own error and a reminder that fail_if_tag_exists = 0 releases without the check.

skip_latest_on_trial - Skip latest tag for trial releases
build_arg - Build arguments (can be repeated, template-enabled)
label - OCI labels (can be repeated, template-enabled)
platform - Target platform (can be repeated)

DEPRECATED

The following names are still accepted but emit a warning and will be removed in a future release. Each is funneled into its canonical attribute by BUILDARGS; where both spellings are given, the canonical one wins and the collision is reported.

file - Use dockerfile instead.

Until 0.104 this was the only spelling that worked: the attribute carried init_arg => 'file' while the documentation described dockerfile, so dockerfile = ... in a dist.ini was silently ignored and the default Dockerfile used instead. dockerfile is now the canonical key.

build_tag, release_tag

Replaced by the single tag attribute. When either is given, the values are merged (build_tag first, release_tag second) into tag and a deprecation warning is emitted. If tag is also set explicitly, it wins and the legacy values are ignored.

repository - Use image instead.
push - Use release_push instead.
load - Use build_load instead.
phase - No longer needed; build and release phases are implicit. It is accepted, warned about and discarded; it has no canonical counterpart.

Note that repository, push and load did not work as aliases before 0.104: they were declared as readers taking their value from the canonical attribute, so setting one in a dist.ini had no effect whatsoever. They are funneled properly now.

SEE ALSO

Dist::Zilla::Plugin::Docker::API::TagTemplate, Dist::Zilla::Plugin::Docker::API::Client, Dist::Zilla::Plugin::Docker::API::Result

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-dist-zilla-plugin-docker-api/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.