Dagger
Search

oci

knows how to talk to an OCI registry and nothing about why.

It does not choose tags, decide when to publish, or know what the bytes it
uploads mean. Callers that need a registry — z5labs' GoApp publish path,
ssdd's baselines — get one here instead of each growing their own.

The module is pure Go. Container.Publish cannot see session service
bindings, which is why callers used to shell out to a container that
could; a Go client running in the module's own runtime reaches a Dagger
service directly, so this wraps go-containerregistry and oras-go rather
than pinning tool images.

Installation

dagger install github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391

Entrypoint

Return Type
Oci
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
func (m *MyModule) Example() *dagger.Oci  {
	return dag.
			Oci()
}
@function
def example() -> dagger.Oci:
	return (
		dag.oci()
	)
@func()
example(): Oci {
	return dag
		.oci()
}

Types

Oci 🔗

Oci is the module’s entrypoint. It holds no state; every operation is reached through Registry.

credentialResolutionSelfTest() 🔗

CredentialResolutionSelfTest checks how a Docker config is read: which entry a host matches, which of an entry’s forms wins, when a credential helper is refused, and what a malformed config is allowed to say.

It sits on the module rather than in tests/ because it checks unexported resolution that never reaches the network, and because each case is one shape of a config file — reaching them all through tests/ would mean a registry per shape to assert on a string comparison. The live tests still prove a resolved credential authenticates; this proves the right one was resolved.

It runs in process and needs no container, so it is cheap enough to be a check of its own.

Return Type
Void !
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 credential-resolution-self-test
func (m *MyModule) Example(ctx context.Context)   {
	return dag.
			Oci().
			Credentialresolutionselftest(ctx)
}
@function
async def example() -> None:
	return await (
		dag.oci()
		.credentialresolutionselftest()
	)
@func()
async example(): Promise<void> {
	return dag
		.oci()
		.credentialResolutionSelfTest()
}

registry() 🔗

Registry binds one registry host and its credentials. service, when non-nil, is a Dagger-hosted registry reached by hostname rather than over the public network — its endpoint replaces host as the address dialled, because a session service’s hostname is assigned by the engine and cannot be predicted by the caller.

There are three ways to authenticate and they have a fixed precedence: username/password beats bearerToken, which beats dockerConfig, and supplying none of them is an anonymous client. See Registry.credential for why the order is that one and why a 401 never falls through to the next source.

insecure is explicit and defaults to off: it means plain HTTP and no TLS verification. It is deliberately not inferred from service being set — that inference is a test affordance leaking into production behaviour. It is spelled insecure rather than tlsVerify because a bool defaulting to true is unsettable from the CLI.

caCert, clientCert and clientKey are the TLS material, and all three are independent of insecure. A registry fronted by a private CA is reached by naming that CA, with verification still on — turning verification off to work around a missing trust anchor is the outcome this exists to remove.

Return Type
Registry !
Arguments
NameTypeDefault ValueDescription
hostString !-

Registry host, as it appears in an image reference: “ghcr.io”, “registry.example.com:5000”. Ignored when service is set.

usernameString -

Username for basic authentication. Omit for an anonymous client.

passwordSecret -

Password or token for basic authentication.

bearerTokenSecret -

A bearer token to send as-is, for a registry that issued one. Used only when no username or password was given.

dockerConfigSecret -

A Docker config file — the contents of ~/.docker/config.json — to read this host’s credentials out of. Used only when nothing more specific was given. Credential helpers named by the file are not run; a host that resolves through one fails naming it.

serviceService -

A Dagger-hosted registry to reach over the session network instead of over the public network.

insecureBoolean -

Talk plain HTTP and skip TLS verification. Off by default.

caCertFile -

A PEM-encoded certificate authority to verify this registry’s certificate against, for a registry fronted by a private CA. It is added to the system trust store, not substituted for it, and it does not switch verification off.

clientCertFile -

A PEM-encoded client certificate to authenticate with, for a registry that authenticates callers by mutual TLS. Must be given together with clientKey.

clientKeySecret -

The PEM-encoded private key for clientCert. It crosses as a secret rather than a file because it is key material. Must be given together with clientCert.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string
func (m *MyModule) Example(host string) *dagger.OciRegistry  {
	return dag.
			Oci().
			Registry(host)
}
@function
def example(host: str) -> dagger.OciRegistry:
	return (
		dag.oci()
		.registry(host)
	)
@func()
example(host: string): OciRegistry {
	return dag
		.oci()
		.registry(host)
}

Registry 🔗

Registry is an authenticated handle on one registry host.

Every method carries a never-cache directive on its own doc-comment line: registry state is mutable and pushes are side-effecting, so the directive repeats on each chained method rather than living only on the factory.

host() 🔗

Host is the registry host this handle was built for.

Return Type
String !
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 host
func (m *MyModule) Example(ctx context.Context, host string) string  {
	return dag.
			Oci().
			Registry(host).
			Host(ctx)
}
@function
async def example(host: str) -> str:
	return await (
		dag.oci()
		.registry(host)
		.host()
	)
@func()
async example(host: string): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.host()
}

username() 🔗

Username is the basic-auth user, empty for an anonymous client.

Return Type
String !
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 username
func (m *MyModule) Example(ctx context.Context, host string) string  {
	return dag.
			Oci().
			Registry(host).
			Username(ctx)
}
@function
async def example(host: str) -> str:
	return await (
		dag.oci()
		.registry(host)
		.username()
	)
@func()
async example(host: string): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.username()
}

insecure() 🔗

Insecure reports whether this handle talks plain HTTP.

Return Type
Boolean !
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 insecure
func (m *MyModule) Example(ctx context.Context, host string) bool  {
	return dag.
			Oci().
			Registry(host).
			Insecure(ctx)
}
@function
async def example(host: str) -> bool:
	return await (
		dag.oci()
		.registry(host)
		.insecure()
	)
@func()
async example(host: string): Promise<boolean> {
	return dag
		.oci()
		.registry(host)
		.insecure()
}

attach() 🔗

Attach uploads content as an OCI referrer of subject and returns the referrer’s own digest.

subject is a manifest digest in this repository; it is resolved first, so attaching to something that is not there fails naming the digest rather than leaving a dangling referrer behind.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
repositoryString !-

Repository holding the subject. The referrer lands here too — the referrers API is per-repository.

subjectString !-

Digest of the manifest being attached to, e.g. “sha256:…”.

contentFile !-

The bytes to attach: one file, one layer.

artifactTypeString !-

The referrer’s artifact type, which is what Referrers filters on.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 attach --repository string --subject string --content file:path --artifact-type string
func (m *MyModule) Example(ctx context.Context, host string, repository string, subject string, content *dagger.File, artifactType string) string  {
	return dag.
			Oci().
			Registry(host).
			Attach(ctx, repository, subject, content, artifactType)
}
@function
async def example(host: str, repository: str, subject: str, content: dagger.File, artifacttype: str) -> str:
	return await (
		dag.oci()
		.registry(host)
		.attach(repository, subject, content, artifacttype)
	)
@func()
async example(host: string, repository: string, subject: string, content: File, artifactType: string): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.attach(repository, subject, content, artifactType)
}

copy() 🔗

Copy copies srcRef into repository:tag on this registry, preserving every manifest — a multi-platform source stays multi-platform, matching what skopeo copy --all did before this module existed. It returns the digest at the destination.

The source is read with this registry’s credentials when it lives on this registry, and anonymously otherwise; cross-registry copies needing source credentials are a follow-up, not a silent reuse of the destination’s.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
srcRefString !-

Fully-qualified source reference, e.g. “docker.io/library/alpine:3.20” or “/@sha256:…”.

repositoryString !-

Destination repository on this registry.

tagString !-

Destination tag.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 copy --src-ref string --repository string --tag string
func (m *MyModule) Example(ctx context.Context, host string, srcRef string, repository string, tag string) string  {
	return dag.
			Oci().
			Registry(host).
			Copy(ctx, srcRef, repository, tag)
}
@function
async def example(host: str, srcref: str, repository: str, tag: str) -> str:
	return await (
		dag.oci()
		.registry(host)
		.copy(srcref, repository, tag)
	)
@func()
async example(host: string, srcRef: string, repository: string, tag: string): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.copy(srcRef, repository, tag)
}

fetch() 🔗

Fetch downloads one blob or manifest by digest and returns it as a file.

Blobs are tried first and manifests second, because the two live at different registry endpoints and a caller holding a digest out of a manifest’s layer list has no reason to know which it is.

Return Type
File !
Arguments
NameTypeDefault ValueDescription
repositoryString !-

Repository holding the content.

digestString !-

Digest of the blob or manifest, e.g. “sha256:…”.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 fetch --repository string --digest string
func (m *MyModule) Example(host string, repository string, digest string) *dagger.File  {
	return dag.
			Oci().
			Registry(host).
			Fetch(repository, digest)
}
@function
def example(host: str, repository: str, digest: str) -> dagger.File:
	return (
		dag.oci()
		.registry(host)
		.fetch(repository, digest)
	)
@func()
example(host: string, repository: string, digest: string): File {
	return dag
		.oci()
		.registry(host)
		.fetch(repository, digest)
}

manifest() 🔗

Manifest returns the raw manifest JSON for a tag or a digest. It is raw rather than parsed because annotations, platforms and referrer subjects are all read back out of it, and re-encoding through a Go type would drop whatever this module does not model.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
repositoryString !-

Repository holding the manifest.

referenceString !-

Tag or digest.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 manifest --repository string --reference string
func (m *MyModule) Example(ctx context.Context, host string, repository string, reference string) string  {
	return dag.
			Oci().
			Registry(host).
			Manifest(ctx, repository, reference)
}
@function
async def example(host: str, repository: str, reference: str) -> str:
	return await (
		dag.oci()
		.registry(host)
		.manifest(repository, reference)
	)
@func()
async example(host: string, repository: string, reference: string): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.manifest(repository, reference)
}

pushArtifact() 🔗

PushArtifact pushes the files in contents to repository:tag as an OCI artifact of artifactType, and returns the manifest digest.

Every file in contents, at any depth, becomes one layer whose org.opencontainers.image.title annotation is its path relative to the directory root. Layers are ordered by that path so the same directory always produces the same manifest.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
repositoryString !-

Repository path within the registry.

tagString !-

Tag to publish under.

contentsDirectory !-

Files to upload. Must contain at least one file.

artifactTypeString !-

The artifact’s type, e.g. “application/vnd.example.sbom.v1+json”. This is what a consumer filters on when listing referrers.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 push-artifact --repository string --tag string --contents DIR_PATH --artifact-type string
func (m *MyModule) Example(ctx context.Context, host string, repository string, tag string, contents *dagger.Directory, artifactType string) string  {
	return dag.
			Oci().
			Registry(host).
			Pushartifact(ctx, repository, tag, contents, artifactType)
}
@function
async def example(host: str, repository: str, tag: str, contents: dagger.Directory, artifacttype: str) -> str:
	return await (
		dag.oci()
		.registry(host)
		.pushartifact(repository, tag, contents, artifacttype)
	)
@func()
async example(host: string, repository: string, tag: string, contents: Directory, artifactType: string): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.pushArtifact(repository, tag, contents, artifactType)
}

pushImage() 🔗

PushImage pushes container variants to repository:tag and returns the digest of what was pushed.

Multiple variants become one manifest list. The variants are materialized through a single AsTarball with the rest as platform variants, so the index this pushes is the one Dagger itself would have published — annotations, config and layer bytes included — rather than one this module reassembled.

repository and tag are separate parameters rather than one interpolated reference: it keeps caller-supplied values out of any string that gets re-parsed as something else, and it makes each half validatable.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
repositoryString !-

Repository path within the registry, e.g. “z5labs/myapp”.

tagString !-

Tag to publish under.

variants[Container ! ] !-

Platform variants. One variant pushes a single image manifest; more than one pushes a manifest list naming every platform.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 push-image --repository string --tag string
func (m *MyModule) Example(ctx context.Context, host string, repository string, tag string, variants []*dagger.Container) string  {
	return dag.
			Oci().
			Registry(host).
			Pushimage(ctx, repository, tag, variants)
}
@function
async def example(host: str, repository: str, tag: str, variants: List[dagger.Container]) -> str:
	return await (
		dag.oci()
		.registry(host)
		.pushimage(repository, tag, variants)
	)
@func()
async example(host: string, repository: string, tag: string, variants: Container[]): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.pushImage(repository, tag, variants)
}

referrers() 🔗

Referrers lists the artifacts attached to subject as a JSON array of OCI descriptors, newest registry ordering preserved.

It returns JSON rather than a typed object for two reasons: codegen has no map type, so annotations could not be modelled; and a module object returned from a never-cached call detaches in Dagger v0.21, so lazily reading its fields fails.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
repositoryString !-

Repository holding the subject.

subjectString !-

Digest of the manifest whose referrers are wanted.

artifactTypeString -

Restrict the listing to one artifact type. Empty lists them all.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 referrers --repository string --subject string
func (m *MyModule) Example(ctx context.Context, host string, repository string, subject string) string  {
	return dag.
			Oci().
			Registry(host).
			Referrers(ctx, repository, subject)
}
@function
async def example(host: str, repository: str, subject: str) -> str:
	return await (
		dag.oci()
		.registry(host)
		.referrers(repository, subject)
	)
@func()
async example(host: string, repository: string, subject: string): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.referrers(repository, subject)
}

resolve() 🔗

Resolve returns the digest a tag currently points at.

Return Type
String !
Arguments
NameTypeDefault ValueDescription
repositoryString !-

Repository holding the tag.

tagString !-

Tag to resolve.

Example
dagger -m github.com/z5labs/devex/daggerverse/oci@5ccf3c8f84dbe2e5b936ac2e009833d4025ed391 call \
 registry --host string \
 resolve --repository string --tag string
func (m *MyModule) Example(ctx context.Context, host string, repository string, tag string) string  {
	return dag.
			Oci().
			Registry(host).
			Resolve(ctx, repository, tag)
}
@function
async def example(host: str, repository: str, tag: str) -> str:
	return await (
		dag.oci()
		.registry(host)
		.resolve(repository, tag)
	)
@func()
async example(host: string, repository: string, tag: string): Promise<string> {
	return dag
		.oci()
		.registry(host)
		.resolve(repository, tag)
}