Skip to main content

Constructors

Every Dagger module has a constructor. The default one is generated automatically and has no arguments.

It's possible to write a custom constructor. The mechanism to do this is SDK-specific.

This is a simple way to accept module-wide configuration, or just to set a few attributes without having to create setter functions for them.

Simple constructor

The default constructor for a module can be overridden by registering a custom constructor. Its parameters are available as flags in the dagger call command directly.

important

Dagger modules have only one constructors. Constructors of custom types are not registered; they are constructed by the function that chains them.

Here is an example module with a custom constructor:

// A Dagger module for saying hello world!

package main

import (
"fmt"
)

func New(
// +optional
// +default="Hello"
greeting string,
// +optional
// +default="World"
name string,
) *MyModule {
return &MyModule{
Greeting: greeting,
Name: name,
}
}

type MyModule struct {
Greeting string
Name string
}

func (hello *MyModule) Message() string {
return fmt.Sprintf("%s, %s!", hello.Greeting, hello.Name)
}

Here is an example call for this Dagger Function:

dagger call --name=Foo message

The result will be:

Hello, Foo!
important

If you plan to use constructor fields in other module functions, ensure that they are declared as public (in Go and TypeScript). This is because Dagger stores fields using serialization and private fields are omitted during the serialization process. As a result, if a field is not declared as public, calling methods that use it will produce unexpected results.

Default values for complex types

Constructors can be passed both simple and complex types (such as Container, Directory, Service etc.) as arguments. Default values can be assigned in both cases.

Here is an example of a Dagger module with a default constructor argument of type Container:

package main

import (
"context"

"main/internal/dagger"
)

func New(
// +optional
ctr *dagger.Container,
) *MyModule {
if ctr == nil {
ctr = dag.Container().From("alpine:3.14.0")
}
return &MyModule{
Ctr: *ctr,
}
}

type MyModule struct {
Ctr dagger.Container
}

func (m *MyModule) Version(ctx context.Context) (string, error) {
c := m.Ctr
return c.
WithExec([]string{"/bin/sh", "-c", "cat /etc/os-release | grep VERSION_ID"}).
Stdout(ctx)
}

It is necessary to explicitly declare the type even when a default value is assigned, so that the Dagger SDK can extend the GraphQL schema correctly.

Here is an example call for this Dagger Function:

dagger call version

The result will be:

VERSION_ID=3.14.0

Exclude argument in constructor

important

The information in this section is only applicable to the Python SDK.

Same as any data class, attributes can be excluded from the generated __init__() function, using dataclasses.field(init=False):

"""A simple hello world example, using a constructor."""

import dataclasses
from typing import Annotated

from dagger import Doc, function, object_type


@object_type
class MyModule:
"""Functions for greeting the world"""

greeting: str = dataclasses.field(default="Hello", init=False)
name: Annotated[str, Doc("Who to greet")] = "World"

@function
def message(self) -> str:
"""Return the greeting message"""
return f"{self.greeting}, {self.name}!"

In this case, only the name flag was added and is visible in the output:

FUNCTIONS
message Return the greeting message

ARGUMENTS
--name string Who to greet (default "World")

Constructor-only arguments

important

The information in this section is only applicable to the Python SDK.

The opposite is also possible. To define an argument that only exists in the constructor, but not as a class attribute, define it as an init-only variable:

"""An example module controlling constructor parameters."""

import dataclasses

import dagger
from dagger import dag, function, object_type


@object_type
class MyModule:
base: dagger.Container = dataclasses.field(init=False)
variant: dataclasses.InitVar[str] = "alpine"

def __post_init__(self, variant: str):
self.base = dag.container().from_(f"python:{variant}")

@function
def container(self) -> dagger.Container:
return self.base

Complex or mutable defaults

important

The information in this section is only applicable to the Python SDK.

For default values that are more complex, dynamic or just mutable, use a factory function without arguments in dataclasses.field(default_factory=...):

"""An example module using default factory functions."""

import dataclasses

import dagger
from dagger import dag, function, object_type


@object_type
class MyModule:
base: dagger.Container = dataclasses.field(
default_factory=lambda: dag.container().from_("python:alpine"),
)
packages: list[str] = dataclasses.field(default_factory=list)

@function
def container(self) -> dagger.Container:
return self.base.with_exec(["apk", "add", "git", *self.packages])

Asynchronous constructor

important

The information in this section is only applicable to the Python SDK.

If a constructor argument needs an asynchronous call to set the default value, it's possible to replace the default constructor function from __init__() to a factory class method named create, as in the following code listing:

warning

This factory class method must be named create.

"""An example module using a factory constructor"""

from typing import Annotated

from dagger import Doc, dag, function, object_type


@object_type
class MyModule:
"""Functions for testing a project"""

parallelize: int

@classmethod
async def create(
cls,
parallelize: Annotated[
int | None, Doc("Number of parallel processes to run")
] = None,
):
if parallelize is None:
parallelize = int(
await dag.container().from_("alpine").with_exec(["nproc"]).stdout()
)
return cls(parallelize=parallelize)

@function
def debug(self) -> str:
"""Check the number of parallel processes"""
return f"Number of parallel processes: {self.parallelize}"