z5labs
and release pipelines for Go projects. Construct via the GoApp or GoLibfactories on Z5labs; call the terminal Ci method to run the pipeline.
Installation
dagger install github.com/z5labs/devex/daggerverse/z5labs@a40be0aa9edc19110bac18e0932430aebe03673bTypes
Z5Labs 🔗
Z5labs is the root module type. Construct project archetypes via GoApp / GoLib.
goApp() 🔗
GoApp wires up an opinionated CI/release pipeline for a package main
Go application. Call Ci to run checks + multi-arch buildpublish, or Builder to produce the same image single-arch locally.
Every binary GoApp builds is stamped at link time with the version and the commit it was built from, so an application can answer “which build am I running” without a second build definition beside this one. Declare these two package-level vars in your main package and they are filled in:
var (
version = "dev"
commit = "none"
)
The names are fixed by the module — main.version and main.commit —
and the values are taken from HEAD, never from a parameter. A tag
pointing at HEAD gives version the stripped tag name; anything else
gives “-”, the same rule the published image
tag follows, so the two agree by construction. commit is the short HEAD
SHA. Because both are functions of the commit alone, two builds of one
commit are byte-identical; there is no caller-supplied value that could
break that. Source without git metadata at HEAD is an error.
publishOn is a regex evaluated against source repo’s HEAD refs (after
normalizing refs/remotes/origin/X → refs/heads/X); matches trigger
publish. When registry is set, auth is required.
platforms defaults to [“linux/amd64”,“linux/arm64”].
registryService, when non-nil, is a Dagger-hosted registry reached over the session network instead of over the public network — used by tests against a local registry service and by callers whose private registry is itself a Dagger service. Its endpoint is assigned by the engine, so it replaces registry as the address published to; registry is still what decides that a publish happens at all.
insecure means plain HTTP and no TLS verification, and it is off unless the caller asks for it. It is deliberately not inferred from registryService being set: that inference made a caller who supplied a service for their own reasons silently publish over an unverified connection. It is spelled insecure rather than tlsVerify because a bool defaulting to true cannot be turned off from the CLI.
Return Type
GoApp !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| source | Directory ! | - | No description provided |
| pkg | String | "." | No description provided |
| binaryName | String | - | No description provided |
| publishOn | String | "^refs/heads/main$" | No description provided |
| registry | String | - | No description provided |
| authUsername | String | "ci" | No description provided |
| auth | Secret | - | No description provided |
| lintConfig | File | - | No description provided |
| platforms | [String ! ] | - | No description provided |
| registryService | Service | - | No description provided |
| insecure | Boolean | - | No description provided |
| idTokenRequestUrl | String | - | The CI provider’s OIDC token request endpoint —
|
| idTokenRequestToken | Secret | - | The bearer token for that endpoint —
|
| idTokenService | Service | - | A Dagger-hosted OIDC token endpoint, reached over the session network instead of the public one. When set, its engine-assigned endpoint replaces the host in idTokenRequestUrl; the path and query stay the caller’s, because those are part of the provider’s protocol. This exists for the same reason registryService does: a service’s address is not known until the engine assigns one, so it cannot be written into a URL ahead of time. It is used by the test suite, which runs a real token endpoint, and by anyone whose issuer is itself a Dagger service. |
| signingKey | Secret | - | A PEM-encoded EC private key to sign the provenance with, instead of an ephemeral key certified by the public sigstore CA. This selects the signing mode and nothing else: the workload identity token is still exchanged, and the predicate still says only what that token’s claims say. Use it for a build that cannot reach a public CA. Leaving it unset is keyless signing and is what a normal CI publish should do. |
Example
dagger -m github.com/z5labs/devex/daggerverse/z5labs@a40be0aa9edc19110bac18e0932430aebe03673b call \
go-app --source DIR_PATHfunc (m *MyModule) Example(source *dagger.Directory) *dagger.Z5LabsGoApp {
return dag.
Z5labs().
Goapp(source)
}@function
def example(source: dagger.Directory) -> dagger.Z5LabsGoApp:
return (
dag.z5labs()
.goapp(source)
)@func()
example(source: Directory): Z5LabsGoApp {
return dag
.z5labs()
.goApp(source)
}goLib() 🔗
GoLib wires up the checks-only pipeline for a Go library. v1 has no publish equivalent for libraries.
Return Type
GoLib !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| source | Directory ! | - | No description provided |
| lintConfig | File | - | No description provided |
Example
dagger -m github.com/z5labs/devex/daggerverse/z5labs@a40be0aa9edc19110bac18e0932430aebe03673b call \
go-lib --source DIR_PATHfunc (m *MyModule) Example(source *dagger.Directory) *dagger.Z5LabsGoLib {
return dag.
Z5labs().
Golib(source)
}@function
def example(source: dagger.Directory) -> dagger.Z5LabsGoLib:
return (
dag.z5labs()
.golib(source)
)@func()
example(source: Directory): Z5LabsGoLib {
return dag
.z5labs()
.goLib(source)
}GoApp 🔗
GoApp is the application archetype. Construct via Z5labs.GoApp.
builder() 🔗
Builder returns the local-dev sibling that produces the same image CI would publish, single-arch (host platform).
Return Type
Builder ! Example
dagger -m github.com/z5labs/devex/daggerverse/z5labs@a40be0aa9edc19110bac18e0932430aebe03673b call \
go-app --source DIR_PATH \
builderfunc (m *MyModule) Example(source *dagger.Directory) *dagger.Z5LabsBuilder {
return dag.
Z5labs().
Goapp(source).
Builder()
}@function
def example(source: dagger.Directory) -> dagger.Z5LabsBuilder:
return (
dag.z5labs()
.goapp(source)
.builder()
)@func()
example(source: Directory): Z5LabsBuilder {
return dag
.z5labs()
.goApp(source)
.builder()
}ci() 🔗
Ci runs the standardized GoApp pipeline: verify .git exists, run the shared check stages (fmt+vet+lint+test -race) once, build a scratch image per platform, then conditionally publish per the publishOn filter.
It returns the digest of what was published — the manifest list naming every platform variant, or the single image manifest when only one platform was built. Every matching ref publishes the same bytes under its own tag, so one digest describes them all. A run that publishes nothing — no ref matched, or no registry was configured — returns the empty string rather than an error.
Returning the digest rather than only an error is what lets a caller reference what was published: an attestation, a deployment manifest or a release note has to name an immutable artifact, and a tag is not one.
Every published image carries the standard OCI source annotations —
revision, source, created, and version on a tag build — on each
platform variant, and every published digest carries three
attestations: an SPDX and a CycloneDX SBOM per platform, produced by
the go module from the binaries this pipeline compiled, and a signed
SLSA provenance statement whose build identity comes from an exchanged
workload identity token. A publish that cannot produce provenance
fails rather than publishing without it.
Publish is a side-effecting operation against an external registry, so the whole pipeline is uncached — re-runs (e.g. after a retry, or after a new ref appears within the same engine session) must actually push.
Return Type
String ! Example
dagger -m github.com/z5labs/devex/daggerverse/z5labs@a40be0aa9edc19110bac18e0932430aebe03673b call \
go-app --source DIR_PATH \
cifunc (m *MyModule) Example(ctx context.Context, source *dagger.Directory) string {
return dag.
Z5labs().
Goapp(source).
Ci(ctx)
}@function
async def example(source: dagger.Directory) -> str:
return await (
dag.z5labs()
.goapp(source)
.ci()
)@func()
async example(source: Directory): Promise<string> {
return dag
.z5labs()
.goApp(source)
.ci()
}GoLib 🔗
GoLib is the library archetype. Construct via Z5labs.GoLib.
ci() 🔗
Ci runs the standardized check stages (fmt, vet, lint, test -race) against the supplied library source.
Return Type
Void ! Example
dagger -m github.com/z5labs/devex/daggerverse/z5labs@a40be0aa9edc19110bac18e0932430aebe03673b call \
go-lib --source DIR_PATH \
cifunc (m *MyModule) Example(ctx context.Context, source *dagger.Directory) {
return dag.
Z5labs().
Golib(source).
Ci(ctx)
}@function
async def example(source: dagger.Directory) -> None:
return await (
dag.z5labs()
.golib(source)
.ci()
)@func()
async example(source: Directory): Promise<void> {
return dag
.z5labs()
.goLib(source)
.ci()
}Builder 🔗
Builder produces the same image GoApp.Ci would publish, single-arch (host platform). Used for local development to verify the artifact before pushing. Both of its functions route through the same per-platform build Ci uses, so the binary carries the same version and commit stamp and a local build is the same artifact.
binary() 🔗
Binary returns the host-platform compiled binary as a *dagger.File.
Return Type
File ! Example
dagger -m github.com/z5labs/devex/daggerverse/z5labs@a40be0aa9edc19110bac18e0932430aebe03673b call \
go-app --source DIR_PATH \
builder \
binaryfunc (m *MyModule) Example(source *dagger.Directory) *dagger.File {
return dag.
Z5labs().
Goapp(source).
Builder().
Binary()
}@function
def example(source: dagger.Directory) -> dagger.File:
return (
dag.z5labs()
.goapp(source)
.builder()
.binary()
)@func()
example(source: Directory): File {
return dag
.z5labs()
.goApp(source)
.builder()
.binary()
}container() 🔗
Container returns the host-platform scratch image containing the compiled binary at /app/ with that path as entrypoint.
Return Type
Container ! Example
dagger -m github.com/z5labs/devex/daggerverse/z5labs@a40be0aa9edc19110bac18e0932430aebe03673b call \
go-app --source DIR_PATH \
builder \
containerfunc (m *MyModule) Example(source *dagger.Directory) *dagger.Container {
return dag.
Z5labs().
Goapp(source).
Builder().
Container()
}@function
def example(source: dagger.Directory) -> dagger.Container:
return (
dag.z5labs()
.goapp(source)
.builder()
.container()
)@func()
example(source: Directory): Container {
return dag
.z5labs()
.goApp(source)
.builder()
.container()
}