liveBook · Manning

Dagger

As explained in Platform Engineering on Kubernetes

Dagger is a programmable, portable engine for defining and executing software delivery pipelines using containerized tasks.

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

How Platform Engineering on Kubernetes covers it

How deep it goes
Shows Go command dispatch and a containerized build task, but does not detail the engine’s internals.
Examples
Uses a Go service pipeline with build, test, publish, and all commands plus a containerized Go build.
What it assumes
Assumes familiarity with programming pipeline tasks and access to a container runtime; the Go example supplies the task structure.
What you can do after
Apply it via Dagger SDKs to create and run service pipelines locally or remotely.
How it connects
Compares Dagger with Tekton and GitHub Actions through Kubernetes integration, local execution, and pipeline reuse.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Platform Engineering on Kubernetes. The section links point to the parts of the book it draws on.