Kubernetes v1.36, released April 22, 2026, graduated a feature called OCI VolumeSource — image volumes — to stable. The pitch sounds simple: a Pod can now mount any OCI image directly as a read-only volume, no different from mounting a ConfigMap. Model weights, static asset bundles, reference data — anything that fits in a registry — can live in their own OCI artifact instead of being COPY'd into the application image a tenant's build step produces.
The obvious follow-up question is the one that actually matters for a platform team deciding whether to build tenant-facing support for this: if you already have a reasonably well-layered Dockerfile or buildpack, doesn't image-layer caching already solve "don't re-upload the big file that didn't change"? It mostly does — for the narrow case of one image, rebuilt from the same base, on the same registry. What image volumes actually add, and where the honest limits are, is worth working through with a real pod spec and real numbers before anyone offers "mount your model weights as a volume" as a platform primitive.
What Actually Graduated to Stable in 1.36
Image volumes didn't appear out of nowhere in 1.36 — they've been moving through the standard three-stage graduation path since 2024:
| Kubernetes version | Stage | What changed |
|---|---|---|
| v1.31 (Aug 2024) | Alpha | ImageVolume feature gate introduced; basic mount support |
| v1.33 (Apr 2025) | Beta | Enabled by default; added subPath/subPathExpr mounting |
| v1.36 (Apr 2026) | Stable | Feature gate locked on, no longer opt-in |
The API surface is small. A volume of type image references any OCI registry artifact — not just something built as a "container image" in the traditional sense — and mounts its filesystem contents read-only into a container:
apiVersion: v1
kind: Pod
metadata:
name: worker
spec:
containers:
- name: app
image: registry.example.com/tenant/worker:a1b2c3d
volumeMounts:
- name: embeddings
mountPath: /data/embeddings
subPath: v3 # pull just one subdirectory out of the artifact
volumes:
- name: embeddings
image:
reference: registry.example.com/tenant/embeddings:v3
pullPolicy: IfNotPresentThree things are worth clocking in that spec. First, image.reference is a completely independent OCI reference from spec.containers[*].image — it has its own tag, its own push history, its own digest. Second, the volume is inherently read-only; there's no write-back path, so it can't double as scratch space. Third, subPath (stable since 1.33) lets a container pull one directory out of a larger artifact without exposing the rest of it in the mount — useful if one artifact bundles several model versions or asset sets and a given deployment only needs one.
Runtime support gates the practical rollout: containerd's support landed in the 2.1 line, and CRI-O has carried it since 1.31. If your Cluster API–managed fleet is still on an older containerd build, the API object will validate and the Pod will sit unschedulable-in-practice until the node's runtime catches up — worth checking before you advertise this to tenants.
Doesn't a Well-Layered Dockerfile Already Do This?
This is the question a skeptical platform engineer should ask before treating image volumes as a new capability rather than a new spelling of an old one. And the honest answer is: for the specific problem of "don't re-push a layer that hasn't changed," a properly ordered Dockerfile or Cloud Native Buildpacks layer (with cache = true in layers.toml) already gets you most of the way there. Content-addressed layers mean an unchanged blob doesn't move over the wire again on the next build. We've written about how far that caching goes — it's not nothing.
What a single monolithic image can't do, no matter how well its layers are ordered, is the following three things — and these are the actual new capabilities image volumes add:
An artifact update no longer requires touching the app image at all. If your 1.8 GB embeddings file lives inside the same image as your application code, updating the embeddings means building a new image tag, even if not one line of application code changed. With an image volume, embeddings:v3 becomes embeddings:v4 as its own push, its own tag, its own rollout — the app image's tag, digest, and build pipeline never enter the picture. Layer caching still made the push cheap either way; what it never did is decouple the release event.
Unrelated app images can share the same artifact. A Python inference service and a Node.js API gateway sharing a common base layer is easy if they both FROM the same base image — but that's rare in practice across different language runtimes, and layer caching only dedupes content that's byte-identical and sits at the same position in each image's build graph. An image volume has no such constraint: both services just reference registry.example.com/shared/reference-data:v7 as a volume, and the node's content store dedupes the pull by digest regardless of what either app image's Dockerfile looks like. That's sharing without requiring shared ancestry.
subPath extracts a slice of an artifact your build never has to touch. If a shared artifact bundles five model versions and a given deployment needs one, layer caching in your own image can't help — the artifact isn't in your build context at all in the volume model, so there's nothing to select from during a build. The kubelet does the selection at mount time instead.
What layer caching still wins on: pure push-time cost when nothing changed, and simplicity — one image, one lifecycle, one place to look. Image volumes trade that simplicity for independent lifecycles, and that trade is only worth making when the three capabilities above actually apply to your workload.
Worked Example: Splitting a Build Pipeline
Here's what the split actually looks like for a representative tenant workload — a Node.js inference API bundling a 1.8 GB sentence-embeddings model, updated roughly monthly, sitting behind application code that ships several times a week. This is an illustrative example built from typical component sizes, not an official Kubernetes or vendor benchmark — treat the numbers as representative of the shape of the change, not a guaranteed result on any specific fleet.
Before — one monolithic image:
FROM node:22-slim
COPY package*.json ./
RUN npm ci --omit=dev
COPY ./embeddings-model /app/model # 1.8 GB, changes ~monthly
COPY ./src /app/src # ~40 MB, changes several times/week| Image size | What a code-only push triggers | |
|---|---|---|
| Monolithic image | ~1.92 GB | New tag, new manifest push. The 1.8 GB model layer is unchanged and byte-identical, so a well-configured registry push skips re-uploading it — but every pod on every node still needs the full 1.92 GB present locally before it can start, and a fresh node scheduling this Pod for the first time pulls all 1.92 GB regardless of which part changed. |
After — split into two OCI references:
volumes:
- name: model
image:
reference: registry.example.com/tenant/embeddings-model:2026-06
pullPolicy: IfNotPresent| Image size | What a code-only push triggers | |
|---|---|---|
| App image | ~40 MB | New tag, new manifest, ~40 MB pushed and pulled. |
| Model image volume | ~1.8 GB (unchanged, referenced by tag) | Nothing — the volume reference in the Pod spec doesn't change, so no new pull happens on redeploy; IfNotPresent means a node that already has that digest cached skips the pull entirely. |
The delta that matters isn't the total bytes moved on any single build — it's what a cold node has to do. A fresh node scheduling this Pod for the first time under the monolithic approach pulls 1.92 GB before the container can start, every time, regardless of whether the model changed. Under the split approach, a cold node still has to pull ~1.8 GB for the model volume the first time it lands a Pod that needs it — but every subsequent Pod on that node, from any tenant referencing the same embeddings-model:2026-06 digest, mounts it with zero additional pull. That's the node-level dedup from the previous section showing up as an actual number: one 1.8 GB pull amortized across every Pod that needs it on that node, instead of one 1.8 GB pull baked into every single image tag that happens to bundle it.
What a Self-Hosted Platform Must Verify Before Offering This to Tenants
None of the above is free once you're the one operating the fleet a tenant's workload lands on. Before exposing image: volumes as a tenant-facing primitive — in a bex.yml field, an App CR spec, or any other tenant-controlled surface — a Cluster API–managed platform needs to check:
- Runtime version floors. Confirm the containerd or CRI-O version on every node pool actually shipped stable image-volume support before advertising the feature — a node pool on an older runtime will silently fail to schedule Pods using it, which is a worse failure mode than not offering it at all.
- Kubelet image garbage collection. Image volumes are pulled into the same on-node image store as regular container images, and by default they're subject to the same disk-pressure eviction as everything else — deleted oldest-last-used-first once disk usage crosses the kubelet's high-threshold watermark. An evicted model artifact isn't lost, but the next Pod that needs it pays a full re-pull — a latency spike a tenant will notice at the worst possible moment unless the platform either pins hot artifacts or budgets headroom into its GC thresholds.
- Registry auth is shared, not separate. Image volume pulls authenticate through the same
imagePullSecretsmechanism as the container image itself — there's no separate credential path to configure, but also no separate access boundary. A tenant's pull secret that can fetch their app image can fetch anything else referenced as a volume under that same secret's scope. - A second untrusted artifact class needs scanning. If your platform scans tenant-pushed images for vulnerabilities or malware before deploy, an image volume reference is exactly as untrusted and needs the same treatment — it's a full filesystem mounted into a running container, not inert data.
- Read-only is absolute. There's no writable image-volume mode. A tenant expecting to use one as shared writable scratch space needs a different primitive (a regular
PersistentVolumeClaim) — worth documenting explicitly so it isn't discovered as a runtime error.
The Verdict: When to Switch, When to Keep Baking
Given the sections above, the answer to the title's question is concrete, not "it depends" hand-waving:
Switch to an image volume when an artifact is large relative to your app image, changes on a different cadence than your code, and either needs to be shared across more than one app image or benefits from node-level dedup across many Pods. The embeddings-model example above clears all three bars — 1.8 GB against a 40 MB app image, monthly versus multiple-times-weekly changes, and (if more than one service uses the same model) shared ancestry that a Dockerfile can't provide.
Keep baking it into the app image when the artifact is small, changes in lockstep with your code, or needs write access. A few megabytes of static config that only ever changes alongside a code deploy gains nothing from a second OCI reference — it's added operational surface (a second thing to scan, a second GC risk, a second version to keep in sync) for a saving that doesn't materialize, since layer caching already makes that push cheap.
The size and change-frequency mismatch is the real signal, not "is this a model" or "is this static assets" as a category. A 20 MB static asset bundle that redeploys with every code push belongs in the image. A 20 GB dataset shared across five services that updates on its own schedule belongs in a volume — full stop, regardless of whether either number resembles the worked example above.
A Cluster API–managed fleet deciding whether to expose this as a tenant primitive is exactly the kind of infrastructure decision that's easier to make well when the build pipeline runs on machines you own. Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with a Render-compatible API on top. Check out the repo on GitHub if this is the layer of the stack you'd rather control than inherit from a vendor's roadmap.
Sources
- Use an Image Volume With a Pod — Kubernetes docs.
- KEP-4639: OCI VolumeSource — kubernetes/enhancements.
- Kubernetes v1.31: Read Only Volumes Based On OCI Artifacts (alpha).
- Kubernetes v1.33: Image Volumes Graduate to Beta.
- Kubernetes Garbage Collection — Kubernetes docs.
- Complete Guide to Kubernetes 1.36: DRA GA, OCI VolumeSource, MutatingAdmissionPolicy.



