🚀 Kubernetes v1.33’s Image Volume β is on Fire! “Code-Only” Images are Changing the Game!

Kubernetes v1.33 has finally brought Image Volume to β status! (※disabled by default)
This feature allows you to mount OCI images directly as read-only volumes – isn’t this revolutionary?

🧠 What Makes Image Volume So Amazing?

Say goodbye to the old way of “cramming everything into one container”, and hello to:

  • Application code
  • Runtime
  • Libraries

…all split into separate images and mounted independently!

This is especially powerful for scripting language users (Node.js/Python/etc.)!
Example: Mount a “code-only” image containing your app, and run it with node:22.15.1.

🧱 Building an Ultra-Lightweight “Code-Only” Image

The beauty of Image Volume is creating ultra-lightweight OCI images containing only code and config files.
Let’s build a simple example with just Node.js application code.

📁 index.js – The App

const http = require('http');
http.createServer((_, res) => res.end('Hello from OCI volume!n'))
    .listen(process.env.PORT || 80);

A simple HTTP server in Node.js that returns “Hello from OCI volume!”

🐳 Dockerfile – The Shocking “FROM scratch”

FROM scratch
COPY app /

Starting FROM scratch (= nothing) and just COPYing the code.
That’s right – not even Node.js is included.

What does this mean? The runtime (Node.js) is provided by a separate container,
and this image is used purely as a “code repository.”

📏 The Size? A Mere 134 Bytes!

This OCI image, composed of just these 3 files, weighs in at only 134 bytes!

docker images | grep hello-app
localhost:5001/hello-app                                                799466d0                 c4bd064f6fff   About a minute ago   134B
  • Builds in an instant ✅
  • Pushes to registry almost instantly ✅
  • Minimal CI/CD load ✅

As long as the code doesn’t change, this image can be reused, improving cache hit rates.

This approach of “packaging only application code as an image” brings a completely different worldview
from traditional “all-in-one containers.”

With Image Volume, you can completely separate the application code lifecycle from the runtime lifecycle.

📦 Instant Base Image Patches! See the Diff

With ImageVolume, following security patches for base images becomes surprisingly easy.

✅ Pattern 1: Fixed Tags with Manual Updates

For example, when a vulnerability patch is released for the node:22.15.0 base image:

-    image: mirror.gcr.io/node:22.15.0
+    image: mirror.gcr.io/node:22.15.1

Just update the Pod‘s image and redeploy – that’s it!
No changes needed to the “app code” image!
No CI rebuilds needed. Just change one line of YAML, and security patches are applied instantly.

🔄 Pattern 2: Floating Tags (node:22) for Automatic Updates

For a more “fully automated” approach, use major version tags (node:22) instead of minor versions,
and set imagePullPolicy: Always. Patches are applied with just a Pod restart.

image: node:22
imagePullPolicy: Always

This way, updates from 22.15.0 → 22.15.122.16.0 are automatically applied with just Pod restarts.

:::caution ⚠️ Caution
While this method always incorporates the latest patches,
there’s also the risk of immediately hitting regressions (bugs in new versions).
Careful consideration is needed for production environments where stability is paramount.

Explicit tags are safer for production.
:::

💡 Summary

Strategy Benefits Considerations
Fixed tags (node:22.15.1) Stable, predictable behavior Manual updates required
Floating tags + Pull Always (node:22) Latest patches auto-applied May upgrade to unintended versions

Choose a tag strategy that balances risk and automation based on your situation and environment.

🧪 Try It Now! Complete Automated Script Included

For those eager to experience the Image Volume world, here’s a one-shot verification script:

✅ Full Verification Script:

#!/usr/bin/env bash
set -euox pipefail

# Create a temporary file for the upstream script
TMP_SCRIPT=$(mktemp)
echo "Downloading upstream kind-with-registry.sh to $TMP_SCRIPT"

# Download the upstream script
curl -fsSL https://kind.sigs.k8s.io/examples/kind-with-registry.sh -o "$TMP_SCRIPT"

# Patch and execute the script
# This adds the ImageVolume feature gate after the apiVersion line
echo "Patching and executing the script with ImageVolume feature gate enabled"
awk '
  /^apiVersion: kind.x-k8s.io/v1alpha4/ {
    print
    print "featureGates:n  ImageVolume: true"
    next
  }
  {print}
' "$TMP_SCRIPT" | bash -s

##############################################################################
# 2. Build & push "code-only" OCI image
##############################################################################
WORK=$(mktemp -d)
echo "Workdir: $WORK"
mkdir -p "$WORK/app"

cat > "$WORK/app/index.js" <<'JS'
const http = require('http');
http.createServer((_, res) => res.end('Hello from OCI volume!n'))
    .listen(process.env.PORT || 80);
JS

cat > "$WORK/Dockerfile" <<'DOCKER'
FROM scratch
COPY app /
DOCKER

RAND_TAG=$(openssl rand -hex 4)
FULL_IMAGE="localhost:5001/hello-app:${RAND_TAG}"

docker build -f "$WORK/Dockerfile" -t "${FULL_IMAGE}" "$WORK"
docker push "${FULL_IMAGE}"

##############################################################################
# 4. Apply Pod manifest (referencing ImageVolume)
##############################################################################
cat > "$WORK/node-demo.yaml" <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: node-demo
spec:
  containers:
  - name: runtime
    image: mirror.gcr.io/node:22-slim
    command: ["node", "/usr/src/app/index.js"]
    workingDir: /usr/src/app
    ports: [{containerPort: 80}]
    volumeMounts:
    - mountPath: /usr/src/app
      name: app-src
  volumes:
  - name: app-src
    image:
      reference: ${FULL_IMAGE}
      pullPolicy: IfNotPresent
EOF

sleep 10
kubectl apply -f "$WORK/node-demo.yaml"
kubectl wait --for=condition=Ready pod/node-demo --timeout=120s

##############################################################################
# 5. Verify it works
##############################################################################
kubectl port-forward pod/node-demo 3000:80 >/dev/null &
PF=$!
sleep 2
echo -n "curl → "
curl http://localhost:3000
kill $PF

cat <<EOM

✅ Done! Node.js is running with code mounted via ImageVolume.

Cleanup:
  kind delete cluster
  rm -rf ${WORK}
EOM

Just copy-paste and run this script to launch a Node.js app using Image Volume in your local environment!

🎯 Use Cases: Where This Shines

Use Case Traditional Method With Image Volume
Node.js code distribution
(e.g., node:22.15.1)
Rebuild with code baked into base Separate code image, just mount it
Python library sharing
(e.g., python:3.11-slim)
Build for each app Fix common env as base, manage apps separately
AI models or certificate deployment Pack everything into huge images Share models/certs as Image Volumes

✅ Conclusion: This is the Future Default

  • “Code-only” images mean faster builds & better cache hits
  • Base image updates = instant patches
  • Slimmer CI/CD, improved security

Even if “mounting images” didn’t quite click before,
experience firsthand how this mechanism will become the next-generation best practice.

Leave a Reply