kindest/node images: picking the version for your kind release
A kindest/node image belongs to the kind release that built it. Where to find its digest, how to pin it, and a CI matrix that follows kind's releases.
A kindest/node image is the container image that kind runs as a
Kubernetes node. Its tag names the Kubernetes version inside, such as
v1.37.0. The tag does not name the kind release that built the image, and
kind supports an image only with that release. The release notes list the
images of each release with their digests. The tag on Docker Hub can point
to a build for another release.
Everything below was checked on 2026-11-03 against kind v0.33.0, the latest release on that day, its release notes and Docker Hub. The cluster further down ran on Docker 29.9.2.
Which image belongs to your kind release
kind version prints the release. kind’s quick start sends you to the notes
of that release to find images suitable for it
(Creating a Cluster).
The notes of v0.33.0
list four:
v1.37.0: kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
v1.36.4: kindest/node:v1.36.4@sha256:099e049362a1526b2db71494e1947aae99bd16290d7c895f2b7ea312e3cbfaed
v1.35.8: kindest/node:v1.35.8@sha256:07b2536e30b803ed61d1677a79df6115f798ce64c80f9e22f6ed45afd09323c0
v1.34.11: kindest/node:v1.34.11@sha256:44e222ee2132dab25ff87301682f89eb82c7880ea3a1bf543bfe9708fd08d67d
The part after @ is the digest of an OCI image index,
the list that points to the amd64 and the arm64 image. v1.37.0 is the
default node image of v0.33.0. Below the list the notes say:
NOTE: You must use the
@sha256digest to guarantee an image built for this release, until such a time as we switch to a different tagging scheme.
kind’s design page on the node image says what is supported: “node images will create a working Kubernetes node at the advertised version with the kind version they were released with (and best effort with other releases)” (Node Image).
Why the tag is not enough
The tag carries only the Kubernetes version, and several kind releases can
build the same one. v1.26.15 is listed in the notes of v0.23.0, v0.24.0
and v0.25.0, each time with another digest:
v0.23.0: kindest/node:v1.26.15@sha256:84333e26cae1d70361bb7339efb568df1871419f2019c80f9a12b7e2d485fe19
v0.24.0: kindest/node:v1.26.15@sha256:1cc15d7b1edd2126ef051e359bf864f37bbcf1568e61be4d2ed1df7a3e87b354
v0.25.0: kindest/node:v1.26.15@sha256:c79602a44b4056d7e48dc20f7504350f1e87530fe953428b792def00bc1076dd
The tag on Docker Hub resolves to the last of them:
$ skopeo inspect --raw docker://docker.io/kindest/node:v1.26.15 | sha256sum
c79602a44b4056d7e48dc20f7504350f1e87530fe953428b792def00bc1076dd -
Docker Hub reports the tag as last pushed on 8 November 2024, the day of
v0.25.0. kind v0.23.0, given kindest/node:v1.26.15 without a digest, pulls
the image built for v0.25.0.
A tag can also point to an image built ahead of the kind release that
supports it. In January 2025, a CI that picked the newest kind and
kindest/node versions got a kindest/node:v1.32.1 built with containerd 2.
The cluster came up, and kind load docker-image failed with
ERROR: failed to detect containerd snapshotter. The report names kind
v0.26.0 with that image
(kubernetes-sigs/kind#3853).
A kind maintainer answered in the issue:
You should scrape the release notes instead of the docker hub tags.
kind v0.27.0 followed in February 2025. Its notes say that “kind v0.27.0+
will be required to use kind load ... subcommands with these new
containerd 2.0+ images (built by kind v0.27+)”. The notes of v0.32.0 say
the same for the next containerd upgrade and add that full compatibility of
node images between kind releases is not guaranteed. Older notes carry
similar constraints. On cgroup v1 hosts, kind v0.20.0 needs images built
with v0.20.0 or later. Kubernetes 1.24 images and later need kind v0.13.0 or
later.
Pinning the image
On the command line, the reference with tag and digest goes to --image:
kind create cluster --image kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
In a config file the image is set per node, with the digest as well (Setting Kubernetes version):
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.35.8@sha256:07b2536e30b803ed61d1677a79df6115f798ce64c80f9e22f6ed45afd09323c0
- role: worker
image: kindest/node:v1.35.8@sha256:07b2536e30b803ed61d1677a79df6115f798ce64c80f9e22f6ed45afd09323c0
--image overrides the image of every node in the config file
(create.go).
We created a cluster from such a file, with the control plane only, and
added -v 1 for kind’s debug lines:
$ kind create cluster --name node-image-post --config kind-config.yaml -v 1 --kubeconfig ./kind-post.kubeconfig
Creating cluster "node-image-post" ...
• Ensuring node image (kindest/node:v1.35.8) 🖼️ ...
DEBUG: docker/images.go:67] Pulling image: kindest/node:v1.35.8@sha256:07b2536e30b803ed61d1677a79df6115f798ce64c80f9e22f6ed45afd09323c0 ...
✓ Ensuring node image (kindest/node:v1.35.8) 🖼️
# ... preparing nodes, control plane, CNI and StorageClass
The status line shows the tag only, because kind drops the digest for display. The pull uses the full reference. The node then reported Kubernetes v1.35.8 and containerd 2.3.4.
kind stuck at “Ensuring node image”
With the Docker provider, this step runs docker inspect --type=image on the
reference and, when the image is missing, docker pull. A failed pull is
retried four times, after pauses of one to four seconds. Then kind stops
with failed to pull image
(images.go).
kind shows none of the pull’s output and sets no timeout on it.
The pull is large. The amd64 image of v1.37.0, the entry sha256:1aac8018…
of the index that the v1.37.0 digest names, has two layers:
$ skopeo inspect --raw docker://docker.io/kindest/node@sha256:1aac8018c42eb5d48fae5be507caaee789fc6d9b9b3f560359dd4157ac00d4f6 | jq '{mediaType, layers: (.layers | length), compressed_bytes: ([.layers[].size] | add)}'
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"layers": 2,
"compressed_bytes": 388182150
}
That is 388 MB to download. At 1 MB/s it takes about six and a half
minutes (388 s), all of it on the “Ensuring node image” line with no sign of
progress. -v 1 prints the “Pulling image” line above and a line per retry.
A kind maintainer points out that the container runtime does the download,
not kind. The suggested workaround is to pull the image yourself first,
since kind does not pull an image that is already present
(kubernetes-sigs/kind#3199):
docker pull kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
docker pull shows the progress of each layer. In CI, the pull as a step of
its own shows its time apart from the cluster creation.
Three Kubernetes minors in kadet-hub’s CI
The kadet-hub/recipes CI checks every recipe on the
three Kubernetes minors upstream supports. The minors, their patch releases
and their node images come from one file, kubernetes-versions.json in
kadet-hub/renovate-config. At the tag kubernetes-versions-2026-11-02 it
reads:
{
"kind": {
"version": "v0.33.0",
"sha256": {
"linux-amd64": "aee6151561422756b764a4ae28e7f44cda5af5a9eead3cc9985112b1de8d8e0d",
"linux-arm64": "20022bee6cfcd5086cb7234d218e3454e6090022f2a8f55d1fa7fcf42c3867a2"
}
},
"kubernetes": [
{
"minor": "1.37",
"version": "1.37.0",
"node_image": "kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5",
"kubeconform": "1.37.0"
},
{
"minor": "1.36",
"version": "1.36.4",
"node_image": "kindest/node:v1.36.4@sha256:099e049362a1526b2db71494e1947aae99bd16290d7c895f2b7ea312e3cbfaed",
"kubeconform": "1.36.4"
},
{
"minor": "1.35",
"version": "1.35.8",
"node_image": "kindest/node:v1.35.8@sha256:07b2536e30b803ed61d1677a79df6115f798ce64c80f9e22f6ed45afd09323c0",
"kubeconform": "1.35.8"
}
]
}kind.sha256 holds the SHA-256 of the kind binaries for Linux on amd64 and
arm64. Each node_image is the line from the v0.33.0 notes. kubeconform
is the Kubernetes version the schema check validates against.
A workflow in renovate-config recomputes the file every day from three
sources
(spec).
https://dl.k8s.io/release/stable.txt names the newest stable Kubernetes
release, v1.37.2 on 3 November 2026. The body of kind’s latest GitHub release
supplies the node images and their digests. The digests GitHub reports for
the release’s assets supply the binary hashes. The window is the three
newest minors for which that kind release has an image. kind v0.33.0 also
lists v1.34.11, which is the fourth and stays out. A new Kubernetes minor
without a kind image leaves the window where it is.
A change of the file reaches main through a pull request and gets a tag
kubernetes-versions-YYYY-MM-DD. Consumers fetch the file by commit SHA,
with the tag as a comment after the URL. The select job of the recipes CI
turns it into job outputs, and the check job is a matrix of recipe and
minor (trimmed from .github/workflows/ci.yml):
select:
outputs:
recipes: ${{ steps.select.outputs.recipes }}
kind: ${{ steps.kubernetes.outputs.kind }}
kubernetes: ${{ steps.kubernetes.outputs.kubernetes }}
steps:
# ... the recipes this run checks
- id: kubernetes
env:
URL: https://raw.githubusercontent.com/kadet-hub/renovate-config/843255e023682724c3325f287bafbf061a1c8b6f/kubernetes-versions.json # kubernetes-versions-2026-11-02
run: |
set -euo pipefail
curl -fsSL --retry 3 "$URL" | jq -r '"kind=\(.kind | tojson)", "kubernetes=\(.kubernetes | tojson)"' | tee -a "$GITHUB_OUTPUT"
check:
needs: [select, lint]
strategy:
fail-fast: false
matrix:
recipe: ${{ fromJSON(needs.select.outputs.recipes) }}
kubernetes: ${{ fromJSON(needs.select.outputs.kubernetes) }}
name: check ${{ matrix.recipe.name }} --level ${{ matrix.recipe.level }} on ${{ matrix.kubernetes.minor }}
steps:
# ... checkout and tools
- env:
KIND_VERSION: ${{ fromJSON(needs.select.outputs.kind).version }}
KIND_SHA256: ${{ fromJSON(needs.select.outputs.kind).sha256['linux-amd64'] }}
run: |
curl -fsSLo "$RUNNER_TEMP/bin/kind" "https://github.com/kubernetes-sigs/kind/releases/download/$KIND_VERSION/kind-linux-amd64"
echo "$KIND_SHA256 $RUNNER_TEMP/bin/kind" | sha256sum -c --quiet
chmod +x "$RUNNER_TEMP/bin/kind"
- env:
NODE_IMAGE: ${{ matrix.kubernetes.node_image }}
# ... recipe and level
run: |
# ...
uv run --locked --project check check/check.py "$RECIPE" --level "$LEVEL" --node-image "$NODE_IMAGE" --report report.txt || status=$?check.py hands the image to kind create cluster --image. kind comes from
the same file, so the binary is always the release that built the images. A
kind pin of its own would let Renovate move kind without the images.
Renovate moves commit and tag in one update. The preset in renovate-config
has a regex manager for this URL form, with the git-tags datasource and a
versioning that sorts the date tags. When a kind release adds a new minor,
the recipes get one pull request that replaces the oldest minor in the
matrix.
Building a custom node image
An image that kind has not published, such as the v1.37.2 that stable.txt
names, can be built locally. kind build node-image takes a Kubernetes source tree, a release tarball as a file or
URL, or a release version
(Building Images):
kind build node-image --type release v1.37.2 --image kindest/node:v1.37.2-local
kind create cluster --image kindest/node:v1.37.2-local
Without --image the result is tagged kindest/node:latest. The build adds
Kubernetes to kind’s base image, which holds what a node needs to run in a
container. The kind binary names its default base image,
docker.io/kindest/base:v20260820-69b56db7 in v0.33.0. Build the image with
the kind release that will create the cluster. The image is then present
locally, and “Ensuring node image” pulls nothing. The url, file and
release types exist since kind v0.24.0. A build from source needs what an
upstream Kubernetes build needs, Docker with buildx included.