Skip to content

Mount an OCI image read-only into an actor - #923

Open
Ron Lev (ronlv10) wants to merge 5 commits into
agent-substrate:mainfrom
ronlv10:actortemplate-image-volume
Open

Mount an OCI image read-only into an actor#923
Ron Lev (ronlv10) wants to merge 5 commits into
agent-substrate:mainfrom
ronlv10:actortemplate-image-volume

Conversation

@ronlv10

@ronlv10 Ron Lev (ronlv10) commented Aug 13, 2026

Copy link
Copy Markdown

Fixes #783
Adds an image source to ActorTemplate's VolumeSource: a container can mount the contents of an OCI image it does not run. This is how tooling gets into images built by third parties without rebuilding them.

spec:
  containers:
  - name: sandbox
    image: docker.io/example/benchmark@sha256:...
    command: ["/ate/agent"]
    volumeMounts:
    - name: agent
      mountPath: /ate
  volumes:
  - name: agent
    image:
      reference: registry.example.com/agent@sha256:...

How it works

atelet pulls the image through the existing layer cache and records the volume's layers in the bundle's overlay spec, next to the rootfs layers. ateom composes the volume inside the bundle — the cached layers with no writable layer on top, so the mount is read-only — and the container binds it at the declared path. The volume is composed per container: containers of one actor may mount the same volume, and each gets its own mount point inside its own bundle, all backed by the same shared layers. On resume the volume is re-composed the same way.

References must be digest-pinned, the same rule as container images: a snapshot is only valid against the exact bytes it was taken with.

On micro-VMs the volume rides the same read-only virtio-fs share as the container rootfs: ateom stages each composed volume beside the rootfs on the host, and the guest binds it into the container at the declared path.

@google-cla

google-cla Bot commented Aug 13, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@ronlv10
Ron Lev (ronlv10) force-pushed the actortemplate-image-volume branch 3 times, most recently from 4e11b94 to 5cb2ccc Compare August 13, 2026 19:34
@ronlv10
Ron Lev (ronlv10) marked this pull request as ready for review August 13, 2026 19:37
@ronlv10
Ron Lev (ronlv10) force-pushed the actortemplate-image-volume branch 3 times, most recently from 8eca1d8 to 416ac52 Compare August 13, 2026 21:49
A volume can name an OCI image; the reference must be digest-pinned and
mounted by at least one container. ateapi projects it into the workload
spec on its own volume type.
@ronlv10
Ron Lev (ronlv10) force-pushed the actortemplate-image-volume branch from 416ac52 to 6137852 Compare August 13, 2026 21:54

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huge +1 in concept, it's a fair bit of code understandably, I need to read back through it in depth. attaching one plausible looking agent finding now.

// ImageDigest is the manifest digest the volume's ref resolved to, in the
// same form and for the same reason as OverlaySpec.ImageDigest: the GC's
// root-set scan protects an image by digest.
ImageDigest string `json:"imageDigest,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 blocking 🔴 – Nothing roots these, so the GC can evict an image volume's layers while an actor is running on them. addSpecRoots in internal/imagecache/gc.go reads spec.ImageDigest and spec.Layers and never looks at spec.ImageVolumes, so a volume's digest never reaches RootSet.ImageDigests and its layer hexes never reach LayerHexes or LayerSets.

Both eviction guards then miss. The volume's image record fails the digest check and is retired, which drops its layers' refcount to zero, and the bundle-spec fallback that would otherwise save them is keyed on LayerHexes. The layers go while they are bind-mounted at <bundle>/volumes/<name>. A resume is the visible failure: setupImageVolumes calls FinalizeLayer on a directory that no longer exists.

The watermark makes it likelier rather than rarer — the GC fires under cache pressure, which is the state this feature encourages by adding images per actor.

addSpecRoots walking spec.ImageVolumes covers it. Worth giving each volume its own LayerSets signature rather than folding its layers into the rootfs one, since the signature is meant to match a record's exact layer list.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch- added !

The bundle overlay spec gains ImageVolumes; ateom composes each at
ateompath.ImageVolumeMountPath — a bind for one layer, an upperless
overlay for several, FinalizeLayer first so whiteouts hold.
resolveImageVolumes pulls each mounted image through the layer cache and
records its layers in the overlay spec; the OCI spec binds the composed
volume read-only at the declared path.
The micro-VM runtime builds its own guest OCI spec, so image volumes are
carried explicitly: ateompb.Container gains image_volume_mounts, staging
binds each composed volume into the read-only share beside the rootfs,
and the guest re-adds the binds at the declared paths.
A three-layer fixture pushed at test time: every layer visible, an upper
layer shadows a lower one, a whiteout hides a file, writes are refused,
and the volume survives suspend/resume.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an image volume source to ActorTemplate

2 participants