NAME
Dist::Zilla::Plugin::Docker::API - Build and publish Docker images as Dist::Zilla release artifacts
VERSION
version 0.105
SYNOPSIS
[Docker::API]
image = ghcr.io/example/my-app
tag = latest
tag = %V
tag = %v
dockerfile = Dockerfile
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, no push | | dzil release | Re-tag the built image with every tag, push (if release_push=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, which walks an ordered list of candidate auth files rather than reading a single one. For the registry the image reference names, the first candidate file with a usable entry for it wins; a file with none falls through to the next, so a Docker config and a Podman auth file can each supply credentials for different registries in the same run. In order: REGISTRY_AUTH_FILE, if set, names the file directly; then config.json in the directory named by DOCKER_CONFIG, if set; then $XDG_RUNTIME_DIR/containers/auth.json, if XDG_RUNTIME_DIR is set; then containers/auth.json under XDG_CONFIG_HOME (or under ~/.config when that is unset); and finally ~/.docker/config.json. ~/.dockercfg, Docker's legacy pre-config.json format, is never read.
All five files share the same auths block, so once a file is chosen the selection within the matching registry's entry does not depend on which one supplied it. 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.
A plain docker login or podman login is now picked up with no extra configuration: Podman's own default auth file is candidate three above. If podman login --authfile PATH was used to write somewhere else, set REGISTRY_AUTH_FILE=PATH in the environment dzil runs in so candidate one finds it.
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-apptag- 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: settingtagexplicitly replaces the default list, it does not append to it.dockerfile- Dockerfile name (default:Dockerfile)release_push- Push to registry during release (default: true)build_verbose- When false (default), the build log only echoes Dockerfile step headers — the legacy builder formatStep N/M : ..., Podman's classic builderSTEP 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 whenrelease_pushis also true. It asks the registry, not the local daemon, throughAPI::Docker'sdistribution->exists(GET /distribution/{name}/json), using the credential resolved forimage(see "Registry credentials"), or an anonymous request when none applies.-
An engine that has no
/distributionroute -- 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 thatfail_if_tag_exists = 0releases without the check. skip_latest_on_trial- Skiplatesttag for trial releasesbuild_arg- Build arguments (can be repeated, template-enabled)label- OCI labels (can be repeated, template-enabled)platform- Target platform, exactly one (e.g.linux/arm64). The key is repeatable only so that a second value is refused with a clear error: the /build endpoint takes one platform, and this plugin is not built for multi-arch images.
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- Usedockerfileinstead.-
Until 0.104 this was the only spelling that worked: the attribute carried
init_arg => 'file'while the documentation describeddockerfile, sodockerfile = ...in a dist.ini was silently ignored and the default Dockerfile used instead.dockerfileis now the canonical key. build_tag,release_tag-
Replaced by the single
tagattribute. When either is given, the values are merged (build_tag first, release_tag second) intotagand a deprecation warning is emitted. Iftagis also set explicitly, it wins and the legacy values are ignored. repository- Useimageinstead.push- Userelease_pushinstead.
Note that repository and push 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.
REMOVED
These keys are no longer supported. Each is warned about and discarded; none has a replacement.
phase- The build and release phases are implicit.build_load,release_load,load- The image is built through the engine API, where it lands in the engine's image store regardless, so there is nothing left to switch.
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 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.