Skip to main content

Project file organization

When your Dagger configuration grows, you may feel the need to better organize your project by splitting it into multiple files.

A simple way to accomplish this is to create and import packages within your project's module.

What is a CUE module?​

A module in CUE is any directory including a cue.mod folder. It makes for the prefix/root of importable packages. For example, universe.dagger.io is a module which includes several packages.

When you ran dagger project init, dagger created this directory for you.

Anonymous module (default)​

When the module name is an empty string, it's known as an anonymous module:

cue.mod/module.cue
module: ""

Anonymous modules are used when we don't need to import other packages from within the same module.

By default, this is how Dagger projects start: a single CUE file (e.g. dagger.cue), which imports everything from third-party packages, such as universe.dagger.io/docker.

When to name a module​

If you want to create packages inside your project, and import from each other, you need to give a name to your module. This is required so you have a prefix/root before your imports.

The module path becomes the import path prefix for packages in the module. This might be the name of a domain you own or another name you control (such as your company name), even your email, followed optionally by a descriptive path (e.g., project name). The address doesn't have to exist, it's only used for namespacing.

You import packages by prefixing the module name they're a part of, plus the path to them, relative to the cue.mod directory.

root                  // <- this is a module because it includes a cue.mod dir
|-- cue.mod
| |-- module.cue // module: "example.com/myproject"
|-- schemas
| |-- compose // <- this is a package because it includes files with a package directive
| | |-- spec.cue // package compose
...
|-- dagger.cue // import "example.com/myproject/schemas/compose"

Summary​

Consider the module as the URL to access the root of your project. Any subfolder inside this module needs to have CUE files with a package name equivalent to the directory name. File names inside each directory are not important, the package name is.

Initializing the module​

Project non initialized​

The module name can be set during project initialization: dagger project init --name <NAME>.

Let's use an email address for convenience purposes:

dagger project init --name "info@example.com"

Project already initialized​

Manually edit the desired name in module.cue:

cue.mod/module.cue
module: "info@example.com"

Creating packages in subdirectories​

Practice​

Let's put everything above into practice:

  • Initialize the workdir environment
mkdir daggerTest && cd daggerTest
  • Initialize the project
dagger project init --name "info@example.com"
  • Install dagger.io and universe.dagger.io dependencies
dagger project update
  • Create 2 subfolders: foo and bar:
mkdir foo bar
  • Inside the bar folder, create a CUE file with any name and whose package is bar (same as parent directory):
bar/anything.cue
package bar

#Test: "world"
  • Inside the foo folder, create a CUE file with any name and whose package is foo (same as parent directory):
foo/main.cue
package foo

import (
"universe.dagger.io/bash"

"info@example.com/bar"
)

#Foo: {
script: string

_run: bash.#RunSimple & {
// reference the #Test definition from the bar directory
env: TEST: bar.#Test

// store the output of the command
"script": contents: "\(script) > /output.txt"
export: files: "/output.txt": _

// don't cache
always: true
}

result: _run.export.files."/output.txt"
}
  • At the root of the project, create your main.cue file (file and package names are arbitrary):
main.cue
package main

import (
"dagger.io/dagger"

"info@example.com/foo"
)

dagger.#Plan & {
actions: {
hello: foo.#Foo & {
script: "echo -n Hello, inlined $TEST!"
}
}
}

Recap​

We now have the directory structure shown below:

$ tree -L 2 .
.
├── bar
│ └── anything.cue
├── cue.mod
│ ├── module.cue
│ ├── pkg
├── foo
│ └── main.cue
└── main.cue

4 directories, 4 files

And the expected output is:

$ dagger do hello
[✔] actions.hello
Field Value
result "Hello, inlined world!"