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@40748316f5487a0b9755b5c5f46e3d15403153fdEntrypoint
Return Type
Oci Example
dagger -m github.com/z5labs/devex/daggerverse/oci@40748316f5487a0b9755b5c5f46e3d15403153fd 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@40748316f5487a0b9755b5c5f46e3d15403153fd call \
credential-resolution-self-testfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| host | String ! | - | Registry host, as it appears in an image reference: “ghcr.io”, “registry.example.com:5000”. Ignored when service is set. |
| username | String | - | Username for basic authentication. Omit for an anonymous client. |
| password | Secret | - | Password or token for basic authentication. |
| bearerToken | Secret | - | A bearer token to send as-is, for a registry that issued one. Used only when no username or password was given. |
| dockerConfig | Secret | - | 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. |
| service | Service | - | A Dagger-hosted registry to reach over the session network instead of over the public network. |
| insecure | Boolean | - | Talk plain HTTP and skip TLS verification. Off by default. |
| caCert | File | - | 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. |
| clientCert | File | - | A PEM-encoded client certificate to authenticate with, for a registry that authenticates callers by mutual TLS. Must be given together with clientKey. |
| clientKey | Secret | - | 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@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host stringfunc (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@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
hostfunc (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@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
usernamefunc (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@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
insecurefunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| repository | String ! | - | Repository holding the subject. The referrer lands here too — the referrers API is per-repository. |
| subject | String ! | - | Digest of the manifest being attached to, e.g. “sha256:…”. |
| content | File ! | - | The bytes to attach: one file, one layer. |
| artifactType | String ! | - | The referrer’s artifact type, which is what Referrers filters on. |
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
attach --repository string --subject string --content file:path --artifact-type stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| srcRef | String ! | - | Fully-qualified source reference, e.g. “docker.io/library/alpine:3.20” or “/@sha256:…”. |
| repository | String ! | - | Destination repository on this registry. |
| tag | String ! | - | Destination tag. |
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
copy --src-ref string --repository string --tag stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| repository | String ! | - | Repository holding the content. |
| digest | String ! | - | Digest of the blob or manifest, e.g. “sha256:…”. |
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
fetch --repository string --digest stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| repository | String ! | - | Repository holding the manifest. |
| reference | String ! | - | Tag or digest. |
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
manifest --repository string --reference stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| repository | String ! | - | Repository path within the registry. |
| tag | String ! | - | Tag to publish under. |
| contents | Directory ! | - | Files to upload. Must contain at least one file. |
| artifactType | String ! | - | 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@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
push-artifact --repository string --tag string --contents DIR_PATH --artifact-type stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| repository | String ! | - | Repository path within the registry, e.g. “z5labs/myapp”. |
| tag | String ! | - | 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@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
push-image --repository string --tag stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| repository | String ! | - | Repository holding the subject. |
| subject | String ! | - | Digest of the manifest whose referrers are wanted. |
| artifactType | String | - | Restrict the listing to one artifact type. Empty lists them all. |
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
referrers --repository string --subject stringfunc (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
| Name | Type | Default Value | Description |
|---|---|---|---|
| repository | String ! | - | Repository holding the tag. |
| tag | String ! | - | Tag to resolve. |
Example
dagger -m github.com/z5labs/devex/daggerverse/oci@40748316f5487a0b9755b5c5f46e3d15403153fd call \
registry --host string \
resolve --repository string --tag stringfunc (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)
}