Skip to content

Kubernetes on‑premises ErrImagePull — pulling an image that's already there

Contents9 sections

When customers called because an installed service wouldn’t start, the pod was usually in ErrImagePull, sometimes ImagePullBackOff. At first that puzzled me. The customers’ on-premises sites have no outbound access, so we carry images in as tar files and load them ahead of time. Why would anything try to pull an image that was already loaded? Once I checked each case, it turned out kubelet decides to pull under only two conditions, and every case we had hit one of them.

Why does ErrImagePull happen at an on-premises customer site?

Because kubelet decided the image had to be pulled. It decides that under two conditions: the policy is Always, or the policy is IfNotPresent and the image doesn’t appear to be on the node. At a customer site with no route to a registry, a pull fails the moment it starts, so the real question becomes “why did kubelet decide to pull?”

Lining up the cases we handled against those conditions gives three groups. The tag was latest, so the policy became Always. The name or tag the manifest asked for differed from the image we brought in. Or the image was loaded, but not anywhere Kubernetes looks.

The customer site has no registry

Most of our on-premises customer sites restrict outbound traffic, and you can only bring in images and files approved ahead of time. So we export the images built in-house as tar files, get them approved along with checksums, put them on the customer VM’s local disk, and import them straight into containerd. Kubernetes is a single RKE2 node installed from offline install files. Because there’s no registry in between, image names keep their in-house registry paths, and the manifests’ image: values never need editing.

Images come in as tar; nothing goes outThe moment kubelet decides to pull, it has failedInternet side · in-house1Buildpush to the registry2Image tarexport + sha2563Approvalsubmit list · hashesno outbound accessCustomer VMtar on local disk → containerdctr images importkubelet → CRIlooks up name · tagA pull needs a registry, and there is no way out
Images from the top box only go as far as containerd in the lower box; the pull in the red box has nowhere to go

In this setup images skip the registry and go straight into containerd, so everything depends on whether kubelet recognizes the image as present. If it does, the pod starts. If it doesn’t, or never checks, kubelet tries to reach a registry and gets stuck.

The two conditions under which kubelet pulls

How the policy gets set is spelled out in the Kubernetes source. If a manifest leaves imagePullPolicy empty, the API server fills in a default: Always if the tag is latest, IfNotPresent otherwise. An image with no tag at all is read as latest, so it also gets Always.

kubernetes/pkg/apis/core/v1/defaults.go (v1.28.2 excerpt)
func SetDefaults_Container(obj *v1.Container) {
if obj.ImagePullPolicy == "" {
// Ignore error and assume it has been validated elsewhere
_, tag, _, _ := parsers.ParseImageName(obj.Image)
// Check image tag
if tag == "latest" {
obj.ImagePullPolicy = v1.PullAlways
} else {
obj.ImagePullPolicy = v1.PullIfNotPresent
}
}

The function that combines the policy with whether the image is on the node lives in kubelet. Unless the policy is Never, Always pulls every time and IfNotPresent pulls only when the image is missing.1

kubernetes/pkg/kubelet/images/image_manager.go (v1.28.2 excerpt)
// shouldPullImage returns whether we should pull an image according to
// the presence and pull policy of the image.
func shouldPullImage(container *v1.Container, imagePresent bool) bool {
if container.ImagePullPolicy == v1.PullNever {
return false
}
if container.ImagePullPolicy == v1.PullAlways ||
(container.ImagePullPolicy == v1.PullIfNotPresent && (!imagePresent)) {
return true
}
return false
}
How kubelet decides to pullAn empty policy is filled in from the tagimagePullPolicy defaultlatest · no tag → Alwaysanything else → IfNotPresentAlwayspulls even if the node has itIfNotPresentasks CRI by name · tagfound → run itmissing → pullpull → no registry → ErrImagePullThe three cases here1latest → Always2name or tag differs → missing3imported, CRI can’t see it → missing
Always heads to the red box without checking, and IfNotPresent ends up there too when CRI doesn't know the image

The easy part to miss is what “present” means. kubelet doesn’t search the disk; it asks CRI (the Container Runtime Interface) by name and tag. So an image sitting somewhere on disk counts as missing if CRI doesn’t know about it. The third case came from exactly this gap.

ErrImagePull and ImagePullBackOff are the same failure

The two states look like different problems, but they’re one pull failure named by timing. Right after a failed attempt it’s ErrImagePull; while waiting for the next attempt it’s ImagePullBackOff. As of kubelet v1.28, the wait starts at 10 seconds, doubles each time, and stops at 300 seconds.

🧪 Try it

The state flips back and forth while pulls fail

I couldn’t run a kubelet here, so I pulled out only the image manager’s backoff rule and built a model that prints the state every 10 seconds. With no registry, every pull fails.

airgap/pullsim/main.go
package main
import (
"fmt"
"time"
)
// A simplified model of the kubelet image manager.
// After a failed pull, backoff starts at 10s, doubles, and stops at 300s.
// A sync during backoff returns ImagePullBackOff without pulling.
const (
base = 10 * time.Second
max = 300 * time.Second
sync = 10 * time.Second // assume a 10s pod sync period
)
func main() {
var nextTry time.Duration
backoff := time.Duration(0)
last := ""
for now := time.Duration(0); now <= 12*time.Minute; now += sync {
state := "ImagePullBackOff"
if now >= nextTry {
state = "ErrImagePull" // no registry, so every pull fails
if backoff == 0 {
backoff = base
} else if backoff *= 2; backoff > max {
backoff = max
}
nextTry = now + backoff
}
if state == "ErrImagePull" || state != last {
fmt.Printf("%5s %-17s next pull in %v\n", fmtDur(now), state, nextTry-now)
}
last = state
}
}
func fmtDur(d time.Duration) string {
return fmt.Sprintf("%d:%02d", int(d.Minutes()), int(d.Seconds())%60)
}
Output
$ go run ./pullsim
0:00 ErrImagePull next pull in 10s
0:10 ErrImagePull next pull in 20s
0:20 ImagePullBackOff next pull in 10s
0:30 ErrImagePull next pull in 40s
0:40 ImagePullBackOff next pull in 30s
1:10 ErrImagePull next pull in 1m20s
1:20 ImagePullBackOff next pull in 1m10s
2:30 ErrImagePull next pull in 2m40s
2:40 ImagePullBackOff next pull in 2m30s
5:10 ErrImagePull next pull in 5m0s
5:20 ImagePullBackOff next pull in 4m50s
10:10 ErrImagePull next pull in 5m0s
10:20 ImagePullBackOff next pull in 4m50s
When each pull was tried, and the wait before the next
  • 0:0010s
  • 0:1020s
  • 0:3040s
  • 1:101m20s
  • 2:302m40s
  • 5:105m
  • 10:105m

pullsim model output · kubelet v1.28 constants (start at 10s, double, cap at 300s)

In the model, ErrImagePull shows up only seven times in 12 minutes; the rest of the time it’s ImagePullBackOff. Yet most calls I got said ErrImagePull. That puzzled me. My guess is that people often looked right after installation, in the first minute or two while the waits were still short. Either way the cause is the same, so the failure message in describe is a faster place to look than the state name.

A latest tag, or a tag that doesn’t match

Case 1: a latest tag pulls even when the image is there

The first case is the simplest. The package included a latest-tagged image like busybox:latest, and the manifest set no policy. By the default rule the policy becomes Always, and kubelet goes to the registry to check even though the image is sitting on the node. In-house there’s a registry, so nothing goes wrong; it only breaks at the customer site.

Here’s the same thing reproduced at the CRI level. Ask for an already-imported image again with crictl pull, and the runtime goes to the registry for the manifest regardless of the local copy, failing at name resolution. That’s what a kubelet with an Always policy does every time.

Repro · containerd 1.7.22, no registry
$ crictl pull registry.example.internal/demo-api:v1.4.2 # already imported
E1021 02:06:22.793327 remote_image.go:180] "PullImage from image service failed"
err="rpc error: code = Unknown desc = failed to pull and unpack image
\"registry.example.internal/demo-api:v1.4.2\": failed to resolve reference ...:
failed to do request: Head \"https://registry.example.internal/v2/demo-api/manifests/v1.4.2\":
dial tcp: lookup registry.example.internal on 8.8.8.8:53: no such host"
(exit 1, 0.02s)

It failed in 0.02 seconds because name resolution failed immediately in the repro environment. At a customer site where DNS or a firewall answers slowly, each attempt waits for a timeout instead. Either way, the result is ErrImagePull.

While moving this post I got access to an RKE2 v1.28.2 node (containerd 1.7.3-k3s1), so I tried it again there. With the image imported, three runs of crictl pull gave the same error as above apart from the DNS server address, and each failed within 0.11–0.12 seconds, crictl startup included.

Case 2: if the name or tag differs at all, the image is missing

In the second case kubelet couldn’t find the image. CRI treats two images as the same only if name and tag match exactly. If the manifest asks for demo-worker:v1.4.3 and the tar we brought holds v1.4.2, the node doesn’t have the image, however similar the contents, and IfNotPresent moves on to a pull.

Names and tags drifted apart in a few ways. The most common was updating the manifest to a new version while loading the old image tar. Images also went missing on the packaging side. Our image export script only collects images from running pods, so if an optional component was scaled down at export time, its image wasn’t in the tar list at all. And crictl rmi --prune, which we ran to clean up unused images before shipping, also deleted images belonging to stopped pods. Neither caused any symptom in-house; only that one pod failed at the customer site.

On premises, a missing image is one whose name and tag aren’t in CRI’s list. Whether the file is on disk doesn’t matter. The only way to prevent this ahead of time is to compare the list the manifests need with the list inside the tars before shipping.

Case 3: imported for sure, but CRI doesn’t know

The third case was the hardest to track down. During an on-site install, we loaded the image tar with ctr images import, the command finished without errors, and the pod still tried to pull. crictl images didn’t list the image either. The cause was the path ctr used to load the image. Without the --local option, import goes through containerd’s transfer service, and images loaded that way weren’t registered as CRI-managed images.2 The fix was to import again with --local --platform linux/amd64.

🧪 Try it

Same tar, different ctr version

I couldn’t recreate the RKE2 environment as is, so I ran a single containerd 1.7.22 daemon and loaded tars of the same format with two clients, ctr 1.7.22 and ctr 2.0.6. ctr 2.0 uses the transfer service by default, so the output format left in our install logs (saved, Importing elapsed) shows up exactly.

Repro · same daemon, different ctr versions
$ ctr version # daemon
Version: v1.7.22
$ ctr images import demo-api_v1.4.2.tar # ctr 1.7.22
unpacking registry.example.internal/demo-api:v1.4.2 (sha256:1563d1f2424d3e44d2187fcd6966353431ea85eca3a1247cd04b900f38dd4fd3)...done
$ ctr images import demo-worker_v1.4.2.tar # ctr 2.0.6
registry.example.internal/demo worker:v1 saved
Importing elapsed: 0.0 s total: 0.0 B (0.0 B/s)
$ crictl images
IMAGE TAG IMAGE ID SIZE
registry.example.internal/demo-api v1.4.2 719e08e7d70f4 723B
$ ctr images ls # labels only
REF LABELS
registry.example.internal/demo-api:v1.4.2 io.cri-containerd.image=managed
registry.example.internal/demo-worker:v1.4.2 -

Both imports finished without errors, but the results split. ctr 1.7 printed unpacking ...done, labeled the image io.cri-containerd.image=managed, and crictl listed it right away. ctr 2.0 printed only saved, added no label, and the image was missing from crictl’s list. The progress line cuts off part of the name, but the image that went in is demo-worker:v1.4.2. kubelet asks CRI, so a pod started in this state goes to a pull even with IfNotPresent.

Repro · stored state, CRI list, after restart
$ ctr images check # stored state
REF UNPACKED
registry.example.internal/demo-worker:v1.4.2 true
$ crictl images # what CRI sees
IMAGE TAG IMAGE ID SIZE
registry.example.internal/demo-api v1.4.2 719e08e7d70f4 723B
# after restarting containerd
$ crictl images
IMAGE TAG IMAGE ID SIZE
registry.example.internal/demo-api v1.4.2 719e08e7d70f4 723B
registry.example.internal/demo-worker v1.4.2 719e08e7d70f4 723B
Same daemon, different clientSame tar format into a containerd 1.7.22 daemonctr 1.7.22 · local importoutputunpacking …doneCRI labelmanagedcrictl imageslistedctr 2.0.6 · transfer APIoutputsavedCRI labelnonecrictl imagesnot listedIt showed up after a daemon restart
The green box got the label and showed up in crictl; the red box was only stored

The repro taught me one more thing. Here the stored state was UNPACKED true, and after a containerd restart CRI reloaded the image and it appeared in the list. Our install logs showed UNPACKED as false in the same situation, so back then we deleted and re-imported. That means the outcome can depend on the runtime and snapshotter setup. So we fixed an order of checks: first confirm the import printed unpacking ...done, then compare the number of tars with the number of images in crictl images. Only if the counts differ do we decide between a restart and a re-import.

I repeated this experiment on the same RKE2 node too. I pointed the node’s own ctr 1.7.3 and a separately downloaded ctr 2.0.6 at the node’s containerd 1.7.3-k3s1, and built fresh tars holding a minimal one-layer image. The card below leaves out the socket address and the -n k8s.io option.

Repro · RKE2 v1.28.2 node
$ ctr images import demo-api_v1.4.2.tar # ctr 1.7.3
unpacking registry.example.internal/demo-api:v1.4.2 (sha256:3370bff1e1639e3c69d511ecb1f0a0e47a24c2f265486aed7ad5287c3a1e44b5)...done
$ ctr images import demo-worker_v1.4.2.tar # ctr 2.0.6
registry.example.internal/demo worker:v1 saved
Importing elapsed: 0.2 s total: 0.0 B (0.0 B/s)
$ crictl images | grep example
registry.example.internal/demo-api v1.4.2 7acac72ffcb1e 10.8kB
$ ctr images import --local --platform linux/amd64 demo-worker_v1.4.2.tar # ctr 2.0.6
unpacking registry.example.internal/demo-worker:v1.4.2 (sha256:3370bff1e1639e3c69d511ecb1f0a0e47a24c2f265486aed7ad5287c3a1e44b5)...done
$ crictl images | grep example
registry.example.internal/demo-api v1.4.2 7acac72ffcb1e 10.8kB
registry.example.internal/demo-worker v1.4.2 7acac72ffcb1e 10.8kB

The result matched the earlier repro. The image imported with ctr 2.0.6 showed UNPACKED true in ctr images check, yet it had no label and was missing from the crictl list. Since this containerd serves the node’s Kubernetes, I didn’t try a restart. Instead I re-imported with --local --platform linux/amd64 without deleting anything, and the label appeared and crictl listed the image right away.

What I check when a call comes in

The order of checks is almost always the same. First the events in kubectl describe pod, then kubectl logs to see whether the container started, and if that’s not enough, down to the Kubernetes logs on the node.

What I check when a call comes inStop as soon as the cause shows up1kubectl describe podEvents: which image, and why2kubectl logsan image problem means no container3node logskubelet and containerd pull records4compare listsctr images ls vs crictl images
Step 1, the Events, settles most cases; step 4 is only for when case 3 is suspected

The last few lines of the Events in describe hold most of the answer: which image kubelet tried to pull and which error it failed with. Below, the runtime error from the repro above is shown in kubelet’s event format.

Expected output (format example)
$ kubectl describe pod demo-api-7c9f6b5d8-x2k4p
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 14s kubelet Pulling image "registry.example.internal/busybox:latest"
Warning Failed 14s kubelet Failed to pull image "registry.example.internal/busybox:latest":
rpc error: code = Unknown desc = failed to pull and unpack image ...:
dial tcp: lookup registry.example.internal ...: no such host
Warning Failed 14s kubelet Error: ErrImagePull
Normal BackOff 2s kubelet Back-off pulling image "registry.example.internal/busybox:latest"
Warning Failed 2s kubelet Error: ImagePullBackOff

The thing to look at is the image name. A latest tag means case 1; a tag that differs from the shipped list means case 2. If the name and tag match the shipped list exactly and kubelet still pulls, suspect case 3 and go down to the node. With an image problem, logs comes back empty or says the container is still waiting to start. An empty log is itself a sign that the application isn’t the problem.

Commands to run on the node (RKE2 paths)
journalctl -u rke2-server --since "30 min ago" | grep -iE "pull|image"
tail -n 200 /var/lib/rancher/rke2/agent/logs/kubelet.log
tail -n 200 /var/lib/rancher/rke2/agent/containerd/containerd.log
# the image list CRI actually sees
export CRI_CONFIG_FILE=/var/lib/rancher/rke2/agent/etc/crictl.yaml
crictl images | grep <image name>
ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls | grep <image name>

The last two lines go together because of case 3. If an image is in ctr images ls but not in crictl images, it was loaded but never registered with CRI.

Two things we changed before releases

After these three cases, the development side changed two things. We check the image list before every release, and we set imagePullPolicy explicitly in the manifests. The list check stops missing images; the fixed policy stops pulls of images that are already there.

Option Behavior on premises Cost
Pin IfNotPresent No pull if the node has it Rebuilding under the same tag isn’t picked up, so tags must change every time
Never Run if present; fail at once with ErrImageNeverPull if not No pulls even in dev, where a registry exists, so manifests split per environment
Pin a digest (@sha256) Only an image with the same content is accepted The digest changes every release and is hard for people to read on site
Keep latest + defaults Always pulls, so it always fails Unusable on premises without outbound access

We chose to pin IfNotPresent. Dev and customer sites can share one manifest, and it’s the simplest thing to read on site. The cost, as the table says, is a tagging rule. If anyone pushes a rebuilt image under the same tag, IfNotPresent keeps running the old one, so we set a separate tagging rule to prevent that.

🧪 Try it

A checker that compares manifests with the shipped tars

A list check done by eye eventually misses something. I wrote a small tool that compares the image references in the manifests with the image names in the tars we ship, and flags images missing from the tars, images that end up Always, and images with no policy set. This is a rewrite that keeps only its structure.

testdata/manifests/*.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-api
spec:
template:
spec:
initContainers:
- name: wait-db
image: registry.example.internal/busybox:latest
containers:
- name: api
image: registry.example.internal/demo-api:v1.4.2
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-worker
spec:
template:
spec:
containers:
- name: worker
image: registry.example.internal/demo-worker:v1.4.3
imagePullPolicy: IfNotPresent
airgap/imagecheck/main.go
package main
import (
"archive/tar"
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
// Before a release, compare "images the manifests use" with "images in the tars we carry in".
var (
reImage = regexp.MustCompile(`^\s*-?\s*image:\s*"?([^"\s]+)"?`)
rePolicy = regexp.MustCompile(`^\s*imagePullPolicy:\s*(\w+)`)
)
type use struct{ file, image, policy string }
func main() {
uses := scanManifests(os.Args[1])
packed := scanTars(os.Args[2])
problems := 0
for _, u := range uses {
var notes []string
if !packed[u.image] {
notes = append(notes, "not in the tars")
}
policy := u.policy
if policy == "" {
policy = defaultPolicy(u.image) + "(default)"
}
if strings.HasPrefix(policy, "Always") {
notes = append(notes, "Always → always pulls on-prem")
}
if u.policy == "" {
notes = append(notes, "no policy set")
}
mark := "OK "
if len(notes) > 0 {
mark, problems = "NG ", problems+1
}
fmt.Printf("%s %-22s %-46s %-22s %s\n", mark, u.file, u.image, policy, strings.Join(notes, ", "))
}
used := map[string]bool{}
for _, u := range uses {
used[u.image] = true
}
for img := range packed {
if !used[img] {
fmt.Printf("-- tar no manifest uses: %s\n", img)
}
}
fmt.Printf("\n%d problems\n", problems)
if problems > 0 {
os.Exit(1)
}
}
func defaultPolicy(image string) string {
ref := image[strings.LastIndex(image, "/")+1:]
if !strings.Contains(ref, ":") || strings.HasSuffix(ref, ":latest") {
return "Always" // Kubernetes default rule: no tag or latest → Always
}
return "IfNotPresent"
}
func scanManifests(dir string) []use {
files, _ := filepath.Glob(filepath.Join(dir, "*.yaml"))
sort.Strings(files)
var out []use
for _, f := range files {
fh, _ := os.Open(f)
sc := bufio.NewScanner(fh)
for sc.Scan() {
if m := reImage.FindStringSubmatch(sc.Text()); m != nil {
out = append(out, use{file: filepath.Base(f), image: m[1]})
} else if m := rePolicy.FindStringSubmatch(sc.Text()); m != nil && len(out) > 0 {
out[len(out)-1].policy = m[1] // treat it as the previous container's policy (simplified)
}
}
fh.Close()
}
return out
}
// Read image names from index.json in an OCI tar.
func scanTars(dir string) map[string]bool {
files, _ := filepath.Glob(filepath.Join(dir, "*.tar"))
names := map[string]bool{}
for _, f := range files {
fh, _ := os.Open(f)
tr := tar.NewReader(fh)
for {
h, err := tr.Next()
if err != nil {
break
}
if h.Name != "index.json" {
continue
}
var idx struct {
Manifests []struct {
Annotations map[string]string `json:"annotations"`
} `json:"manifests"`
}
json.NewDecoder(tr).Decode(&idx)
for _, m := range idx.Manifests {
if n := m.Annotations["io.containerd.image.name"]; n != "" {
names[n] = true
}
}
}
fh.Close()
}
return names
}
Output
$ go run ./imagecheck testdata/manifests testdata/images
NG deployment_api.yaml registry.example.internal/busybox:latest Always(default) Always → always pulls on-prem, no policy set
NG deployment_api.yaml registry.example.internal/demo-api:v1.4.2 IfNotPresent(default) no policy set
NG deployment_worker.yaml registry.example.internal/demo-worker:v1.4.3 IfNotPresent not in the tars
-- tar no manifest uses: registry.example.internal/demo-worker:v1.4.2
3 problems
exit status 1

All three cases get caught at once. The initContainer’s busybox:latest has no policy, so it becomes Always, and demo-worker asks for v1.4.3 while the tars only have v1.4.2. The tool also reports tars nothing uses, which is usually the same mistake seen from the other side. Any problem ends in exit 1, so the tool can sit in front of the packaging script. It can’t catch case 3, so we added the post-import count check to the install procedure instead.

Summary

  • ErrImagePull on premises means kubelet decided to pull. That happens when the policy is Always, or when it’s IfNotPresent and CRI doesn’t have the image.
  • ErrImagePull and ImagePullBackOff are one failure, named for the moment of the attempt and the wait that follows.
  • A latest tag with no policy becomes Always. A name or tag that differs at all counts as a missing image. Depending on the import path, CRI may not know about an image that was loaded.
  • Diagnosis starts from the image name in describe and goes down through logs, node logs, and a comparison of the ctr and crictl lists.

Here is how far the repros reach.

What I checked What it shows What it doesn’t
v1.28.2 source excerpts The default policy rule and the pull decision Details on the customer cluster’s version
Backoff model Why the two states alternate, and the intervals The real kubelet sync period and event timing
crictl pull repro Always goes to the registry even for a present image How long a failure takes behind customer DNS and firewalls
ctr 1.7 / 2.0 repro Importing via transfer leaves no CRI label, so crictl can’t see it (same on an RKE2 v1.28.2 node) Whether a containerd restart fixes it on an RKE2 node
Checker Name, tag, and policy mismatches between manifests and tars Helm templates or YAML with mixed documents

The describe output is a format example made without a cluster; everything else is real output. The repro ran on containerd 1.7.22, ctr 2.0.6, and crictl 1.28.0. While moving this post, I ran the crictl pull and ctr repros once more on an RKE2 v1.28.2 node (containerd 1.7.3-k3s1, ctr 1.7.3 and 2.0.6, crictl 1.26.1). While moving this post, I ran pullsim and imagecheck again with Go 1.23.11 on darwin/arm64 and got the same output.

When I first saw ErrImagePull, I assumed the import had gone wrong and re-checked the tars. Now I first ask why kubelet decided to pull, before asking whether the image is there. Sorting it into policy, a name mismatch, or CRI not knowing settles most cases with a single describe. In-house, with a registry available, none of the three cases showed any symptom.

References

Footnotes

  1. In the v1.37 source, the default rule and the 10-to-300-second backoff are unchanged. The pull decision is now split across imagePullPrecheck and EnsureImageExists, but the rule is the same: Always pulls without checking, and IfNotPresent pulls only when CRI lacks the image. KubeletEnsureSecretPulledImages, on by default since v1.35, re-verifies credentials for images pulled with a pull secret, but its default policy, NeverVerifyPreloadedImages, doesn’t re-verify images that reached the node some other way than through kubelet. ↩

  2. ctr 1.7 also has the transfer path, but --local defaults to true there, so it imports locally unless you turn that off. From ctr 2.0 the default is false, so without --local the import goes through the transfer API. ↩

Edit on GitHub
esc

Recent posts

↑↓ move↵ openOpen search page ›