Introduction to Dagger

Introduction to Dagger

cloudnative

This blog post provides a detailed introduction to Dagger, what it is and how you can start using Dagger modules


Dagger is a programmable application delivery platform that runs your CI/CD pipeline in containers. Users define their CI/CD pipeline as code, in the same language as the application that will run in the pipeline.

By defining CI/CD processes through code, they can be run in any pipeline independent of the provider. Whether you are using GitLab CI/CD, GitHub Actions, Codefresh, or CircleCI, ultimately, the pipeline is just used to run the Dagger code. This makes it very easy to move Dagger pipelines between CI/CD providers. 

This blog post is divided into two main parts. The first one details what Dagger is, how it works, and the main benefits of using Dagger. The second part provides a tutorial that you can follow to get started with Dagger and understanding its benefits.

Dagger Overview 

The source code is open source and Dagger provides free and managed solutions for their enterprise platform.  

The main platform benefits include the following:

  • Shift CI Left
  • Programmable CI
  • Universal Caching
  • Cross-Language support 

Benefits to app teams

The following content is taken from the Dagger GitHub Repository.

  • Reduce complexity: even complex builds can be expressed as a few simple functions
  • No more "push and pray": everything CI can do, your dev environment can do too
  • Use the same language to develop your app and its scripts
  • Easy onboarding of new developers: if you can build, test and deploy - they can too.
  • Everything is cached by default: expect 2x to 10x speedups
  • Parity between dev and CI environments
  • Cross-team collaboration: reuse another team's workflows without learning their stack

Benefits to platform teams

  • Reduce CI lock-in. Dagger functions run on all major CI platforms - no proprietary DSL needed.
  • Don't be a bottleneck. Let app teams write their functions. Enable standardisation by providing a library of reusable components.
  • Faster CI runs. CI pipelines that are "Daggerized" typically run 2x to 10x faster, thanks to caching and concurrency. This means developers waste less time waiting for CI, and users spend less money on CI computing.
  • A viable platform strategy. App teams need flexibility and control. Dagger provides users with a way to reconcile the two, in an incremental way that leverages the stack you already have.

How does Dagger Work?

The following Graphic has been taken from the Dagger documentation.

Source

When developers use Dagger, they are setting up CI pipelines through the Dagger SDK. SDK stands for System Developer Kit. When you set up and use the SDK, the program will open a new session with the Dagger Engine – this is a new container running on your host machine. If your setup already has a Dagger Engine available, it will use the existing one. Otherwise, triggering a program through the SDK will set up a new session with the Dagger Engine. 

Developers specify in their program API requests through the SDK. The API requests are then run through a pipeline in the Dagger Engine. Once the Engine receives an API request, it will do some magic to set up a Directed Acrylic Graph (DAG)  that defines the different processes in the pipeline. The Dagger Engine then starts to process operations concurrently.

Don’t worry too much about what a DAG is, it’s a way to describe the relationship between different processes.

The result of one pipeline might be used as input in another pipeline. A pipeline is described as “a module” in Dagger.

Daggers Caching Functionality

Dagger highlights across its resources that it has very advanced caching functionality. Users have to set up a volume as part of the pipeline. This volume is used by Dagger to persist the data between pipeline runs. Resulting, once the user has run a pipeline, Dagger will only rerun processes that include changes to the pipeline and related resources.   

Prerequisites

Install the Dagger CLI locally. There are multiple different installation options listed in the documentation. We are going to use Homebrew:

brew install dagger/tap/dagger

Ensure that everything is working by running the following command:

dagger --help

Getting Started with Dagger Tutorial – Part 1

In this section, we are going to use Dagger to build a remote git repository locally and scan it for vulnerabilities with the Trivy plugin.

First, create a new directory:

mkdir dagger-example

And change into that directory:

cd dagger-example

Create a new script called build.sh:

touch build.sh

Next, pass the following code into the build.sh script:

#!/bin/bash


# get Go examples source code repository
source=$(dagger query <<EOF | jq -r .git.branch.tree.id
{
git(url:"https://go.googlesource.com/example") {
  branch(name:"master") {
    tree {
      id
    }
  }
}
}
EOF
)


# mount source code repository in golang container
# build Go binary
# export binary from container to host filesystem
build=$(dagger query <<EOF | jq -r .container.from.withDirectory.withWorkdir.withExec.file.export
{
container {
  from(address:"golang:latest") {
    withDirectory(path:"/src", directory:"$source") {
      withWorkdir(path:"/src/hello") {
        withExec(args:["env", "GOOS=darwin", "GOARCH=arm64", "go", "build", "-o", "dagger-builds-hello", "."]) {
          file(path:"./dagger-builds-hello") {
            export(path:"./dagger-builds-hello")
          }
        }
      }
    }
  }
}
}
EOF
)


# check build result and display message
if [ "$build" == "true" ]
then
  echo "Build successful"
else
  echo "Build unsuccessful"
fi

In the first part, we request the go repository and in the second query, we build the project inside the goland:latest container. You can read more about the process in the documentation

In the scripts, we make direct use of GraphQL API queries. This is one of the magic points behind Dagger. We/They can translate the GraphQL API queries into different SDKs. We will use the Go SDK in the next section of this blog.

getting-started tutorial for building a custom client.

Run the pipeline, use the following command:

dagger run ./build.sh

Now the go application will be available in your current working directory. Run the application:

chmod +x ./dagger-builds-hello
./dagger-builds-hello


This should provide the following output:

Important

The Dagger documentation provides further examples of running different types of API Queries for the script.

Using the Dagger Go SDK – Part 2 

In this section, we are going to make use of the following tutorial

Copy the files or clone our example repository:

git clone https://github.com/Cloud-Native-Security/dagger-trivy

Change into the app directory:

cd app

If you are using another repository than the one above, initialise the go directory:

go mod init <Github repo>


go mod init github.com/Cloud-Native-Security/dagger-trivy/app

Replace the GitHub path with your repository path.

Next, add the dagger module:

go get dagger.io/dagger@latest

Add Trivy vulnerability scanning

Trivy is an all-in-one cloud native security scanner. This section showcases how you can add the Trivy module to scan container image for vulnerabilities in Dagger.

First, run the following command to initialise a module:

dagger init --name=example --sdk=go

Note: At this stage, please ensure that you do not set the --name flag to Trivy.

Also, if the command does not run as expected, here is what you can do to resolve it:

  • If your go version in your go.mod file has a minor version, remove the minor version: go 1.21.6 becomes go 1.21

Next, in the app directory, we need to install the Trivy integration from the page in Daggerverse:

dagger install github.com/jpadams/daggerverse/trivy

The dagger module will be set up in your directory as a main.go file. This is what dagger calls a Dagger module. 

Modify the existing pipeline

We are going to replace the content in the main.go file added by Dagger with the following content:

package main

import (
	"context"
	"fmt"
)

type Example struct{}

const (
	trivyImageTag = "0.49.1" // semver tag or "latest"
)

// example usage: "dagger call container-echo --string-arg yo"
func (m *Example) ContainerEcho(stringArg string) *Container {
	return dag.Container().From("alpine:latest").WithExec([]string{"echo", stringArg})
}

// example usage: "dagger call grep-dir --directory-arg . --pattern GrepDir"
func (m *Example) GrepDir(ctx context.Context, directoryArg *Directory, pattern string) (string, error) {
	return dag.Container().
		From("alpine:latest").
		WithMountedDirectory("/mnt", directoryArg).
		WithWorkdir("/mnt").
		WithExec([]string{"grep", "-R", pattern, "."}).
		Stdout(ctx)
}

func (m *Example) Build(ctx context.Context, src *Directory) *Directory {

	// get `golang` image
	golang := dag.Container().From("golang:latest")

	// mount cloned repository into `golang` image
	golang = golang.WithDirectory("/src", src).WithWorkdir("/src")

	// define the application build command
	path := "build/"
	golang = golang.WithExec([]string{"env", "GOOS=darwin", "GOARCH=arm64", "go", "build", "-o", path, "./src/hello.go"})

	scanResult, err := dag.Trivy().ScanContainer(ctx, golang, TrivyScanContainerOpts{
		TrivyImageTag: trivyImageTag,
		Severity:      "HIGH,CRITICAL",
		Format:        "table",
		ExitCode:      0,
	})

	fmt.Println(scanResult)

	if err != nil {
		return nil
	}

	// get reference to build output directory in container
	outputDir := golang.Directory(path)

	return outputDir
}

Next, build and run this pipeline:

dagger --focus=false call build --src ./ export --path ./build-two --progress plain

In this case, we are asking Dagger for the entire output through --focus=false, provide the source code that we want to use in the build pipeline --src ./ and export from the Dagger Engine the build path.

In the terminal, you should now see the Trivy scan result:

If successful, the command should have created a new build directory with a dagger-trivy binary that will run our hello world example:

./build/hello
Hello, world!

Additional Resources

Viktor Farcic published the following tutorial in which he stated “you should ditch pipelines almost entirely”

https://youtu.be/oosQ3z_9UEM?si=gv7ZHbxrVn-9V-Sr 

Here is an introduction to Dagger by the Dagger team:

https://youtu.be/HHhttaiHtm4?si=j2qKuut7zVlfN2Fr 

What’s next?

This blog post detailed the benefits of Dagger and how you can get started with Dagger and its Go SDK. I highly encourage you to try out some of the other Dagger functionality and integrations.

⭐If you enjoy Trivy, please give the repository a star on GitHub