Dagger currently conflates two distinct concepts in a single dagger.json file:
- Project configuration (what tools to use, how to configure them)
- Module definition (what code to package, what dependencies it needs)
This causes confusion about what files are accessible, how dependencies work, and what appears in the CLI.
This proposal establishes workspaces and modules as two fundamentally different things, each with their own configuration file and dependency model.
A workspace is a directory used as context for configuring and using Dagger. Typically it is a git repository, or a subdirectory within a larger repo.
The main way to configure a Dagger workspace is to add modules to it, possibly with custom workspace-level configuration.
A Dagger module is software packaged for the Dagger platform (engine & SDK). It implements functions and types to extend the capabilities of a Dagger workspace: typically with new ways to build, check, generate, deploy, or publish artifacts within the workspace.
Modules can access Dagger's powerful API to orchestrate system primitives (containers, secrets, etc), as well as interact with the contents of the workspace. This allows deep integration with the project's existing tools by parsing their configuration files and adapting behavior - the best of both worlds between native integration and cross-platform repeatability.
| Workspace | Module | |
|---|---|---|
| What it is | A directory (project context) | Packaged software |
| Configured via | .dagger/config.toml |
dagger.json |
| Contains | Modules added to it | Functions and types |
| Purpose | Configure Dagger for this project | Extend Dagger's capabilities |
| Analogy | A VS Code workspace | A VS Code extension |
Modules are added to workspaces. Once added, a module can access and interact with the workspace's contents.
There are two distinct ways to depend on a module:
| Relationship | Meaning | Configured in | Example |
|---|---|---|---|
| Workspace → Module | "Use this module in my project" | .dagger/config.toml |
Adding go-toolchain to build your Go code |
| Module → Module | "My code calls this module" | dagger.json |
Importing a helper library in your module's source |
These serve different purposes:
-
Workspace → Module is project configuration. The module gains access to your workspace and extends your CLI. This is how you set up your development environment.
-
Module → Module is code dependency. One module's implementation calls another module's functions. The dependency is internal - it doesn't affect the workspace or CLI.
A module added to a workspace is not the same as a module dependency. They live in different config files, are installed with different commands, and have different effects.
When the CLI starts:
-
Find workspace: Walk up from current directory looking for
.dagger/. If not found, workspace is empty (no modules installed). -
Load workspace modules: Parse
.dagger/config.tomland resolve each module in[modules]. -
Serve modules: Register all workspace modules with the engine for the session.
The CLI always operates in workspace context. There is no "module context" at the CLI level.
# Add a module to the workspace
dagger install github.com/dagger/go-toolchainThe module is registered under its name from dagger.json. Override with --name:
dagger install github.com/dagger/go-toolchain --name=goThis updates .dagger/config.toml. If no workspace exists yet:
- If in a git repository, creates
.dagger/config.tomlat the repo root - Otherwise, creates in the current directory
Once installed, module functions are available:
dagger call go build
dagger call go testConfig file: .dagger/config.toml (human-editable)
# Paths to ignore during workspace operations (extends .gitignore)
# ignore = ["docs/**", "marketing/**"]
# Modules added to this workspace
[modules]
ci = "modules/ci"
node = "github.com/dagger/node-toolchain@v1.0"
# Module with configuration (maps to constructor arguments)
[modules.go]
source = "github.com/dagger/go-toolchain@v1.0"
[modules.go.config]
goVersion = "1.22"
lintStrict = truePaths are relative to the .dagger/ directory.
The [modules.<name>.config] section sets default values for the module's constructor arguments:
# Simple case: source string only
[modules]
node = "github.com/dagger/node-toolchain@v1.0"
# With constructor defaults
[modules.go]
source = "github.com/dagger/go-toolchain@v1.0"
[modules.go.config]
goVersion = "1.22"
lintStrict = true
tags = ["integration", "unit"]The config keys map directly to constructor argument names. Supported types: strings, booleans, numbers, arrays.
This replaces customizations in dagger.json. Only constructor arguments can be configured - this keeps the config surface simple and encourages module authors to expose important settings as constructor parameters.
Module config and workspace settings can be overridden per environment (e.g., CI, staging, production). Environments are selected explicitly via --env:
dagger check --env=ciSee Part N: Environments (coming soon) for the full design.
Top-level ignore defines paths to skip during all workspace operations (glob, search, file access). This extends .gitignore for tracked-but-irrelevant parts of the repo:
ignore = ["docs/**", "marketing/**", "data-science/**"]The engine already respects .gitignore by default. The ignore key covers paths that are tracked in git but irrelevant to Dagger - useful in large monorepos to speed up module discovery.
For scoping which artifacts to operate on (rather than which files to see), use artifact path filtering. See Part 3: Artifacts:
dagger check --path='./myapp'Lock file: .dagger/lock (machine-managed)
[["version", "1"]]
["modules", "resolve", ["github.com/dagger/go-toolchain@v1.0"], "abc123..."]
["core", "git.ref", ["https://github.com/dagger/go-toolchain", "v1.0"], "abc123..."]
The lock file pins module versions to exact commits and caches runtime lookups (git refs, container digests, HTTP content). Modules can store their own lookups under their namespace. See PR #11156 for the lockfile design.
Most projects eventually need custom logic that doesn't fit existing modules - custom build steps, project-specific CI/CD, glue code combining multiple tools. This is when you create a module.
# From anywhere in the workspace
dagger module init --sdk=go ciThis:
- Creates
.dagger/modules/ci/with module source - Auto-installs in
.dagger/config.toml:[modules] ci = "modules/ci"
- Module is immediately callable:
dagger call ci <function>
The --sdk flag is required. Options: go, python, typescript, php, or a custom SDK module reference.
Directory structure:
repo/
├── .dagger/
│ ├── config.toml
│ └── modules/
│ └── ci/
│ ├── dagger.json
│ └── main.go
└── src/
Your module code lives in .dagger/modules/<name>/. Edit the generated source files to add functions:
// .dagger/modules/ci/main.go
func (m *Ci) Build(ctx context.Context) *dagger.Container {
// ...
}Changes take effect immediately - just run dagger call ci build.
To make a module installable by other projects:
Option 1: Promote an existing module
Move it from .dagger/modules/foo/ to a git-accessible location (dedicated repo, monorepo subdirectory, etc.).
Option 2: Start standalone
Run dagger module init outside any workspace:
mkdir my-module && cd my-module
dagger module init --sdk=go my-moduleWhen no workspace is found, the module is created in the current directory.
Either way, others can then install it:
dagger install github.com/you/my-moduleTo test a standalone module during development, create a workspace:
dagger install .
dagger call my-module <function>This section covers backwards compatibility and transition from the current model.
Currently dagger init creates a module. This is too accessible for an advanced feature - beginners may call it when they just want to set up their workspace.
Change: dagger init is replaced by dagger module init for creating modules. The dagger init command will show a deprecation warning directing users to either:
dagger installto add modules to a workspacedagger module initto create a new module
Currently, running dagger call in a directory with dagger.json loads that module and its dependencies into the CLI namespace.
Change: dagger call only looks at workspace configuration (.dagger/config.toml). Being in a module directory has no effect on the CLI namespace.
Migration: Module developers should set up a workspace in their module directory (see Development Workflow).
Currently -m specifies which module to load as the "current module".
Change: The -m flag becomes an escape hatch for directly calling a module without workspace context:
dagger call -m github.com/dagger/go-toolchain buildThis loads the module in isolation (no workspace modules, no module dependencies). Same behavior as today. Useful for testing or one-off calls.
Currently dagger install adds a dependency to the current module's dagger.json.
Change: dagger install adds a module to the workspace's .dagger/config.toml.
To add a code dependency to a module (for use in module source code), use:
dagger module dependency add github.com/org/helper-libThe dagger.json format changes:
| Field | Change |
|---|---|
sdk |
Now required (was optional) |
toolchains |
Deprecated (use workspace modules instead) |
Projects with existing dagger.json files containing dependencies or toolchains:
Automatic fallback: If .dagger/config.toml doesn't exist, the CLI falls back to current behavior (reading from dagger.json). A deprecation warning is shown.
Migration command:
dagger migrateThis converts dagger.json dependencies/toolchains to .dagger/config.toml modules, preserving names and versions.
Design phase. Depends on team alignment on the workspace/module distinction.
Next: Part 2: Workspace API
Changelog
[modules.<name>.config]maps to constructor argumentscustomizationsin dagger.json[fs]section with global include/excludeignorecustomizations