Skip to main content

docker-devtools

Work on the Docker files in a repository: the build context a Dockerfile would send, and the image references it and your Compose files point at.

$ docker-devtools image-refs ls
Dockerfile:1        python:3.11-slim
compose.yaml:3      nginx:1.25-alpine

$ docker-devtools image-refs update --tag-policy same-pattern --dry-run
Dockerfile:1     python:3.11-slim -> python:3.14-slim      (tag 3.11-slim -> 3.14-slim)
compose.yaml:3   nginx:1.25-alpine -> nginx:1.31-alpine    (tag 1.25-alpine -> 1.31-alpine)

Why another one

Renovate and Dependabot already update image references, and they do it well. They run as bots against a repository and open pull requests. This one runs on your machine and edits the files in place. It is fast enough for a pre-commit hook, so a stale base image gets caught before it is ever committed.

Where the semantics are Docker's, this defers to Docker's own code:

Step Package
Parse Dockerfiles moby/buildkit/frontend/dockerfile/parser and instructions
Parse image references google/go-containerregistry/pkg/name
Talk to registries google/go-containerregistry/pkg/v1/remote
Match .dockerignore rules moby/patternmatcher
Walk a build context tonistiigi/fsutil, the package BuildKit sends contexts with

None of the .dockerignore semantics are reimplemented here, and CI checks that rather than asserting it: for every fixture, scripts/conformance.sh builds FROM scratch with COPY . /, exports the image as a tarball, and diffs the tar members against what build-context ls reports.

Install

$ uvx docker-devtools image-refs ls          # no install
$ pipx run docker-devtools image-refs ls     # no install
$ uv tool install docker-devtools
$ pip install docker-devtools
$ mise use ubi:FlavioAmurrioCS/docker-devtools

Prebuilt binaries are attached to each release. With a Go toolchain:

$ go install github.com/FlavioAmurrioCS/docker-devtools/cmd/docker-devtools@latest

Reading and updating files doesn't require a Docker installation or a running daemon. Registry lookups authenticate with the same ~/.docker/config.json the docker CLI uses.

The build context is what BuildKit would send, because docker build forwards to buildx by default. The legacy builder never learned <dockerfile>.dockerignore at all. Its ReadDockerignore opens only <context>/.dockerignore, so a DOCKER_BUILDKIT=0 build legitimately disagrees with this listing whenever a per-Dockerfile ignore file is in play.

As a pre-commit hook

repos:
  - repo: https://github.com/FlavioAmurrioCS/docker-devtools
    rev: v0.0.1
    hooks:
      - id: docker-image-check

The hooks are scoped to Dockerfile*, Containerfile* and (docker-)?compose*.ya?ml already:

Hook What it does
docker-image-check Fails when a tag could move. Writes nothing.
docker-image-update Moves tags in place, under same-pattern.
docker-image-pin Appends or refreshes the @sha256 digest.

Each one reaches the registry, so a hook is only as fast as the tag listing it asks for. docker-image-check is the one to reach for first: it reports without touching the tree.

pre-commit installs a repo: from source, and building this one compiles the Go binary, so the machine running the hook needs a Go toolchain. Somewhere that cannot have one, install the published wheel instead and point a local hook at the docker-devtools it puts on PATH.

Usage

docker-devtools build-context ls [PATH]      list the files Docker would send
docker-devtools image-refs ls [PATH...]      list every image reference, with file and line
docker-devtools image-refs update [PATH...]  rewrite references in place
docker-devtools registry tags REF            list a repository's tags, newest last
docker-devtools install-docker-plugin        register as "docker devtools"
docker-devtools version                      print the version (also --version)

Each group's ls is also the default, so image-refs Dockerfile and build-context . work without it. The cost is that a mistyped verb reads as a path: image-refs updte reports stat updte: no such file or directory.

ls is also spelled list. The groups are named away from context and image on purpose: docker context ls lists CLI endpoints and docker image ls lists local images, and neither is anything like what these do.

PATH is the build context directory for build-context ls, and files or directories to scan for image-refs. Every command takes --json, which is the same document the Python API parses.

build-context ls lists directories in their own right, the way docker build sends them, so a listing piped through -0 into xargs gets both. It also takes:

-f, --file PATH   Dockerfile to derive <path>.dockerignore from
    --ignored     list what the ignore file excluded instead
    --all         list everything, prefixed + for sent and - for excluded
    --size        prefix each path with its size in bytes
    --why         append the ignore-file rule that decided each path
    --target STAGE  build as if --target were given, changing what is reached
    --whole-context list everything the ignore rules permit
    --summary     print totals to stderr after the listing
-0, --zero        separate paths with NUL

What actually gets sent

BuildKit transfers only the paths the Dockerfile names. Every COPY and ADD source becomes a follow path on the build context, so a Dockerfile that copies one file transfers one file, however large the directory around it:

$ docker-devtools build-context ls --summary
taplo.toml
transferred: 1 file, 1.7 KiB
permitted:   24626 files, 458.5 MiB

The gap between those two lines is the point. permitted is what the ignore rules allow through, which is also what gets sent when something copies the context whole: COPY . / switches the filter off. --whole-context lists that set, and --target picks the stage, since a stage the build never reaches never reads its sources.

Which Dockerfile, and which .dockerignore

A context needs a Dockerfile. With no -f, Dockerfile is looked for and then the lowercase dockerfile, which is the whole candidate set BuildKit uses; there is no Containerfile fallback. When neither is there the command fails, because docker build would too, and a listing of a build that cannot run describes nothing.

A context with no .dockerignore at all says so, since that is the reason .git and a virtualenv turn up in the listing:

$ docker-devtools build-context ls
warning: no .dockerignore in .; every file is sent

-f takes a path, resolved from your working directory rather than from the context. That is the rule docker build -f follows, and the Dockerfile may sit outside the context entirely. The ignore file is the one beside the Dockerfile, <path>.dockerignore, falling back to <context>/.dockerignore. The first wins outright. They never merge.

$ docker-devtools build-context ls -f docker/build.Dockerfile ./app
  # reads docker/build.Dockerfile.dockerignore, else app/.dockerignore

--why says which rule decided each path, which is usually the question:

$ docker-devtools build-context ls --all --why
+ app.js
- node_modules/drop/index.js  <- .dockerignore:1 node_modules
+ node_modules/keep/index.js  <- .dockerignore:2 !node_modules/keep

Updating image references

What changes is split by how much judgement it needs.

--pin-digest resolves the current tag to a digest and appends it, turning nginx:1.29 into nginx:1.29@sha256:…. It doesn't decide anything about versions, so it is reversible and safe to run anywhere.

--tag-policy moves the tag. The default, same-pattern, moves only the last component and keeps the suffix, so how specific your tag is decides how far it may move:

Current tag same-pattern minor patch latest
3.12-slim 3.13-slim 3.13-slim 3.12.7-slim 4.0-slim
3.12.1-slim 3.12.7-slim 3.13.0-slim 3.12.7-slim 4.0-slim
latest no change no change no change no change

Only same-pattern keeps the shape of a tag. The other three compare version components, and a component the current tag omits counts as zero, so patch can turn 3.12-slim into 3.12.7-slim: a tag that pinned a minor line now pins a patch.

No policy ever changes the suffix: -alpine and -slim are different images, and swapping them would change your base distribution without saying so. Tags with no version, such as latest or bookworm, are never moved, because there is no ordering to move along.

Add --dry-run to see the plan without writing, and --fail-on-diff to exit non-zero when anything would change, which is what makes it useful in CI.

--fail-on-diff reports on the plan, not on the writing, so on its own it still rewrites the files and then exits non-zero. Pair it with --dry-run for a check that leaves the tree alone, which is what the docker-image-check hook does.

Base images behind an ARG

A Dockerfile that opens ARG BASE_IMAGE=debian:13-slim and then FROM "${BASE_IMAGE}" still has a real base image, and it is updatable. The FROM is expanded through the ARG defaults with BuildKit's own lexer, the same way docker build does it, and the reference is reported on the ARG line, because that is the only text an update can rewrite:

$ docker-devtools image-refs ls --unresolved
Dockerfile:1   debian:13-slim
Dockerfile:5   "${BASE_IMAGE}"   (resolved from ARG BASE_IMAGE on line 1)

This holds only when the ARG default is the whole reference, spelled out on its own line. ARG VERSION=12 with FROM debian:${VERSION}-slim stays unresolved: the image is debian:12-slim, which is written nowhere, and rewriting would mean splicing a bare tag into the middle of a line.

What it will not touch

Some references cannot be resolved to an image, and those are reported rather than guessed at. Pass --unresolved to image-refs ls to see them:

  • FROM builder, where builder is an earlier stage
  • COPY --from=0, which indexes a stage
  • FROM $BASE where the ARG has no default, or supplies only part of the reference
  • FROM scratch, which is the empty base rather than a registry image
  • Compose values built from variables, such as ${REGISTRY}/app:latest

A listing says how many it withheld, so a file whose every reference is one of these does not simply vanish from the output.

Editing in place

An update splices the new reference into the exact byte range the parser reported. It never re-encodes the file, so comments, quoting style, anchors and whitespace all survive:

    image: "nginx:1.29-alpine"   # keep this comment and the quotes

becomes

    image: "nginx:1.31-alpine"   # keep this comment and the quotes

If a byte range no longer holds the text the parse said it held, the update fails instead of writing. A rewrite that has drifted from the parse is a bug, and corrupting the file would hide it.

Listing tags

$ docker-devtools registry tags python:3.12-slim
  3.11-slim
* 3.12-slim
  3.13-slim
  3.14-slim

Given a tag, the listing keeps only tags sharing its suffix and marks the one you named, so it answers what that reference could move to. -alpine and -slim stay apart for the same reason no policy crosses between them. Pass --all for everything, and --json to script against.

The ordering is computed here, not taken from the registry. The OCI distribution spec requires the tags endpoint to return "lexical (i.e. case-insensitive alphanumeric order)" and carries no timestamps, which is the order that puts 3.10 before 3.9. There is no portable way to sort by publication date: reading one costs three requests per tag and is meaningless for reproducible builds, which set it to the epoch. --sort lexical hands the registry's own order back.

Credentials come from ~/.docker/config.json, including the credsStore and credHelpers entries that shell out to docker-credential-*, and from $DOCKER_CONFIG, $REGISTRY_AUTH_FILE and Podman's containers/auth.json. Behind all of those, ~/.netrc is consulted, or $NETRC when it is set.

Shell completion

The binary emits a usage spec describing its own command tree, and the usage CLI turns that into completions for bash, zsh, fish, powershell and nushell:

$ mise use usage
$ usage g completion zsh docker-devtools --usage-cmd 'docker-devtools --usage-spec' --install

The generated scripts call back to usage at completion time, so it has to stay on your PATH. mise run completions regenerates all five, plus a markdown reference, into build/.

As a Docker CLI plugin

$ docker-devtools install-docker-plugin
$ docker devtools image-refs ls

This symlinks the binary into ~/.docker/cli-plugins/, so upgrading the binary upgrades the plugin. Windows gets a copy instead, having no dependable unprivileged symlink. DOCKER_CONFIG moves the directory, and --system installs for every user.

The subcommand is devtools because Docker validates plugin names against ^[a-z][a-z0-9]*$ and refuses to load anything else. Python wheels cannot do this step at install time: they have no post-install hook, and ~/.docker/cli-plugins/ sits outside every Python install path.

Python API

The wheel bundles the binary and a typed wrapper.

from docker_devtools import image_ls
from docker_devtools import image_update

for ref in image_ls(".").resolved():
    print(f"{ref.path}:{ref.line}", ref.repository, ref.tag)

report = image_update(".", pin_digest=True, dry_run=True)
for change in report.changes:
    print(change.old, "->", change.new, f"({change.reason})")

image_update defaults to dry_run=True, so calling it by accident cannot rewrite a repository. The CLI defaults the other way, as a CLI should: image update writes unless you pass --dry-run.

The wrapper shells out to the bundled binary, which the wheel installs onto PATH. Where that directory isn't on PATH, python -m docker_devtools runs it anyway, and DOCKER_DEVTOOLS_BINARY points at a specific build.

Development

mise.toml defines the tools and the tasks.

$ mise run build         # compile into ./build
$ mise run test          # go test + pytest
$ mise run lint          # pre-commit across the repo
$ mise run prose         # vale-ai-tells across all markdown
$ mise run conformance   # diff context listing against real docker build
$ mise run completions   # regenerate completions and docs
$ mise run wheels        # every platform wheel into ./dist
$ mise run test-clone    # verify a fresh clone in a container

Registry behaviour is tested against go-containerregistry's in-process registry, so the suite doesn't touch the network or carry recorded fixtures.

License

MIT. See LICENSE.

src/docker_devtools/_find.py adapts the binary-discovery search order from uv, which is MIT OR Apache-2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

docker_devtools-0.1.0.tar.gz (56.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

docker_devtools-0.1.0-py3-none-win_arm64.whl (4.4 MB view details)

Uploaded Python 3Windows ARM64

docker_devtools-0.1.0-py3-none-win_amd64.whl (5.0 MB view details)

Uploaded Python 3Windows x86-64

docker_devtools-0.1.0-py3-none-musllinux_1_2_x86_64.whl (4.9 MB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

docker_devtools-0.1.0-py3-none-musllinux_1_2_aarch64.whl (4.4 MB view details)

Uploaded Python 3musllinux: musl 1.2+ ARM64

docker_devtools-0.1.0-py3-none-manylinux_2_17_x86_64.whl (4.9 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

docker_devtools-0.1.0-py3-none-manylinux_2_17_aarch64.whl (4.4 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

docker_devtools-0.1.0-py3-none-macosx_11_0_arm64.whl (4.5 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

docker_devtools-0.1.0-py3-none-macosx_10_9_x86_64.whl (4.9 MB view details)

Uploaded Python 3macOS 10.9+ x86-64

File details

Details for the file docker_devtools-0.1.0.tar.gz.

File metadata

  • Download URL: docker_devtools-0.1.0.tar.gz
  • Upload date:
  • Size: 56.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for docker_devtools-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9ecba0d518e53b5682e193408f421cb9c1e663ef604ff4972e3b5221254f7190
MD5 af544b7229391ab4984a671d968b6cea
BLAKE2b-256 0c9d55492fd95161faa67a1ee6e702afef771dda9665ff8cb44daa0fee9446f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0.tar.gz:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docker_devtools-0.1.0-py3-none-win_arm64.whl.

File metadata

File hashes

Hashes for docker_devtools-0.1.0-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 b35b32e88281d513ccbf90552b89c1d00118bfa2b61f1747a0c0e78dd7ecdc2a
MD5 9ce3de227e010056e6959cc498136a03
BLAKE2b-256 53ee3672eec11a3316cdc01ee5a4a5cd954d19e6b180ac602f125e8ddd7c1015

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0-py3-none-win_arm64.whl:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docker_devtools-0.1.0-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for docker_devtools-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 05cab82358ab78bf5dd48eeeab7638b46f80b315b5794a4610d86ec94b00a012
MD5 1431e6b86e9c71cad23cf80be0d68d00
BLAKE2b-256 67bdb655a624a0db89984a1915e880cae22cc0f72045ed38d6f4bd262cd4b79a

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0-py3-none-win_amd64.whl:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docker_devtools-0.1.0-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for docker_devtools-0.1.0-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 3f3cfbaa5dd5e06d884bf72ac9f842e0a8276f2e77c28d7ae95624f748344d12
MD5 3a5c0aac3ccca9d73718ce3959854feb
BLAKE2b-256 829dcd05bc4b8238a297b80c2ce369785e840ce79d1066e818567ced5ced36cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0-py3-none-musllinux_1_2_x86_64.whl:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docker_devtools-0.1.0-py3-none-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for docker_devtools-0.1.0-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 cbab019f49aa4d6d4b03ae30a3f8586485f789027f039e3b9bac4a084687311a
MD5 399d1142fafc5f1c4b102b0234d13f1b
BLAKE2b-256 2e6cdba88663a0843cd00777fc7753e20ab4cd977552b1e3447b1ba21037b7d8

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0-py3-none-musllinux_1_2_aarch64.whl:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docker_devtools-0.1.0-py3-none-manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for docker_devtools-0.1.0-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 9b40c34b194292c98380a282747b192e1fd3503c9036645ea4cc3bc1f0da5e24
MD5 179e99528cf60faabc0330b73956ebfd
BLAKE2b-256 120b865860aeb1ca0d15ae8603dbaf9864c0f66bc55c0be2e85db73ed070cdd5

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0-py3-none-manylinux_2_17_x86_64.whl:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docker_devtools-0.1.0-py3-none-manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for docker_devtools-0.1.0-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 62694660cf429423d7338145f135553029232b9249ad00c5f595d807af3c4a7d
MD5 82d43c761643ece9b68a59492b003477
BLAKE2b-256 fd40720a29d275d157ca8b43ee4e568c7882f7d702283f144a8d6442164f3319

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0-py3-none-manylinux_2_17_aarch64.whl:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docker_devtools-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for docker_devtools-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d7f6fea4528f5908392effbf786931596741d09a28a4a736b650b8b82dce7bcf
MD5 1fabfce85a39aa2551fd6d99c6b78259
BLAKE2b-256 775e63d578de1f57857fa9f6d145b087b27d857036867603927e868d4e837610

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0-py3-none-macosx_11_0_arm64.whl:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file docker_devtools-0.1.0-py3-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for docker_devtools-0.1.0-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 1cabe05838a281723ae5bc3d10880e59a052d043f74ba871e7070e2830f44137
MD5 385b49bd42d1651143ea1ef6c0259f10
BLAKE2b-256 9ddf6a83954d2b39e0bcdf51328dc2fbfe0cb97cae88fc2adf88553a3b6e0856

See more details on using hashes here.

Provenance

The following attestation bundles were made for docker_devtools-0.1.0-py3-none-macosx_10_9_x86_64.whl:

Publisher: main.yaml on FlavioAmurrioCS/docker-devtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

9 files

0.0.1

9 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page