Oci

The OCI rules model image movement and deterministic image assembly without a Docker daemon. ociPull() fetches an immutable layout, ociBuild() composes file layers and metadata, ociPush() publishes a local image, and ociMirror() copies directly between registries. A managed Crane toolchain handles registry operations.

Available goals

GoalToolProduct module
imp gen-lockfilesCrane//rules/oci/toolchain

Set up Crane

Registry operations use the pinned Crane release provided by the OCI rule:

import "//rules/oci";

The release is downloaded into a shared named cache on first use. Pure local composition of a "scratch" image does not invoke Crane, but pulls, pushes, mirrors, and builds based on a pulled image do.

Pull a base and build an image

import { ociPull, ociBuild } from "//rules/oci";

export const alpine = ociPull({
    repo: "docker.io/library/alpine",
    digest: "sha256:...",
});

export const app = ociBuild({
    base: alpine,
    path: ".",
    layers: [{
        srcs: ["bin/server"],
        path: "/usr/local/bin",
        mode: "0755",
    }],
    entrypoint: ["/usr/local/bin/server"],
    env: { LOG_LEVEL: "info" },
    workdir: "/srv",
});

A pull requires exactly one of tag or digest. Tags are resolved on every invocation because they can move; the resolved digest and image contents are then stored in the shared OCI cache. Prefer a digest when reproducibility matters.

ociBuild accepts an ociPull/ociBuild target as its base or the literal "scratch". It does not interpret a Dockerfile and cannot run RUN commands. Each layer stages workspace files selected by srcs at the requested image path, with optional exclusions, ownership, and mode. Layer tarballs, config, manifest, and OCI index are assembled deterministically, so identical inputs produce identical image content.

Build and package locally

imp build //images:app
imp package //images:app

build leaves the OCI layout in the build graph/CAS. package writes dist/images/app/image.tar, an OCI archive suitable for tools such as podman load or docker load. Pulled images can be packaged in the same way.

Push or mirror

import { ociPush, ociMirror } from "//rules/oci";

export const publish = ociPush({
    image: app,
    repo: "registry.example.com/acme/server",
    tag: "latest",
});

export const mirror = ociMirror({
    from: { repo: "docker.io/library/alpine", tag: "3.23" },
    to: { repo: "registry.example.com/mirror/alpine", tag: "3.23" },
});

Select either target with imp build. Push and mirror are intentionally impure: they perform registry side effects on every invocation and are never replayed from the task cache. Mirror uses a registry-to-registry Crane copy and does not materialize the image locally.

Credential sourcing is not implemented yet. The authentication seam is wired through every registry operation, but currently supplies no tools or environment, so these rules are presently suitable for public/anonymous registries only. Do not assume an ambient Docker login will be visible inside the hermetic execution environment.

The current image builder emits one image manifest. Multi-platform indexes and Dockerfile-compatible command execution are outside this rule's present surface.

Targets

ociPull

const ociPull

Declare an OCI image pull label: fetches repo:tag (or repo@digest) into the shared oci-storage named cache as an OCI-layout directory, for use as an ociBuild() base or an ociPush() source.

ParameterTypeDescription
optsobject
opts.repostringImage repository, e.g. "docker.io/library/alpine" or "ghcr.io/org/image".
[opts.tag]stringMutable tag — resolved to a digest at build time via crane digest.
[opts.digest]stringImmutable sha256:... digest — skips the resolve step entirely.

Returns: object Exported label handle.

ociBuild

const ociBuild

Declare an OCI image build label: composes base plus layers into a new image by hand-assembling its OCI layout (layer blobs, config blob, manifest blob, index.json) — no Dockerfile, no commands executed inside a container. One or more file-staging specs; each becomes one deterministic layer tarball. Deliberately no platforms/multi-arch surface yet — single-manifest only for this pass. When multi-arch support is added, it attaches here (an OCI index assembled via crane index append over one build per platform) without needing to change layers'/base's shape.

ParameterTypeDescription
optsobject
[opts.path="."]stringDirectory (relative to the declaring BUILD.js) layers[].srcs glob against.
opts.baseobject"scratch"
opts.layersArray<{srcs: string[], path: string, exclude?: string[], uid?: number, gid?: number, mode?: string}>
[opts.entrypoint]string[]Image ENTRYPOINT.
[opts.cmd]string[]Image CMD.
[opts.env]Record<string,string>Image environment variables.
[opts.labels]Record<string,string>Image labels.
[opts.user]stringImage USER.
[opts.workdir]stringImage WORKDIR.

Returns: object Exported label handle.

ociPush

const ociPush

Declare an OCI image push label: publishes an ociPull()/ociBuild() image to repo:tag.

ParameterTypeDescription
optsobject
opts.imageobjectAn ociPull()/ociBuild() label handle.
opts.repostringDestination repository.
opts.tagstringDestination tag.

Returns: object Exported label handle.

ociMirror

const ociMirror

Declare an OCI mirror label: re-hosts an image from one registry ref to another via crane copy (registry-to-registry, no local disk materialization) — e.g. mirroring a public upstream image into an internal registry without a Dockerfile.

ParameterTypeDescription
optsobject
opts.fromobjectSource ref: { repo, tag } or { repo, digest }.
opts.toobjectDestination ref: { repo, tag }.

Returns: object Exported label handle.