Skip to main content

Dagger SDKs

Dagger SDKs make it easy to call the Dagger API from your favorite programming language, by developing Dagger Functions or custom applications.

A Dagger SDK provides two components:

  • A client library to call the Dagger API from your code
  • Tooling to extend the Dagger API with your own Dagger Functions (bundled in a Dagger module)

The Dagger API uses GraphQL as its low-level language-agnostic framework, and can also be accessed using any standard GraphQL client. However, you do not need to know GraphQL to call the Dagger API; the translation to underlying GraphQL API calls is handled internally by the Dagger SDKs.

Stable Dagger SDKs are currently available for Go, TypeScript and Python. There are also experimental SDKs contributed by the community.

Dagger Functions

The recommended, and most common way, to interact with the Dagger API is through Dagger Functions. Dagger Functions are just regular code, written in your usual language using a type-safe Dagger SDK.

Dagger Functions are packaged, shared and reused using Dagger modules. A new Dagger module is initialized by calling dagger init. This creates a new dagger.json configuration file in the current working directory, together with sample Dagger Function source code. The configuration file will default the name of the module to the current directory name, unless an alternative is specified with the --name argument.

Once a module is initialized, dagger develop --sdk=... sets up or updates all the resources needed to develop the module locally using a Dagger SDK. By default, the module source code will be stored in the current working directory, unless an alternative is specified with the --source argument.

Here is an example of initializing a Dagger module:

dagger init --name=my-module
dagger develop --sdk=go
warning

Running dagger develop regenerates the module's code based on dependencies, the current state of the module, and the current Dagger API version. This can result in unexpected results if there are significant changes between the previous and latest installed Dagger API versions. Always refer to the changelog for a complete list of changes (including breaking changes) in each Dagger release before running dagger develop, or use the --compat=skip option to bypass updating the Dagger API version.

The default template from dagger develop creates the following structure:

.
├── LICENSE
├── dagger.gen.go
├── dagger.json
├── go.mod
├── go.sum
├── internal
│ ├── dagger
│ ├── querybuilder
│ └── telemetry
└── main.go

In this structure:

  • dagger.json is the Dagger module configuration file.
  • go.mod/go.sum manage the Go module and its dependencies.
  • main.go is where your Dagger module code goes. It contains sample code to help you get started.
  • internal contains automatically-generated types and helpers needed to configure and run the module:
    • dagger contains definitions for the Dagger API that's tied to the currently running Dagger Engine container.
    • querybuilder has utilities for building GraphQL queries (used internally by the dagger package).
    • telemetry has utilities for sending Dagger Engine telemetry.
note

While you can use the utilities defined in the automatically-generated code above, you cannot edit these files. Even if you edit them locally, any changes will not be persisted when you run the module.

You can now write Dagger Functions using the selected Dagger SDK. Here is an example, which calls a remote API method and returns the result:

package main

import (
"context"
)

type MyModule struct{}

func (m *MyModule) GetUser(ctx context.Context) (string, error) {
return dag.Container().
From("alpine:latest").
WithExec([]string{"apk", "add", "curl"}).
WithExec([]string{"apk", "add", "jq"}).
WithExec([]string{"sh", "-c", "curl https://randomuser.me/api/ | jq .results[0].name"}).
Stdout(ctx)
}

This Dagger Function includes the context as input and error as return in its signature.

caution

You can try this Dagger Function by copying it into the default template generated by dagger init, but remember that you must update the module name in the code samples above to match the name used when your module was first initialized.

In simple terms, this Dagger Function:

  • initializes a new container from an alpine base image.
  • executes the apk add ... command in the container to add the curl and jq utilities.
  • uses the curl utility to send an HTTP request to the URL https://randomuser.me/api/ and parses the response using jq.
  • retrieves and returns the output stream of the last executed command as a string.
important

Every Dagger Function has access to the dag client, which is a pre-initialized Dagger API client. This client contains all the core types (like Container, Directory, etc.), as well as bindings to any dependencies your Dagger module has declared.

Here is an example call for this Dagger Function:

dagger call get-user

Here's what you should see:

{
"title": "Mrs",
"first": "Beatrice",
"last": "Lavigne"
}
important

Dagger Functions execute within containers spawned by the Dagger Engine. This "sandboxing" serves a few important purposes:

  1. Reproducibility: Executing in a well-defined and well-controlled container ensures that a Dagger Function runs the same way every time it is invoked. It also guards against creating "hidden dependencies" on ambient properties of the execution environment that could change at any moment.
  2. Caching: A reproducible containerized environment makes it possible to cache the result of Dagger Function execution, which in turn allows Dagger to automatically speed up operations.
  3. Security: Even when running third-party Dagger Functions sourced from a Git repository, those Dagger Functions will not have default access to your host environment (host files, directories, environment variables, etc.). Access to these host resources can only be granted by explicitly passing them as argument values to the Dagger Function.

Custom applications

An alternative approach is to develop a custom application using a Dagger SDK. This involves:

  • installing the SDK for your selected language in your development environment
  • initializing a Dagger API client in your application code
  • calling and combining Dagger API methods from your application to achieve the required result
  • executing your application using dagger run
note

The Dagger Go SDK requires Go 1.22 or later.

From an existing Go module, install the Dagger Go SDK using the commands below:

go get dagger.io/dagger@latest

After importing dagger.io/dagger in your Go module code, run the following command to update go.sum:

go mod tidy

This example demonstrates how to build a Go application for multiple architectures and Go versions using the Go SDK.

Clone an example project and create a new Go module in the project directory:

git clone https://go.googlesource.com/example
cd example/hello
mkdir multibuild && cd multibuild
go mod init multibuild

Create a new file in the multibuild directory named main.go and add the following code to it:

package main

import (
"context"
"fmt"

"dagger.io/dagger/dag"
)

func main() {
if err := build(context.Background()); err != nil {
fmt.Println(err)
}
}

func build(ctx context.Context) error {
fmt.Println("Building with Dagger")

// define build matrix
oses := []string{"linux", "darwin"}
arches := []string{"amd64", "arm64"}
goVersions := []string{"1.22", "1.23"}

defer dag.Close()

// get reference to the local project
src := dag.Host().Directory(".")

// create empty directory to put build outputs
outputs := dag.Directory()

for _, version := range goVersions {
// get `golang` image for specified Go version
imageTag := fmt.Sprintf("golang:%s", version)
golang := dag.Container().From(imageTag)
// mount cloned repository into `golang` image
golang = golang.WithDirectory("/src", src).WithWorkdir("/src")

for _, goos := range oses {
for _, goarch := range arches {
// create a directory for each os, arch and version
path := fmt.Sprintf("build/%s/%s/%s/", version, goos, goarch)
// set GOARCH and GOOS in the build environment
build := golang.WithEnvVariable("GOOS", goos)
build = build.WithEnvVariable("GOARCH", goarch)

// build application
build = build.WithExec([]string{"go", "build", "-o", path})

// get reference to build output directory in container
outputs = outputs.WithDirectory(path, build.Directory(path))
}
}
}
// write build artifacts to host
_, err := outputs.Export(ctx, ".")
if err != nil {
return err
}
return nil
}

This Go program imports the Dagger SDK and defines two functions. The build() function represents the pipeline and creates a Dagger client, which provides an interface to the Dagger API. It also defines the build matrix, consisting of two OSs (darwin and linux) and two architectures (amd64 and arm64), and builds the Go application for each combination. The Go build process is instructed via the GOOS and GOARCH build variables, which are reset for each case.

Try the Go program by executing the command below from the project directory:

dagger run go run multibuild/main.go

The dagger run command executes the specified command in a Dagger session and displays live progress. The Go program builds the application for each OS/architecture combination and writes the build results to the host. You will see the build process run four times, once for each combination. Note that the builds are happening concurrently, because the builds do not depend on eachother.

Use the tree command to see the build artifacts on the host, as shown below:

tree build
build
├── 1.22
│ ├── darwin
│ │ ├── amd64
│ │ │ └── hello
│ │ └── arm64
│ │ └── hello
│ └── linux
│ ├── amd64
│ │ └── hello
│ └── arm64
│ └── hello
└── 1.23
├── darwin
│ ├── amd64
│ │ └── hello
│ └── arm64
│ └── hello
└── linux
├── amd64
│ └── hello
└── arm64
└── hello

Differences

Here is a quick summary of differences between these two approaches.

Dagger FunctionsCustom applications
Pre-initialized Dagger API clientYN
Direct host accessNY
Direct third-party module accessYN
Cross-language interoperabilityYN