Skip to main content


Write your first function

So far, you've been calling individual Dagger Functions using the Dagger CLI. But what if you want to combine multiple Dagger Function calls?

One option is, of course, to stitch together calls using the CLI and a shell script or a Makefile However, this approach is not recommended, because you will typically end up with long and unwieldy CLI commands and shell scripting "glue" that is hard to debug and maintain.

A better approach is to create a programmable Dagger Module, which can then call other Dagger Modules and Dagger Functions to achieve the required results. Creating your own Dagger Module enables you to transform your workflows into structured, discrete and composable units with clear inputs and outputs, with all the benefits of using a native programming language.

By creating your own Dagger Module and installing other modules into it, you can express more complex workflows and operations by reusing modules created by others and connecting them together using the programming language you're most comfortable with.

To see this in action, create a custom Dagger Module that uses two other Dagger Modules to build your project, add it to a custom container image of your choice, and publish the result to a registry.


The steps below assume that you have already Daggerized the example Go project repository by initializing a new Dagger Module and installing the Go builder module as a dependency. If not, complete those steps before proceeding.

Bootstrap a module template

Begin by running dagger develop to bootstrap a module template in one of the supported programming languages:

dagger develop --sdk=go

This will generate a dagger.json module file, an initial dagger/main.go source file, as well as a generated dagger/dagger.gen.go and dagger/internal directory for the generated module code.


The class/type definitions in the generated module code are derived from the module name.

Install another dependency

You've already installed the Go builder module in your project. Next, install the Wolfi container builder module from earlier as another dependency:

dagger install

Add a function to the module

Update the generated module template:

Update dagger/main.go with the following code:

package main

import (

type Example struct{}

// Build and publish a project using a Wolfi container
func (m *Example) BuildAndPublish(ctx context.Context, buildSrc *Directory, buildArgs []string) (string, error) {
ctr := dag.Wolfi().Container()

return dag.
BuildContainer(GolangBuildContainerOpts{Source: buildSrc, Args: buildArgs, Base: ctr}).
Publish(ctx, fmt.Sprintf("", math.Floor(rand.Float64()*10000000))) //#nosec

This new BuildAndPublish() function accepts two arguments - the source directory and a list of build arguments - and does the following:

  • It invokes the Wolfi module via the dag client.
  • It calls the module's Container() function to return a new Wolfi container image as a Container.
  • It invokes the Golang module via the dag Dagger client.
  • It calls the module's BuildContainer() function to build the source code and return a Container with the compiled binary.
  • It calls the Container.Publish() method to publish the container image to a registry and print the SHA identifier of the published image.

Call the function

Call the new function using the Dagger CLI:

dagger call build-and-publish \
--build-src=. \

Here's an example of the output you should see:

Test the published container image using the command below (remember to update the image name based on the function output):

docker run \
--name ctr \
--rm \
-it /usr/local/bin/hello

You should see the same output as before:

Hello, world!

Well done! You've just successfully written your first Dagger Function!


When you install one module in another, Dagger exposes its functions using a language-agnostic GraphQL API. This means that any function can call any other function, regardless of whether they use the same programming language or not. For example, a Go function can call a Python function, which can call a Typescript function, and so on. This also means that you no longer care which language your CI tooling is written in; you can use the one that you're most comfortable with or that best suits your requirements. Learn more about how Dagger works.