Pin or override the toolbox image

The kvm, proxmox, and ec2 backends provision through the toolbox container — Terraform, Task, the libvirt client, and the pinned Go toolchain all live there so your host needs only Docker. (The vz backend runs limactl on the host and uses no toolbox at all, so none of this applies to it.)

Which image that container runs is decided by the ztd binary, not by you editing a compose file.

The rule: released binaries pull, source builds build locally

Your ztdImageWhat Docker does
a release, e.g. 0.4.0registry.gitlab.com/frob/ztd/ztd-toolbox:0.4.0pulls it once, then reuses it
a source/dev build (dev, …-snapshot)ztd-toolbox:localbuilds it from docker/Dockerfile when absent

The pull policy is missing in both cases. That single policy covers both because Compose resolves “not in the local cache” per image: a registry-qualified tag is pulled, while ztd-toolbox:local exists in no registry and so falls through to the build: stanza.

The published tag is the binary’s own version, so an installed ztd and its toolbox can never drift apart: upgrading ztd moves it to a matching image, and an old binary keeps pointing at the image it was released with. CI publishes the image from the same git tag that stamps the binary.

missing (not always) is the pull policy for a release, so once the image has been fetched a released ztd keeps working offline.

Check what your binary resolved:

ztd config
...
toolbox_image = registry.gitlab.com/frob/ztd/ztd-toolbox:0.4.0
toolbox_pull_policy = missing

Override it

Two environment variables, both honored on any build:

# Run a mirror, an air-gapped copy, or your own rebuild.
export ZTD_TOOLBOX_IMAGE=registry.internal.example/ztd-toolbox:0.4.0

# Force a local build of docker/Dockerfile, even on a released binary
# (no registry access, or you're testing a toolbox change).
export ZTD_TOOLBOX_PULL_POLICY=build

Setting ZTD_TOOLBOX_IMAGE alone also switches the policy to missing — a local build would otherwise overwrite the tag you just asked for. Set ZTD_TOOLBOX_PULL_POLICY explicitly when you want the other behavior; it always wins.

Both show up in ztd config, so an override is never invisible.

Private registries

ztd does not handle registry credentials — it only names the image. Log in with Docker first and the pull works as usual:

docker login registry.internal.example

Building the toolbox yourself

Contributors need nothing special: a source build is a dev build, so docker/Dockerfile is rebuilt on every run and your edits take effect immediately. To force a rebuild after changing the Dockerfile on a released binary, use ZTD_TOOLBOX_PULL_POLICY=build, or build and tag it by hand:

docker build -t "$(ztd config | awk -F' = ' '/^toolbox_image/{print $2}')" ./docker

docker compose run reuses a cached image and will not rebuild on its own when only the Dockerfile changed under a non-build policy — that’s exactly what the policy controls.