Skip to main content

OCI

The OCI plugin builds and moves container images. oci.docker_build builds one from a Dockerfile with docker buildx; oci.image assembles one directly from target outputs, with no Dockerfile, no daemon, and nothing executed. oci.pull, oci.push, and oci.load move an image between a registry, heph's cache, and a local docker daemon. oci.index groups images built separately, one per platform, into a single multi-platform image. oci.runner turns a running container into a runner other targets can execute inside.

Driver​

A driver is the component that knows how to execute a target's action. This plugin registers eight drivers: oci.docker_build, oci.image, oci.layer, oci.index, oci.pull, oci.push, oci.load, and oci.runner.

Enabling it​

The OCI plugin is an external plugin — it is not compiled into the heph binary. It ships as a shared library (cdylib) with a manifest file (heph-oci-plugin.json) and takes no options.

.hephconfig
plugins:
- url: https://github.com/hephbuild/heph-artifacts-v1/releases/download/vlatest/heph-oci-plugin.json
checksum: sha256:<hex> # optional; pin from heph-oci-plugin.json.sha256

The checksum field is optional but recommended — it pins the manifest to a known digest so a tampered or misdelivered manifest is rejected before loading. See Pinning manifests with checksums for details.

Building an image​

Two drivers build an image. Pick by whether the image needs to run a command.

DriverUse whenNeeds
oci.docker_buildThe image needs a RUN step — installing packages, compiling, a multi-stage build.A running docker daemon and a docker buildx builder that can export to a file.
oci.imageThe image is a base plus files on top — a static binary, a few assets. Nothing needs to execute.Nothing. No daemon, no docker binary, no execution of any kind.

Prefer oci.image when it fits. Its cache key covers everything that can change the image: there is no base resolved from the network at build time, no un-hashed toolchain version, no secret value that can't be hashed. It also cross-builds for free — assembling a linux/arm64 image on an amd64 machine needs no emulation, because nothing runs. oci.docker_build is the escape hatch for a real build step, at the cost of leaving some of those factors outside the cache key — see What oci.docker_build does not hash.

oci.docker_build​

BUILD
target(
name = "img",
driver = "oci.docker_build",
dockerfile = "Dockerfile",
context = [":srcs"],
build_args = {"VERSION": "1.2.3"},
platforms = ["linux/amd64", "linux/arm64"],
)
FieldTypeDefaultDescription
dockerfilestring"Dockerfile"The Dockerfile, as either a target address or a path. An address (starting with : or //) makes the target that produces it a dependency, hashed on its own. A path is relative to the target's package and must be produced by a context dependency (a plain checked-in Dockerfile comes from the Filesystem provider). Absolute paths are rejected.
contextlist or dict of string → target addresses{}Build-context dependencies. A bare list is the default group, exported to the build as the SRC build arg; a dict names groups, each exported as SRC_<GROUP> (context-relative paths of that group's outputs). Group names are uppercased and non-alphanumerics become _.
context_by_platformdict of platform → (list or dict as context){}Context dependencies that differ per platform, keyed "os/arch" — e.g. {"linux/amd64": {"bin": [":bin_amd64"]}, "linux/arm64": {"bin": [":bin_arm64"]}}. One buildx invocation builds every platform from one context, so a dependency that differs by architecture (a cross-compiled binary, for example) can't go in context — it stages under a per-platform prefix instead, and the Dockerfile selects with BuildKit's TARGETPLATFORM build arg. Group names must not collide with context; every platform in platforms needs an entry and vice versa; requires platforms to be set explicitly.
basesdict of string → target address{}Base images, by build-context name → a single oci.pull(layout = True) (or oci.image/oci.docker_build) target. Wired to docker buildx --build-context <name>=…, so the Dockerfile does FROM <name> (and can COPY --from=<name> for multi-stage). A multi-platform build needs the base pulled with all_platforms = True too.
formatstring"oci"Archive format: oci or docker.
build_argsdict of string → string{}--build-arg values. Hashed — they change the image. A key may not contain =.
stagestringunsetBuild a specific stage (--target) of a multi-stage Dockerfile — the name in FROM … AS <stage>. Named stage rather than buildx's target, since a heph target is a different thing.
outstring<target name>.tarOutput archive filename, relative to the target's package. Must be a bare filename.
platformsstring[]the builder's own defaultTarget platforms (--platform), e.g. ["linux/amd64", "linux/arm64"]. Left empty, the builder's default platform is resolved at parse time (docker buildx inspect --bootstrap) and folded into the cache key, so an arm64 laptop and an amd64 CI runner don't compute the same key for different images. A multi-platform build needs a container-driver builder — see The builder — the default daemon builder only builds one platform, and format = "docker" can't hold more than one image at all.
builderstringbuildx's current builderThe docker buildx builder to build on (--builder). A hashed input — and the reason BUILDX_BUILDER is stripped from the build's environment: what matters is the name written here, not whatever the host happens to have selected.
secretsstring[][]BuildKit build secrets, as raw --secret specs, e.g. ["id=token,env=TOKEN"], consumed in the Dockerfile via RUN --mount=type=secret. The spec string is hashed; the secret's value is not (and cannot be). src= sources are rejected — they'd resolve against a working directory outside the sandbox; use env=, or stage the file through context.
sshstring[][]SSH forwarding, as raw --ssh specs, e.g. ["default"], consumed via RUN --mount=type=ssh. Same hashing caveat and src= restriction as secrets.
cache_fromstring[][]BuildKit --cache-from refs, e.g. ["type=registry,ref=reg/img:cache"]. A build optimization, excluded from the input hash — never busts the heph cache.
cache_tostring[][]BuildKit --cache-to refs, e.g. ["type=registry,ref=reg/img:cache,mode=max"]. Also excluded from the input hash.
cachebool or dictboth tiers onCaching for the built archive. See Cache control.

Two output groups: the default group ("") is the archive; digest is a plain-text file holding the built image's digest, for a downstream target to read without unpacking the archive:

BUILD
target(
name = "deploy",
driver = "bash",
deps = {"digest": "//app:img|digest"},
run = "envsubst < manifest.tmpl.yaml > $OUT",
out = "manifest.yaml",
)

The builder​

oci.docker_build needs docker buildx with a builder that can write an image archive to a file. The plain docker driver — what a stock Docker Engine selects by default — cannot: --output type=oci,dest=… and type=docker,dest=… both fail on it, and with them every oci.docker_build target regardless of format. Either turn on the daemon's containerd image store, or create a container-driver builder and name it:

terminal
$ docker buildx create --name heph --driver docker-container
BUILD
target(name = "img", driver = "oci.docker_build", builder = "heph", ...)

Multi-platform builds have the same requirement, plus a container-driver builder specifically — the default daemon builder only builds one platform at a time.

What oci.docker_build does not hash​

oci.docker_build's cache key covers the build context, Dockerfile, build args, stage, platforms, and base-image contexts — an unchanged context is a cache hit, and the archive is served from cache without rebuilding. It does not cover everything that can change the resulting image bytes:

  • FROM base images not passed through bases. BuildKit resolves a plain FROM alpine:3.20 itself, from the network — whatever that tag pointed at when the machine that populated the cache last resolved it. Pin FROM by @sha256:, or pull the base with oci.pull and wire it through bases, which is a hashed input.
  • Anything RUN fetches — apt-get, curl, npm ci. Execution-time downloads are neither content-addressed nor verified.
  • Secret and SSH values. The spec strings are hashed; what the agent or environment actually hands the build is not.
  • The host docker / buildx version. BuildKit changes output-visible defaults across releases (attestations, compression), so two machines on different versions can produce different archives from an identical cache key.
  • .dockerignore. Nothing stages one, so unless a context target produces it the build sees none — a heph-built image can differ from the same docker build run by hand in the repo.

The build's environment is not on this list: every oci.docker_build runs with a cleared environment and an explicit passthrough allowlist, so a stray BUILDX_BUILDER or SOURCE_DATE_EPOCH on the host cannot change the build behind the cache key's back.

oci.image and oci.layer​

oci.layer turns target outputs into one image layer — a tar of a file tree, nothing executes. oci.image stacks oci.layer targets on an optional base into a built image. Splitting the two means a big shared layer (static assets, a CA bundle) is built and cached once and reused by every image that lists it.

BUILD
target(
name = "bin_layer",
driver = "oci.layer",
srcs = [":server"],
prefix = "/usr/bin",
)

target(
name = "img",
driver = "oci.image",
base = ":base_alpine", # an oci.pull(layout = True) target
layers = [":bin_layer"],
entrypoint = ["/usr/bin/server"],
platforms = ["linux/amd64", "linux/arm64"],
)

oci.layer fields​

FieldTypeDefaultDescription
srcsstring[][]Targets whose files go into this layer, at their workspace-relative path, rewritten by strip and prefix. A srcs that produces no files is an error, not an empty layer — an image missing its binary would otherwise build, push, and start, and only fail when something tries to run it.
prefixstringrequiredWhere the files land inside the image, e.g. "/usr/bin". Required deliberately — a default of / (files at their workspace-relative path, e.g. /cmd/server/bin) is never what anyone wants, and would fail silently at docker run time instead of at build time.
stripstringunsetA leading workspace-relative path to drop before applying prefix — e.g. strip = "cmd/server" puts cmd/server/bin at <prefix>/bin. A strip that matches nothing is an error, not a silent no-op.
modestring (octal)"0755" for executable files, "0644" otherwiseFile mode as an octal string, e.g. "0755". A string because Starlark has no octal literal — mode = 755 would silently mean decimal 755. Setuid, setgid, and the sticky bit are rejected: heph's artifact hash records only the executable bit, so those permissions can't survive a rebuild from cache.
cachebool or dictboth tiers onCaching for the layer tar. See Cache control.

A layer's bytes are written to be reproducible regardless of the host: file timestamps, uid, and gid are zeroed; entries are sorted by path; symlinks are preserved (never followed); and the layer is written uncompressed.

oci.image fields​

FieldTypeDefaultDescription
basestringunsetBase image — an oci.pull(layout = True) (or another oci.image) target. Omitted, the image starts from scratch. This target's layers go on top; the base's config (Env, Entrypoint, Cmd, User, WorkingDir, Labels, ExposedPorts) is inherited, and anything set below wins. A multi-platform build needs a base with an instance per platform: oci.pull(layout = True, all_platforms = True).
layersstring[][]oci.layer targets, bottom to top. Order matters — a later layer's file shadows the same path in an earlier one.
layers_by_platformdict of platform → string[]{}Layers that differ per platform, appended after layers. Every platform in platforms needs an entry and vice versa — a missing one would silently ship an image without its binary for that platform.
platformsstring[]requiredTarget platforms, e.g. ["linux/amd64", "linux/arm64"]. No default: platforms is a label written into the image config with nothing relating it to layers, so defaulting to the host's architecture would let an amd64 laptop and an arm64 CI runner build different images from one BUILD file, or let an amd64 binary ship inside a config claiming arm64 — an exec format error at run time, correctly cached, with no build-time error.
entrypointstring[][]The image's entrypoint (ENTRYPOINT). Setting it clears any cmd inherited from the base, mirroring a Dockerfile's ENTRYPOINT.
cmdstring[][]The image's default arguments (CMD).
envdict of string → string{}Environment variables, merged over the base's by name.
userstringunsetThe user to run as (USER), e.g. "65532:65532".
workdirstringunsetThe working directory (WORKDIR).
labelsdict of string → string{}Image labels, merged over the base's.
exposed_portsstring[][]Ports the image declares (EXPOSE), e.g. ["8080/tcp"].
formatstring"oci"Archive format: oci or docker.
layoutboolfalseWrite an OCI layout directory instead of a single archive file — the shape another image's base, and oci.docker_build's bases, consume.
outstring<target name>.tar, or .oci with layout = TrueOutput filename (or directory name), relative to the target's package. Must be a bare name.
cachebool or dictboth tiers onCaching for the built image. See Cache control.

Like oci.docker_build, oci.image has a default output group (the archive/layout) plus a digest group holding the image digest as text.

Multi-platform images​

oci.docker_build(platforms = [...]) and oci.image(platforms = [...]) build every platform from one recipe — one Dockerfile (or one set of layers), with per-platform differences limited to context_by_platform / layers_by_platform. When the platforms genuinely need different recipes — a different base, a different package manager, a stage that only exists on one architecture — build them as separate targets and group the results with oci.index.

BUILD
target(
name = "amd64",
driver = "oci.docker_build",
dockerfile = "Dockerfile.amd64",
context = [":srcs"],
platforms = ["linux/amd64"],
)

target(
name = "arm64",
driver = "oci.docker_build",
dockerfile = "Dockerfile.arm64",
context = [":srcs"],
platforms = ["linux/arm64"],
)

target(
name = "img",
driver = "oci.index",
images = [":amd64", ":arm64"],
)

//pkg:img is then one image everywhere downstream: oci.push sends the whole manifest list under one tag, oci.load picks the instance for the host, and oci.docker_build's bases resolves the right platform out of it.

Nothing is rebuilt or re-hashed by oci.index — it reads the layouts its inputs already produced and writes one index over them, copying blobs by reference. Each input still caches on its own inputs, so changing the arm64 Dockerfile doesn't rebuild amd64.

oci.index fields​

FieldTypeDefaultDescription
imagesstring[]requiredThe images to group, one per platform — oci.docker_build, oci.image, oci.pull, or another oci.index target. Each contributes every instance it holds, so grouping two single-platform builds is the common case, but a multi-platform input is merged rather than rejected. Two inputs claiming the same platform is an error.
formatstring"oci"docker is rejected — a docker-format archive holds a single image, which is the one thing this rule doesn't produce.
layoutboolfalseWrite an OCI layout directory instead of a single archive file.
outstring<target name>.tar, or .oci with layout = TrueOutput filename (or directory name), relative to the target's package. Must be a bare name.
cachebool or dictboth tiers onCaching for the grouped image. See Cache control.

Moving images​

oci.pull​

Pulls an image from a registry into a cacheable archive output — the image-world analogue of HTTP Fetch: bytes come from the network, not from other targets, and the pulled archive is a cacheable target output, so a base image shared by many oci.docker_build or oci.image targets is pulled once and served from cache thereafter.

BUILD
target(
name = "alpine",
driver = "oci.pull",
ref = "alpine:3.20",
layout = True,
)
FieldTypeDefaultDescription
refstringrequiredSource image reference, e.g. docker.io/library/alpine:3.20 or, pinned, alpine@sha256:….
layoutboolfalseWrite an OCI layout directory instead of a single archive file — the shape oci.docker_build's bases and another image's base consume. Without it the pulled archive can only be pushed or loaded.
platformsstring[]Linux on the host's architectureWhich platforms to pull out of a multi-platform manifest list, as "os/arch". One entry selects one instance; several keep an index holding exactly those. Always part of the cache key. Mutually exclusive with all_platforms.
all_platformsboolfalsePull every instance of the manifest list instead of naming them, keeping the whole index. This is what a base for a multi-platform oci.docker_build/oci.image needs.
outstring<target name>.tar, or .oci with layout = TrueOutput filename (or directory name), relative to the target's package. Must be a bare name.
insecureboolfalsePull from an insecure (HTTP / self-signed) registry: plain HTTP, certificate validation off.
credentialsstring[][]Credential targets to pull with, presented with heph.auth.docker([...]). See Registry authentication.
ambient_credentialsboolfalsePull with the host's own docker login. Not combinable with credentials.
cachebool or dictboth tiers onCaching for the pulled archive. A pull is content-addressed only when ref is digest-pinned. See Cache control.

The platform is always resolved to a concrete os/arch and always hashed, even when platforms is left at its default — leaving it implicit would make an arm64 and an amd64 machine share one cache entry for two different images, and would fail outright on macOS, where there's no darwin instance to pull from a Linux image.

Pinning for reproducibility​

heph caches a pull on the ref string, not on registry content — a docker.io/library/alpine:3.20 moving to point at new bytes still serves the stale, already-cached archive. Pin by digest for a reproducible pull:

BUILD
target(
name = "alpine",
driver = "oci.pull",
ref = "alpine@sha256:9f2c4e1b7a0d38560c1a4c9b6e...",
layout = True,
)

Leaving ref on a mutable tag logs a warning at parse time, on every run:

WARN oci.pull: pulling a mutable tag "alpine:3.20" — heph caches on the ref
string, so a moved tag serves the stale archive; pin the ref by
@sha256:digest to make the pull reproducible

On an actual pull (never on a cache hit, since nothing is resolved then), a second warning names the exact digest the tag currently resolves to — paste it straight back into ref:

WARN oci.pull: "alpine:3.20" currently resolves to
"docker.io/library/alpine@sha256:9f2c4e1b7a0d38560c1a4c9b6e..." — pin
`src` to that to make this pull reproducible

(The message refers to the field internally as src — this is the same ref attribute set above.)

See Reproducibility for the same principle applied elsewhere in heph.

Shared blob cache​

Every oci.pull target draws downloaded registry blobs from one shared scratch cache, keyed by nothing but its own address — blobs are content-addressed by digest, so one store holds amd64 and arm64 layers side by side. Two images sharing a base layer download it once, not once per pull target. Nothing to configure; inspect or reclaim it like any other scratch:

terminal
heph tool scratch ls # the blob store shows up alongside your own caches
heph tool scratch rm --all # drop it if it ever needs a clean slate

oci.push​

Pushes an image archive to a registry. An action, not an artifact — it has an external side effect (the upload) and is therefore never cached; it runs every time it's requested.

BUILD
target(
name = "publish",
driver = "oci.push",
image = ":img",
ref = "registry.io/me/app:1.2",
)
FieldTypeDefaultDescription
imagestringrequiredTarget address of the image to push — a oci.docker_build, oci.image, or oci.index target. Only its archive output is consumed.
refstringrequiredDestination registry reference, e.g. registry.io/me/app:1.2. May use ${read://pkg:name} and ${image_hashout}, see Computed references.
insecureboolfalsePush to an insecure (HTTP / self-signed) registry: plain HTTP, certificate validation off.
credentialsstring[][]Credential targets to push with, presented with heph.auth.docker([...]). See Registry authentication.
ambient_credentialsboolfalsePush with the host's own docker login. Not combinable with credentials.

A multi-platform archive pushes every instance plus the manifest list that ties them together. Blobs the registry already has are skipped.

Computed references​

ref can take its repository from another target and its tag from the image itself:

BUILD
target(name = "registry", driver = "bash", run = "echo registry.io/me/app > $OUT", out = "registry.txt")

target(
name = "publish",
driver = "oci.push",
image = ":img",
ref = "${read://pkg:registry}:${image_hashout}",
)
VariableValue
${read://pkg:name}The contents of that target's single output. See Deferred values.
${image_hashout}The hash of the image archive, so every distinct image gets its own tag.

${image_hashout} is heph's content hash of the image, not the registry digest, so it won't match what docker images or the registry reports. Any other ${…} in ref is refused before anything runs, and a value produced by a ${read://…} target must not contain $. On success, the run logs the reference it pushed along with the registry digest.

oci.load​

Loads an image archive into the local docker daemon. Also an action, not cached — it mutates the daemon's image store and runs every time. Its output is the ref it tagged in the daemon — needed because the derived tag (see tag below) carries an input hash and can't be written down by hand, so a downstream target reads the ref instead of reconstructing it.

BUILD
target(
name = "load",
driver = "oci.load",
image = ":img",
)
terminal
$ heph run --cat-out //app:load
app_img:9f2c4e1b7a0d3856
$ docker run --rm "$(heph run --cat-out //app:load)"

A downstream target reads the ref the same way it reads any other dependency's output:

BUILD
target(
name = "smoke_test",
driver = "bash",
deps = [":load"],
run = "docker run --rm \"$(cat $SRC)\"",
)
FieldTypeDefaultDescription
imagestringrequiredTarget address of the image to load — a oci.docker_build, oci.image, or oci.index target. Only its archive output is consumed.
tagstringderivedLocal ref to give the loaded image. Takes either half of a ref, or both — whichever half is left out is derived. See below.
platformstringLinux on the host's architectureWhich instance to load out of a multi-platform archive, as "os/arch". A daemon holds one image per tag, so a multi-arch archive must be narrowed to one instance on the way in.

tag splits into a repository and a tag. Naming only one leaves the other derived — the derived tag changes if and only if the image changes, so two people building the same commit get the same tag, and it never points at whichever image happened to load last; the derived repository is the image target's address turned into a docker-legal name (//cmd/server:img becomes cmd_server_img). Whichever half is written is carried through as-is:

BUILD
target(driver = "oci.load", image = ":img") # app_img:9f2c4e1b7a0d3856
target(driver = "oci.load", image = ":img", tag = "app") # app:9f2c4e1b7a0d3856
target(driver = "oci.load", image = ":img", tag = "ghcr.io/me/app") # ghcr.io/me/app:9f2c4e1b7a0d3856
target(driver = "oci.load", image = ":img", tag = "app:dev") # app:dev

A tag with no : after its last / names a repository only — this includes a repository on a registry port, e.g. localhost:5000/app, which keeps a derived tag rather than being read as tag 5000/app. A tag that can't identify an image to load — empty, :dev, app:, or pinned to a digest (app@sha256:…) — is rejected at parse.

Registry authentication​

oci.pull and oci.push are anonymous by default. A target that needs a registry login says which identity it uses, in the BUILD file:

The target setsIt authenticates as
nothing (the default)anonymous
credentials = [...]those credentials only
ambient_credentials = Truethe host's own Docker login (~/.docker/config.json or $DOCKER_CONFIG, and any docker-credential-* helper it names, or podman's auth.json); anonymous for a registry it has nothing for

credentials and ambient_credentials are mutually exclusive; setting both is an error.

Declared credentials​

Declare a credential presented with heph.auth.docker, listing the registries it covers, and reference it from the target:

BUILD
ghcr = target(
name = "ghcr",
driver = "auth.credential",
sources = [heph.auth.env(["USERNAME", "PASSWORD"])],
present = heph.auth.docker(["ghcr.io"]),
)

target(driver = "oci.pull", name = "base", ref = "ghcr.io/acme/base@sha256:…", credentials = [ghcr])
target(driver = "oci.push", name = "push", image = ":img", ref = "ghcr.io/acme/app:1.2", credentials = [ghcr])
  • The credential must provide a username and a secret (password). A credential with no username is refused. The username depends on the registry: ghcr.io and GitLab accept any name, Google Artifact Registry wants oauth2accesstoken, ECR wants AWS, Docker Hub wants your account name.
  • The registry must be listed in heph.auth.docker([...]). Docker Hub aliases (docker.io, index.docker.io, registry-1.docker.io) are equivalent, so ["docker.io"] covers alpine.
  • A registry none of the listed credentials covers is an error naming the registry, and so are two credentials covering the same registry. A target never falls back to another identity.

Using your own docker login​

BUILD
target(driver = "oci.pull", name = "base", ref = "registry.corp/base@sha256:…", ambient_credentials = True)

Handy for a developer's laptop. Because the result then depends on whoever runs the build, prefer credentials for anything CI runs.

When a request fails, the error says whether it was anonymous and names both attributes.

Caching a credential-gated pull​

Credentials never enter the cache key. A pull by tag (ref = "registry.corp/base:1") resolves against the identity that ran it, and a registry that filters tags by permission can then hand one identity's image to another through a shared cache. heph warns when a tag-referenced pull uses credentials. Pin the ref by digest (@sha256:…), or set cache = False.

A digest-pinned pull is cached like any other: anyone who can read the remote cache can read the image.

note

oci.docker_build doesn't take credentials. To build on a private base image, pull it with oci.pull(layout = True, credentials = [...]) and pass it as bases.

Running targets inside a container​

oci.runner describes a running container as a runner other targets execute inside:

BUILD
target(
name = "load",
driver = "oci.load",
image = ":img",
tag = "app:runner",
)

target(
name = "runner",
driver = "oci.runner",
image = "app:runner",
deps = [":load"],
)

target(
name = "check",
driver = "bash",
runner = ":runner",
run = ["python --version"],
)
FieldTypeDefaultDescription
imagestringrequiredImage reference to run targets in, e.g. the tag oci.load gave it. Must already be in the local docker daemon.
depsstring[][]Targets that must build first — normally the oci.load target that puts image in the daemon. Hashed: rebuilding the image re-derives the runner.
run_argsstring[][]Extra docker run arguments for the held container — --network, additional -v mounts, --user. The workspace root and heph's own home are mounted automatically; do not repeat them here.

One container is held open for the whole build and each target runs inside it with docker exec. The runner's fingerprint is the image's content digest, resolved from the daemon at build time — retagging the image moves the digest, which re-keys every target that runs under it.

Cached locally, never remotely: the answer is a fact about this daemon's image store, and publishing it would let one machine's resolution key another's builds.

Cache control​

oci.docker_build, oci.image, oci.layer, oci.index, and oci.pull all accept cache as a bare bool or a dict with up to three keys:

KeyTypeDefaultMeaning
enabledbooltrueEnable local caching for this target.
remotebooltrueEnable remote caching for this target.
historyint1Number of past revisions to retain in the local cache (minimum 1).

A bare True sets both enabled and remote to true. A bare False disables both. oci.push, oci.load, and oci.runner have no cache field — as actions with an external side effect (or, for oci.runner, an answer scoped to this machine's own daemon), they're never remotely cached.