Skip to main content

Module Structure

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. By default, the module source code will be stored in the current working directory, unless an alternative is specified with the --source argument.

The default template from dagger develop creates the following structure:

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

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.

For examples of modules written in Go, see Daggerverse Modules in Go.

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.

File layout

Multiple files

You can split your Dagger module into multiple files, not just main.go. To do this, you can just create another file beside main.go (for example, utils.go):

.
│── ...
│── main.go
│── utils.go
└── dagger.json

This file should be inside the same package as main.go, and as such, can access any private variables/functions/types inside the package.

Additionally, you can also split your Dagger module into Go subpackages (for example, utils):

.
│── ...
│── main.go
|── utils
│ └── utils.go
└── dagger.json

Because this is a separate package, you can only use the variables/functions/types that are exported from this package in main.go (you can't access types from main.go in the utils package).

note

Only types and functions in the top-level package are part of the public-facing API for the module.

You can access other Dagger types from a sub-package by importing the generated sub-package dagger/<module>/internal/dagger:

// utils/utils.go

import "dagger/<module>/internal/dagger"

func DoThing(client *dagger.Client) *dagger.Directory {
// we need to pass *dagger.Client in here, since we don't have access to `dag`
...
}

Monorepos

A monorepo typically contains multiple independent projects, each of which has different test, build and deployment requirements. Managing these requirements in a single CI workflow or YAML file can be incredibly complex and time-consuming.

Dagger modules provide a framework that you can use to break up this complexity and cleanly separate CI responsibilities in a monorepo without losing reusability or performance. There are two possible patterns you can follow:

  1. One top-level Dagger module per project, with sub-modules for the various CI workflows in that project. This pattern is suitable when there are significant differences between the projects in the monorepo (e.g. a monorepo with SDKs, CLIs, web applications, docs, all of which have different CI requirements).

Benefits of this pattern include:

  • Easier debugging: Sub-modules provide a way to separate, and therefore easily debug, the business logic for different CI tasks.
  • Code reuse: There may be opportunities for sub-modules in different projects to import each other to reuse existing functionality.
  • Improved performance: The top-level module of a project can orchestrate the sub-modules using the language’s native concurrency features.
  1. A single, shared CI / automation module which all projects use and contribute to. This pattern is suitable when there are significant commonalities between the projects in the monorepo (e.g. a monorepo with only micro-services or only front-end applications).

Benefits of this pattern include:

  • Code reuse: This reduces code duplication and ensures a consistent CI environment for all projects. For example, the shared module could create a common build environment and leverage this for multiple projects in the monorepo.
  • Reduced onboarding friction: There is no need to create a new CI module when adding a new project or component. New projects can get started faster with their CI implementation.
  • Best practices: All projects benefit from the best practices implemented in the shared module.
  • Knowledge sharing: By contributing to a shared CI module, project teams can learn from each other's CI strategies.

Runtime container

Dagger modules run in a runtime container that's bootstrapped by the Dagger Engine, with the necessary environment to run the Dagger module's code.

The runtime container is currently hardcoded to run in Go 1.21 (although this may be configurable in future).

Language-native packaging

The structure of a Dagger module mimics that of each language's conventional packaging mechanisms and tools.

Dagger modules written for use with the Go SDK are automatically created as Go modules. At module creation time, a go.mod and go.sum file will automatically be created that import the necessary dependencies. Dependencies can be installed and managed just as for any standard Go environment.

After using new dependencies in your code, update your go.mod/go.sum with the newly imported dependencies by using go mod tidy.

Go workspaces

Since it's common to have a sub-directory inside your main project containing your Dagger module code, you can manage your modules using Go workspaces.

When a new Dagger module is created, Dagger attempts to add it to a root go.work if it exists. If not, it can be added manually later with go work use ./path/to/mymodule.

// go.work
go 1.21.7

use (
./path/to/mymodule
)

Go vendor

Go vendor directories are not currently supported. See https://github.com/dagger/dagger/issues/6076 for more information.