Skip to content

Instantly share code, notes, and snippets.

@shykes
Last active February 5, 2026 20:30
Show Gist options
  • Select an option

  • Save shykes/e4778dc5ec17c9a8bbd3120f5c21ce73 to your computer and use it in GitHub Desktop.

Select an option

Save shykes/e4778dc5ec17c9a8bbd3120f5c21ce73 to your computer and use it in GitHub Desktop.
Dagger Design: Part 1 - Module vs. Workspace

Part 1: Module vs. Workspace

Table of Contents

Problem

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.

Core Concepts

This proposal establishes workspaces and modules as two fundamentally different things, each with their own configuration file and dependency model.

What is a Workspace?

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.

What is a Module?

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.

Comparison

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.

Dependency Model

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.

Configure a Workspace

How Dagger Loads Configuration

When the CLI starts:

  1. Find workspace: Walk up from current directory looking for .dagger/. If not found, workspace is empty (no modules installed).

  2. Load workspace modules: Parse .dagger/config.toml and resolve each module in [modules].

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

Adding Modules

# Add a module to the workspace
dagger install github.com/dagger/go-toolchain

The module is registered under its name from dagger.json. Override with --name:

dagger install github.com/dagger/go-toolchain --name=go

This updates .dagger/config.toml. If no workspace exists yet:

  • If in a git repository, creates .dagger/config.toml at the repo root
  • Otherwise, creates in the current directory

Once installed, module functions are available:

dagger call go build
dagger call go test

Configuration Reference

Config 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 = true

Paths are relative to the .dagger/ directory.

Module Configuration

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.

Environments

Module config and workspace settings can be overridden per environment (e.g., CI, staging, production). Environments are selected explicitly via --env:

dagger check --env=ci

See Part N: Environments (coming soon) for the full design.

Workspace Ignore

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.

Develop a Module

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.

Create a Module

# From anywhere in the workspace
dagger module init --sdk=go ci

This:

  1. Creates .dagger/modules/ci/ with module source
  2. Auto-installs in .dagger/config.toml:
    [modules]
    ci = "modules/ci"
  3. 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/

Write Module Code

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.

Share a Module

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-module

When 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-module

To test a standalone module during development, create a workspace:

dagger install .
dagger call my-module <function>

Migration

This section covers backwards compatibility and transition from the current model.

dagger init

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 install to add modules to a workspace
  • dagger module init to create a new module

dagger call in Module Directories

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

dagger call -m Flag

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 build

This loads the module in isolation (no workspace modules, no module dependencies). Same behavior as today. Useful for testing or one-off calls.

dagger install Behavior

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-lib

dagger.json Changes

The dagger.json format changes:

Field Change
sdk Now required (was optional)
toolchains Deprecated (use workspace modules instead)

Migrating Existing Projects

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 migrate

This converts dagger.json dependencies/toolchains to .dagger/config.toml modules, preserving names and versions.

Status

Design phase. Depends on team alignment on the workspace/module distinction.


Next: Part 2: Workspace API

@shykes
Copy link
Author

shykes commented Feb 5, 2026

Changelog

  • Removed [fs] workspace filters section
  • Scoping operations to part of the workspace belongs in the artifact layer (path-based filtering via dagger check --path), not filesystem-level hiding
  • Added cross-reference to Part 3 for path filtering

@shykes
Copy link
Author

shykes commented Feb 5, 2026

Changelog

  • Added [workspace] ignore for monorepo performance optimization
    • Extends .gitignore for tracked-but-irrelevant paths
    • Applied to all workspace operations (glob, search, file access)
    • Distinct from artifact path filtering (scoping operations vs hiding files)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment