Dagger lets developers define service pipelines in a programming language and run them through a container-based pipeline engine. A pipeline can build, test, and publish a service without being defined as Kubernetes resources or YAML. The same approach works on a developer workstation or in a remote environment. section 3.5.4
A Dagger workflow separates pipeline code from where it runs:
- Write tasks with an SDK. The supported SDKs described here are Go, Python, TypeScript, and JavaScript. section 3.5.4
- Run the pipeline with the Dagger engine and an OCI container runtime, such as Docker or Podman, locally or remotely. section 3.5.4
- Distribute pipeline code using the language’s packaging tools so teams can reuse tasks. section 3.5.4
Build, test, and publish a service
A Go pipeline for a service accepts build, test, publish, or all as its command. Its build task reads a host source directory, starts a container from golang:1.20-alpine, mounts the source and Go caches, and prepares an output directory. The all command calls the build, test, and publish tasks in sequence. This puts the delivery steps in code that developers can run rather than requiring a Kubernetes pipeline definition. section 3.5.4 section 3.5.3
Listing 3.11 Go application defining tasks using Dagger
func main() {
var err error
ctx := context.Background()
if len(os.Args) < 2 {
...)
}
client := getDaggerClient(ctx)
defer client.Close()
switch os.Args[1] {
case "build":
if len(os.Args) < 3 {
panic(...)
}
_, err = buildService(ctx, client, os.Args[2])
case "test":
err = testService(ctx, client, os.Args[2])
case "publish":
pv, err := buildService(ctx, client, os.Args[2])
err = publishService(ctx, client, os.Args[2], pv, os.Args[3])
case "all":
pv, err := buildService(ctx, client, os.Args[2])
err = testService(ctx, client, os.Args[2])
err = publishService(ctx, client, os.Args[2], pv, os.Args[3])
default:
log.Fatalln("invalid command specified")
}
Inspect the Go command dispatch to see how individual tasks and the combined workflow are invoked. section 3.5.4
Listing 3.12 Tasks: Go code that uses Dagger built-in functions
func buildService(ctx context.Context,
client *dagger.Client,
dir string) ([]*dagger.Container, error) {
srcDir := client.Host().Directory(dir)
platformVariants := make([]*dagger.Container, 0, len(platforms))
for _, platform := range platforms {
ctr := client.Container()
ctr = ctr.From("golang:1.20-alpine")
// mount in our source code
ctr = ctr.WithDirectory("/src", srcDir)
ctr = ctr.WithMountedCache("/go/pkg/mod", client.CacheVolume("go-mod"))
ctr = ctr.WithMountedCache("/root/.cache/go-build",
client.CacheVolume("go-build"))
// mount in an empty dir to put the built binary
ctr = ctr.WithDirectory("/output", client.Directory())
// ensure the binary will be statically linked and thus executable
// in the final image
ctr = ctr.WithEnvVariable("CGO_ENABLED", "0")
// configure go to support different architectures
ctr = ctr.WithEnvVariable("GOOS", "linux")
ctr = ctr.WithEnvVariable("GOARCH", architecture(platform))
// build the binary and put the result at the mounted output directory
ctr = ctr.WithWorkdir("/src")
ctr = ctr.WithExec([]string{"go", "build","-o", "/output/app",".",})
// select the output directory
outputDir := ctr.Directory("/output")
// create a new container with the output and the platform label
binaryCtr := client.Container(dagger.ContainerOpts{Platform: platform}).
WithEntrypoint([]string{"./app"}).
WithRootfs(outputDir)
platformVariants = append(platformVariants, binaryCtr)
}
return platformVariants, nil
}
Inspect the build task’s container setup, source mount, and Go build caches. section 3.5.4
Choose Dagger when teams want code-defined pipelines they can run locally as well as remotely. Tekton instead uses Kubernetes resources and integrates with Kubernetes tooling; GitHub Actions offers a managed alternative. These options can complement one another. Local execution is particularly useful for shortening the feedback loop while developing a service pipeline. section 3.5.5 section 3.7
