Back to Dagger

Module Initialization

docs/versioned_docs/version-0.20.2/extending/modules/modules.mdx

0.21.818.0 KB
Original Source

import DaggerModuleInit from '../../partials/_dagger_module_init.mdx';

Module Initialization

<DaggerModuleInit />

File layout

Multiple files

<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">

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:

go
// 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`
	...
}
</TabItem> <TabItem value="python" label="Python"> The Dagger module's code in Python can be split into multiple files by making a [package](https://docs.python.org/3/tutorial/modules.html#packages) and ensuring the *main object* is imported in `__init__.py`. All the other object types should already be imported from there.

For example given this directory structure:

.
├── dagger.json
├── src
│   ├── my_module
│   │   ├── __init__.py
│   │   ├── main.py
│   │   ├── test.py
│   │   └── lint.py

The __init__.py file should import the main object from main.py:

python
# src/my_module/__init__.py
"""My very own Dagger module"""
from .main import MyModule as MyModule

And the main.py file should import the other objects from their respective files:

python
# src/my_module/main.py
import dagger

from .test import Test  # in src/my_module/test.py
from .lint import Lint  # in src/my_module/lint.py

@dagger.object_type
class MyModule:
    @dagger.function
    def test(self) -> Test:
        return Test()

    @dagger.function
    def lint(self) -> Lint:
        return Lint()

:::important Dagger expects that a Python Dagger module is structured like a library, so that the SDK is able to load the code with an import, but it's up to the Python build system to know where files are located in order to build and install the Python package correctly.

This affords a lot of flexibility in how Dagger Python modules can be structured, and which tools are supported. :::

The default project template follows known conventions for structuring a Python library (src layout, package name matching project name), which allows Python build backends to automatically recognize where the files are.

However, it's possible to change the project's name and file structure with a bit of extra configuration, as long as the build backend correctly builds the code in a way that allows the SDK to import it after installation (i.e., must be installed in site-packages).

:::note Build backends are independent from installers (or build frontends, see PEP 517), even though some installers may provide both (as separate packages). For example, hatch vs hatchling, poetry vs poetry-core. :::

:::tip dagger init won't override existing pyproject.toml and .py files, so it's possible to use an external process to generate a different template, before calling dagger init. :::

:::tip SINGLE FILE MODULE Here is an example of moving all the code into a single main.py module, resulting in the following structure:

.
├── dagger.json
├── main.py
├── pyproject.toml
└── uv.lock

And corresponding pyproject.toml configuration:

<Tabs groupId="build-systems"> <TabItem value="hatchling">
toml
[build-system]
requires = ["hatchling>=0.15.0"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["main.py"]
</TabItem> <TabItem value="poetry-core">
toml
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
</TabItem> <TabItem value="setuptools">
toml
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
py-modules = ["main"]
</TabItem> </Tabs>

:::

:::tip CUSTOMIZE IMPORT PACKAGE The Python SDK looks for the main object of the Dagger module in the Python import package that is named after the distribution package name (in particular, using underscores _ as a word separator).

If they are different, you must explicitly tell the Python SDK where the main object needs to be imported from, using the following entry point configuration in pyproject.toml:

toml
[project.entry-points."dagger.mod"]
main_object = "<import package>:<main object>"

For example, for a Dagger module named my-module:

  • Main object: MyModule (required to be the name in dagger.json; in PascalCase)
  • Default distribution package: my-module (in pyproject.toml; can be changed)
  • Default import package: src/my_module (normalized after distribution package name; can be changed)

Then, the default main_object entry point that the Python SDK looks for is my_module:MyModule, with a fallback to main:MyModule for backwards compatibility.

Thus, if you have the following configuration:

toml
[project.entry-points."dagger.mod"]
main_object = "my_module.main:MyModule"

Then the import in __init__.py is no longer needed since Dagger knows to import from my_module.main directly.

:::

</TabItem> <TabItem value="typescript" label="TypeScript">

Due to TypeScript limitations, it is not possible to split your main class module (index.ts) into multiple files. However, it is possible to create sub-classes in different files and access them from your main class module:

typescript
// src/index.ts
import { func, object } from "@dagger.io/dagger"

import { Test } from "./test" // in src/test.ts
import { Lint } from "./lint" // in src/lint.ts

@object()
class MyModule {
  @func()
  test(): Test {
    return new Test()
  }

  @func()
  lint(): Lint {
    return new Lint()
  }
}
</TabItem> <TabItem value="php" label="PHP">

Only functions from your main class (MyModule.php) can initially be called by Dagger. However, it is possible to create other classes and access them from your main class:

php
// src/MyModule.php
<?php

declare(strict_types=1);

namespace DaggerModule;

use Dagger\Attribute\{DaggerObject, DaggerFunction};
use DaggerModule\Test; // in src/Test.php
use DaggerModule\Lint; // in src/Lint.php

use function Dagger\dag;

#[DaggerObject]
class MyModule
{
    #[DaggerFunction]
    public function test(): Test
    {
        return new Test();
    }

    #[DaggerFunction]
    public function lint(): Lint
    {
        return new Lint();
    }
}
</TabItem> <TabItem value="java" label="Java"> The Dagger module's code in Java can be split into multiple classes, in multiple files. A few constraints apply:
  • The main Dagger object must be represented by a class using the same name as the module, in PascalCase. For instance if the module name is my-module, the main object's class must be named MyModule.
  • The exposed objects must be annotated with @Object and the exposed functions with @Function.

:::note The package in which the main object exists is not important. By convention, the name is based on the module name, but any name may be used. :::

The description of the module can be set in a package-info.java file, with the @Module annotation:

java
/** My Dagger module */
@Module
package io.dagger.modules.mymodule;

import io.dagger.module.annotation.Module;

:::note By default, the description will be read from the JavaDoc documentation of the package, class or function. To define a different description, use the description field in each @Module, @Object or @Function annotation.

java
/**
 * Returns the build container
 *
 * @param name The name of the container
 * @return The container
 */
@Function(description = "Build container")
public Container build(String name) {
    //...
}
console
$ dagger functions

Name    Description
build   Build container

::: </TabItem> </Tabs>

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.

<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">

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

</TabItem> <TabItem value="python" label="Python">

The runtime container is based on the python:3.13-slim base image by default, but it can be overridden by setting requires-python, or pinned with a .python-version file next to your pyproject.toml:

shell
echo "3.12" > .python-version

This will instruct Dagger to use the python:3.12-slim base image instead.

Pinning the interpreter version can be useful to prevent an automatic upgrade from a future version of Dagger, or to select a newer version.

:::tip For more advanced needs, a different base image can be used by adding the following to your pyproject.toml:

toml
[tool.dagger]
base-image = "acme/python:3.12"

This can be useful to add a few requirements to the module's execution environment such as system packages like git, or to add necessary environment variables, for example. However, don't deviate from the default base image too much or it may break in a future version of Dagger.

Override this at your own risk! :::

</TabItem> <TabItem value="typescript" label="TypeScript">

The runtime container is currently hardcoded to run in Node.js 22.11.0, but it can be overridden by setting an alternative base image.

Bun 1.1.38 and Deno 2.2.4 are experimentally supported.

The TypeScript SDK is installed automatically, including dependencies, with a version that's tied to the currently running Dagger Engine container:

By default, the SDK is installed as a bundled local dependency, so all SDK related dependencies are already pre-bundled in the module's code and extra dependencies are not added to the module's package.json file.

shell
# executed by the runtime container
yarn install

The SDK files are mounted under /sdk in the Dagger Engine runtime container.

This is why the initial package.json doesn't include any dependencies except TypeScript.

json
{
  "dependencies": {
    "typescript": "^5.3.2"
  }
}

:::note To configure the SDK as a local vendored dependency, read bundled or vendored SDK. :::

</TabItem> <TabItem value="php" label="PHP">

The runtime container is currently hardcoded to run in php:8.3-cli-alpine (although this may be configurable in future).

</TabItem> <TabItem value="java" label="Java">

Two containers are used by the runtime, one to build the module into a JAR file, one to run it.

They are currently hardcoded to run in maven:3.9.9-eclipse-temurin-17 and eclipse-temurin:23-jre-noble respectively (although this may be configurable in future).

</TabItem> </Tabs>

Language-native packaging

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

<Tabs groupId="language" queryString="sdk"> <TabItem value="go" label="Go">

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
// 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.

</TabItem> <TabItem value="python" label="Python">

Dagger modules in Python are built to be installed, like libraries. At module creation time, a pyproject.toml and uv.lock file will automatically be created that depend on the locally generated client library (in ./sdk). This dependency is configured to be editable so that changes in the code don't require a re-install.

With the uv.lock file, Dagger uses uv's project management capabilites, but it can be opted out by removing this file, in which case Dagger falls back to the pip interface instead.

:::info Dagger also supports pinning dependencies with a pip-tools compatible requirements.lock file in order to support the use of other project managers like Poetry or Hatch locally (when developing).

In this case, Dagger installs dependencies with:

shell
# executed by the runtime
uv pip install -r requirements.lock -e ./sdk -e .

Notice that the ./sdk and . packages don't need to be in the requirements.lock file, only the third party dependencies.

This means module developers can use any tool they want to manage their virtual environment and install dependencies, but if third-party dependencies aren't pinned in a requirements.lock file, the developers may get different versions between the Dagger execution environment and their own local environment.

For example, Poetry has its own poetry.lock which Dagger doesn't recognize, but it can be exported as a requirements.lock file with:

shell
poetry export --without main -o requirements.lock

It will have to be manually kept in sync, though. :::

</TabItem> <TabItem value="typescript" label="TypeScript"> Dagger modules in Typescript are built to be installed, like libraries. The runtime container installs the module code with:
shell
# executed by the runtime container
yarn install --production

This means that so long as the project has a package.json file, it can be used as a library and it can be managed using any Node.js package manager such as npm, pnpm or yarn.

Only production dependencies are installed, not packages defined in the devDependencies field.

</TabItem> <TabItem value="php" label="PHP"> Dagger modules in PHP are built to be installed, like libraries. The runtime container installs the module code with:
shell
# executed by the runtime container
composer install

This means that so long as the project has a composer.json file, it can be used as a library.

</TabItem> <TabItem value="java" label="Java"> Dagger modules in Java are built as JAR files, using Maven. The runtime container builds the module code with:
shell
# executed by the runtime build container
mvn clean package

The generated JAR file is then executed by the runtime container with:

shell
# executed by the runtime run container
java -jar module.jar

:::important This means you have to keep the pom.xml file and especially the maven-compiler-plugin and the maven-shade-plugin configurations. :::

:::note Other build tools like Gradle might be supported in the future. ::: </TabItem> </Tabs>

Remote modules

Dagger can use remote repositories as Dagger modules. This feature is compatible with all major Git hosting platforms such as GitHub, GitLab, BitBucket, Azure DevOps, Codeberg, and Sourcehut. Dagger supports authentication via both HTTPS (using Git credential managers) and SSH (using a unified authentication approach).

Here is an example of using a Go builder Dagger module from a public repository over HTTPS:

shell
dagger -m github.com/kpenfound/dagger-modules/[email protected] call \
  build --source=https://github.com/dagger/dagger --args=./cmd/dagger \
  export --path=./build

Here is the same example using SSH authentication. Note that this requires SSH authentication to be properly configured on your Dagger host.

shell
dagger -m [email protected]:kpenfound/dagger-modules/[email protected] call \
  build --source=https://github.com/dagger/dagger --args=./cmd/dagger \
  export --path=./build

For more information, refer to the documentation on remote repository access.