Workloads pulling container images tagged latest or with no tag at all
What does ZopNight detect here?
ZopNight flags Deployments, StatefulSets and DaemonSets on EKS, GKE and AKS when any container or init container uses an image tagged `:latest` or no tag. Kubernetes treats a missing tag as `latest` and pulls with `imagePullPolicy: Always` by default, so the same manifest can run different code on each node.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1711 · RC-1811 · RC-1911 · RC-1712 · RC-1812 · RC-1912 · RC-1713 · RC-1813 · RC-1913 |
| Category | reliability |
| Severity | medium |
| Metric | container image reference |
| Threshold | tag is latest or absent; digests pass |
| Source | ZopNight |
| Permissions used | list deployments.apps · list statefulsets.apps · list daemonsets.apps |
Where it applies
One tag, many possible images
A tag is a movable label. The
container images documentation states
that if you don’t specify a tag, Kubernetes assumes latest, and it warns against using :latest
in production because it is harder to track which version is running and harder to roll back.
The pull policy makes it worse. When imagePullPolicy is omitted and the tag is latest or
missing, Kubernetes sets the policy to Always. Every new pod asks the registry for whatever
latest means at that moment. A node replaced on Tuesday can run a different build from the one
that started on Monday, from the same unchanged manifest. A rollback to the previous ReplicaSet
does not bring the old code back either, because the old ReplicaSet points at the same tag.
Searching workloads for unpinned images
The command checks main and init containers and treats digest references (@sha256:...) as
pinned:
kubectl get deployments,statefulsets,daemonsets -A -o json | jq -r ' .items[] | .kind as $k | .metadata as $m | (.spec.template.spec.containers + (.spec.template.spec.initContainers // []))[] | select((.image | contains("@") | not) and ((.image | split("/") | last | contains(":") | not) or (.image | endswith(":latest")))) | "\($k) \($m.namespace)/\($m.name) \(.name) \(.image)"'How ZopNight parses each image reference
For every container, including init containers, ZopNight reads the image string. A reference
containing @ is a digest and passes. Otherwise it looks for a tag after the last /, so a
registry port such as registry.example:5000/app is not mistaken for a tag. No tag, or a tag equal
to latest, marks the container. The finding lists every offending container by name, so an app
container and a sidecar with the same problem appear on one recommendation. The check runs for
Deployments, StatefulSets and DaemonSets on EKS, GKE and AKS, with no metric or time window.
Why init containers are in scope here
Several other workload checks, such as the probe and resource rules, skip init containers because
those containers run once and exit. Image pinning is different: an init container pulls and
executes a real image before the app starts, so an unpinned migration or setup image is just as
unpredictable. Workloads with no containers in their spec are skipped. Tags such as stable or
v1 that are moved in place are not detected, since only latest and missing tags are flagged.
Reliability risk, no savings figure
This medium-severity reliability finding has no dollar amount. The cost is unreproducible deploys, rollbacks that do not roll back and incidents that are hard to trace to a build.
Pinning every image
- Find the exact build running today with
kubectl get pods -n <namespace> -o jsonpath='{..imageID}', which shows the digest each node pulled. - Replace the tag with a version tag or a digest, for example
kubectl set image deployment/api app=registry.example/api:1.42.0. - Change CI so every build publishes an immutable version tag and the manifests reference it.
- Optionally enforce the rule at admission with a policy that rejects
:latest.