# Configuring your Workspace URL: https://docs.dagger.io/config/index # Configuring your Workspace How a workspace is configured: the settings in `dagger.toml` and how modules connect to each other. - [User configuration](./user.mdx) holds personal overrides that stay out of the repository. - [Module wiring](./module-wiring.mdx) connects one module's output to another module's settings. - [Migrate from dagger.json](./migrate-dagger-json.mdx) converts a legacy project to a workspace. - [Configuration files](../reference/config-files/index.mdx) has the schemas for the files that configure Dagger. --- # Migrate from dagger.json URL: https://docs.dagger.io/config/migrate-dagger-json # Migrate from dagger.json :::note If you're new to Dagger, you can skip this page. It's for existing users encountering changes. ::: In Dagger 0.x, `dagger.json` served two roles: 1. A manifest for your *module*: a package of code that the Dagger engine can load and execute 2. A configuration file for your *workspace*: the software repository which you want to use Dagger in In Dagger 1.0, these two roles are filled by two different files: | Role | 0.x file | 1.0 file | | -- | -- | -- | | Manifest for a module | `dagger.json` | `dagger-module.toml` | | Configuration for a workspace | `dagger.json` | `dagger.toml` | ## Backwards compatibility Dagger 1.0 can still load `dagger.json` in both roles, so you don't have to rush a migration. But, we recommend you migrate to take advantage of all the features of 1.0. ## Migrating your workspace To migrate your workspace: ```shell dagger workspace migrate ``` This will interpret your `dagger.json` as a workspace configuration, and translate it to an equivalent `dagger.toml`. Each migrated module is registered as a scope of its SDK. The scope gets an explicit `name` only when the module's name differs from the name Dagger infers from the scope directory or its entrypoint installation. See the [`[sdks.]` reference](../reference/config-files/dagger-toml.mdx#sdksname). | `dagger.json` field | Translation | | -- | -- | | `sdk` | Create a module at `.dagger/modules/`, install it in `dagger.toml`, make it the workspace entrypoint | | `toolchains` | Each installed toolchain is migrated as a 1.0 module (see "migrating a module") then installed in `dagger.toml`. Its customizations are converted to module settings, when possible | | `blueprint` | This field cannot be migrated | ## Coding agents When Dagger detects that a coding agent is running `dagger workspace migrate` or `dagger module migrate`, it requires an explicit choice up front. Pass `-y` to apply the migration, or `--no-apply` to show the changes without writing them: ```shell dagger workspace migrate --no-apply ``` Optional module candidates are skipped in both cases. Use `dagger module migrate PATH` to migrate one explicitly. ## Migration report After migration, a report is written to `.dagger/migration-report.md`. Read it to know which parts of your `dagger.json` may not have been successfully migrated. ## Migrating a module To migrate a module from `dagger.json` to `dagger-module.toml`: ```shell dagger module migrate ./path/to/module ``` This will translate only the selected module, without migrating the surrounding workspace. --- # Module wiring URL: https://docs.dagger.io/config/module-wiring # Module wiring Modules in a workspace connect to each other through settings. A setting whose value is a `":"` string is a module reference. It injects the value returned by a function on another installed module. This is how generic modules compose without knowing about each other, and without you writing a glue module. ## Wire a service into a module A test-runner module accepts an optional `Service`; your app module has a function that returns one. Connect them in `dagger.toml`: ```toml [modules.myapp] source = "./ci/myapp" [modules.playwright] source = "dagger.io/js/playwright" [modules.playwright.settings] service = "myapp:serve" ``` Now `dagger check playwright:test` runs the browser tests against your app. Dagger resolves the reference when it constructs the playwright module and passes the running service in. To find functions you can wire, run `dagger up -l`. It lists every service-returning function in the workspace, in exactly the `module:function` form a setting accepts. Any function returning the right type works, whether or not it appears there. ## Wire a container References aren't limited to services. A `Container` argument wires the same way: ```toml [modules.playwright.settings] baseCtr = "base-images:chromium" ``` ## Wire a file, directory, or workspace Build artifacts and workspaces can be passed between modules in the same way. A function returning a `File`, `Directory`, or `Workspace` can be referenced by a matching constructor argument: ```toml [modules.packager.settings] binary = "builder:binary" assets = "frontend:assets" source = "source-prep:workspace" ``` ## Reference the workspace entrypoint The entrypoint module's functions are hoisted onto the workspace root, so a reference to one can drop the module name: ```shell dagger settings playwright service serve ``` The config always stores the long form: ```toml [modules.myapp] source = "./ci/myapp" entrypoint = true [modules.playwright.settings] service = "myapp:serve" ``` A bare name is only treated as a reference when the entrypoint has a function of that name; otherwise it keeps its ordinary address meaning. ## Use a reference on the command line A module reference is an ordinary address string, so it also works as a CLI flag for any object-typed constructor argument: ```shell dagger api call playwright --service=myapp:serve test ``` ## How references resolve - The leading segment is a module's install name, the `[modules.X]` key in this `dagger.toml`. The second segment is a zero-arg function on it whose return type matches the argument. A bare `` with no module segment refers to the workspace entrypoint module. - If the first segment names an installed module, the string *is* a module reference. A missing function or mismatched type is then a hard error, never a silent fallback to an image or URL. If no install name matches, the string keeps its ordinary address meaning: an OCI ref for a `Container`, a `tcp://` URL for a `Service`. - Core names (`host`, `git`, `secret`, `container`, `http`, `module`, and so on) are reserved and never resolve as module references, so `git:2.40` stays an image ref. ## Design your module for wiring If you author modules, wiring changes how you shape a constructor: - **Accept collaborators as optional constructor arguments.** An optional `Service`, `Container`, `File`, `Directory`, or `Workspace` argument is a wiring point. Without one, users need a glue module to connect yours to anything. - **Consume workspaces in the constructor.** A module object cannot retain a `Workspace` as a field. Derive and store the `File`, `Directory`, or other value your module needs from the wired workspace instead. - **Put workspace-level configuration on the constructor, not on function arguments.** Settings map to constructor arguments. A shard count, a service, or a base image belongs there if the workspace should configure it once. - **Degrade gracefully.** When nothing is wired, do something sensible rather than failing: skip the service binding, or fall back to a default image. The workspace may have nothing to wire yet. The [Playwright module](../reference/modules/js/playwright.mdx) is a worked example of all three. --- # Secrets URL: https://docs.dagger.io/config/secrets # Secrets Dagger has built-in support for secrets — passwords, API keys, tokens — sourced from multiple providers. Secrets are never exposed in logs, written to container filesystems, or inserted into the cache. ## Passing secrets Secrets are passed to functions via provider URIs: ```shell # From environment variables dagger api call deploy --token=env:DEPLOY_TOKEN # From files dagger api call deploy --token=file:./secrets/token.txt # From command output dagger api call deploy --token=cmd:"aws sts get-session-token --query Token" ``` ## Providers ### Environment variables ```shell dagger api call my-function --secret=env:MY_SECRET ``` Reads the value of `MY_SECRET` from the host environment. ### Files ```shell dagger api call my-function --secret=file:/path/to/secret ``` Reads the secret from a file on the host. ### Command output ```shell dagger api call my-function --secret=cmd:"command to run" ``` Runs the command on the host and uses its stdout as the secret value. ### HashiCorp Vault Dagger can resolve secrets directly from Vault using the `vault://` scheme. Only KVv2 mounts are supported. ​```shell dagger api call my-function --secret=vault://secret/foo/bar.token ​``` Reads the `token` field of the KVv2 secret `foo/bar` from the `secret` mount. **URI format:** `vault:///.` — the mount is the first path segment, the field is everything after the last dot. **Caching:** by default, ttl is infinite. Add `?ttl=1h` to cache the resolved value client-side for the given duration. **Authentication:** requires the Dagger CLI to be authenticated with Vault via `VAULT_ADDR`, plus auth-method-specific variables: - `VAULT_TOKEN` — token authentication - `VAULT_APPROLE_MOUNT_PATH`, `VAULT_APPROLE_ROLE_ID`, `VAULT_APPROLE_SECRET_ID` — AppRole authentication - `VAULT_OIDC_MOUNT_PATH`, `VAULT_OIDC_CALLBACK_PORT`, `VAULT_OIDC_ROLE`, `VAULT_OIDC_SKIP_BROWSER` - OIDC authentication ### 1Password ```shell dagger api call my-function --secret=op://vault-name/item-name/field ``` Reads from 1Password. Requires authentication via `op signin`. ### AWS Secrets Manager ```shell dagger api call my-function --secret=aws+sm://prod/my-secret ``` For JSON secrets, extract a specific field: ```shell dagger api call my-function --secret=aws+sm://prod/database?field=password ``` Options: `?region=us-west-2`, `?version=`, `?stage=AWSPREVIOUS`. ### AWS Parameter Store ```shell dagger api call my-function --secret=aws+ps://prod/api-key ``` Reads the parameter `/prod/api-key` (the leading slash is added for you). SecureString parameters are automatically decrypted. ### AWS authentication Both AWS providers use the [default credential chain](https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html): environment variables, shared credentials file (`~/.aws/credentials`), or IAM role (EC2, ECS, Lambda). Set the region with `AWS_REGION` or the `?region=` query parameter. ### Google Cloud Secret Manager ```shell dagger api call my-function --secret=gcp://my-secret ``` A bare name reads the secret from the project set by `GCP_PROJECT_ID`, `GOOGLE_CLOUD_PROJECT`, or `GCLOUD_PROJECT`. Use `gcp://projects/PROJECT_ID/secrets/SECRET_NAME/versions/VERSION` to specify the project and version explicitly. Authentication uses [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials). ### libsecret (GNOME Keyring) ```shell dagger api call my-function --secret=libsecret://login/my-secret ``` Reads from the host's freedesktop.org Secret Service (e.g. GNOME Keyring) on Linux. ## Safeguards Dagger ensures secrets never leak: - **Logs**: Secret values are redacted from all output - **Filesystem**: Secrets are never written to container layers - **Cache**: Operations using secrets are excluded from cache keys If a workflow crashes, secrets remain protected. --- # User configuration URL: https://docs.dagger.io/config/user # User configuration Some values shouldn't be committed: private account profiles, local paths, personal development clusters. Those live in your user-level Dagger config file, `~/.config/dagger/config.toml`, or the file named by `$DAGGER_CONFIG`. The file is shared with other Dagger subsystems (such as `[llm]`). Workspace overrides sit in a `[workspaces.*]` section, keyed by the workspace's Git remote: ```toml # Always applied when working in the github.com/acme/api workspace: [workspaces."github.com/acme/api".modules.aws.settings] profile = "alice-dev" ``` ## Merge order Dagger merges your user-level overrides on top of the repository's `dagger.toml`. User-level values shadow repository values key by key, without modifying `dagger.toml`. ## Writing values You don't have to edit the file by hand. Pass `-g/--global` to `dagger module settings` or `dagger workspace config` to store a value user-level instead of in the repository: ```shell dagger module settings -g aws profile alice-dev # always applied here dagger module settings -g -u aws profile # remove the override dagger workspace config -g modules.aws.settings.profile alice-dev ``` `--global` selects where a write is stored; reads always show the effective merged view. Unsetting with `-g` removes only the user-level value. The repository value underneath is untouched. `-g` also composes with `-W`. A remote workspace is readable but its repository config can't be written, while its user-level overrides live in your local file: ```shell dagger -W https://github.com/acme/api settings -g aws profile alice-dev ``` ## What can be stored Only module settings: `modules..settings.*`. Because one key spans every branch and clone of a repository, an entry for a module that doesn't exist in the current checkout is ignored there rather than being an error. ## Workspace key The key is the normalized `origin` remote: host and path, with no scheme, no user, and no `.git` suffix. Equivalent spellings all match: `git@github.com:acme/api.git`, `https://github.com/acme/api`, and `github.com/acme/api` identify the same workspace, both as the config key and in the repository's git config. A repository with multiple remotes is keyed by `origin` only; a repository with no remote matches no user-level overrides. Remote workspaces selected with `-W` are keyed by their clone address. --- # Set up Cloud Checks URL: https://docs.dagger.io/getting-started/cloud-checks # Set up Cloud Checks In this guide, you will connect your GitHub repository to Dagger Cloud, and run your Checks automatically after each push. Complete the [Quickstart](./quickstart.mdx) before you start. A Git event, such as a push or pull request, starts Cloud Checks. They run your Checks on Cloud Engines. You do not need a CI workflow file. Your project must be a GitHub repository, and its Git `origin` remote must point to that repository. ## Enable Cloud Checks From your project root: ```shell dagger cloud checks on ``` Dagger gets the repository from the Git `origin` remote. The command prints `on` when Cloud Checks are enabled. If they are already enabled, it makes no change. In an interactive terminal, the command offers to complete missing prerequisites: - Sign in or create a Dagger Cloud account and organization. - Connect the GitHub account or organization that owns the repository. In the browser, grant Dagger access to the repository. Each prerequisite must finish before the command enables Cloud Checks. You can decline a prompt and complete it later. In non-interactive mode, the command stops and prints the required command followed by the command to retry. It does not open a browser or wait for input. You can also complete the prerequisites separately: ```shell dagger cloud signup dagger cloud integration create github --open ``` Complete the browser steps, then retry: ```shell dagger cloud checks on ``` ## Trigger Cloud Checks Commit the files that the Quickstart created, then push them: ```shell git add dagger.toml git commit -m "Set up Dagger" git push -u origin HEAD ``` If `dagger generate` changed other files, stage those too. Run `git status --short` to list them. ## Watch the Checks pass The push started your Checks. Watch them run: ```shell dagger activity ``` Find the row for `Set up Dagger`. It can take a moment to appear. Run the command again until its `CHECKS` column is green. If a Check turns red, run `dagger check` locally. Fix the failure. Then commit and push the fix. Green means that every Check passed. ## What you accomplished Your project now checks itself. Every push and every pull request runs your Checks automatically, on Dagger's Cloud Engines. You did not write a CI workflow file. You do not maintain a build server. The Checks that run on each push are the same Checks that `dagger check` runs on your machine, so the two cannot drift apart. ## Next steps - [Run selected Checks](../using/checking.mdx) - [Manage Cloud Checks from the CLI](../reference/cli/index.mdx#dagger-cloud-checks) --- # Install the Dagger CLI URL: https://docs.dagger.io/getting-started/install import { daggerVersion } from '../partials/version.js'; Use the command for your operating system. Each command installs Dagger v{daggerVersion}. {`curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=${daggerVersion} BIN_DIR=/usr/local/bin sh`} If you cannot write to `/usr/local/bin`, run the script with `sudo -E`: {`curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=${daggerVersion} BIN_DIR=/usr/local/bin sudo -E sh`} {`curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=${daggerVersion} BIN_DIR=$HOME/.local/bin sh`} Verify that `$HOME/.local/bin` is in your `PATH`. To install Dagger for all users, run the script with `sudo -E`: {`curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=${daggerVersion} BIN_DIR=/usr/local/bin sudo -E sh`} In PowerShell 7 or later: {`iwr -useb https://dl.dagger.io/dagger/install.ps1 | iex; Install-Dagger -DaggerVersion ${daggerVersion} -AddToPath`} This installs `dagger.exe` in `%USERPROFILE%\dagger` and adds it to your user `PATH`. Run this command to verify the installation: {`dagger version\n# version: v${daggerVersion}`} To change the Dagger version, run the applicable install command again. Specify the required version. --- # Introduction URL: https://docs.dagger.io/getting-started/introduction # Introduction Dagger is the missing software stack for CI. It makes your pipelines faster and more repeatable, not by throwing bigger machines at them, but by replacing artisanal scripts with a clean API, real code, and a portable DAG execution engine. Once daggerized, your pipeline logic is decoupled from its environment. Trigger it before or after push; run it locally or let our cloud scale it out; spin up multi-container environments just in time; all cached automatically, traced end to end, and extensible in your favorite language. --- # Quickstart URL: https://docs.dagger.io/getting-started/quickstart # Quickstart In this guide, you will create a basic Dagger configuration for your project, and run your first Checks locally. Complete [Try Dagger](./try-dagger.mdx) before you start. Dagger configures the project that you run it in. Run every command in this guide from your project root. ## Install modules Initialize the workspace: ```shell dagger init ``` This creates an empty `dagger.toml`, or uses the existing file. This file holds the workspace configuration for everyone who uses the repository. In interactive mode, follow the optional setup prompts. Select modules that match your project. Then select **Install selected**. In non-interactive mode, `init` prints commands to run in order: ```shell #!/bin/sh # 1. Find and install recommended modules. dagger module recommend # 2. Enable Cloud checks (optional). dagger cloud checks on ``` If `init` detects a legacy workspace, it stops without changing files. Run `dagger workspace migrate` to review the migration. After migration, Dagger offers the same next steps. Migration includes installed local modules. Other module candidates are optional and can include fixtures. Non-interactive migration skips these candidates, including with `--auto-apply`. To migrate one explicitly, run `dagger module migrate PATH`. ### If no modules are recommended If Dagger reports `No recommendations`, search for a module by the name of a tool in your project. For example, if the project uses ESLint: ```shell dagger module search eslint dagger module install dagger.io/js/eslint ``` ## Configure modules Most modules use suitable default settings. List the available settings: ```shell dagger module settings ``` Change a setting when the default does not match the project. For example, if you installed ESLint and the project uses Yarn: ```shell dagger module settings eslint packageManager yarn ``` Settings are stored in `dagger.toml`, so they apply to all users of the workspace. ## Run Generators and Checks List the workspace Generators: ```shell dagger generate -l ``` If the list is not empty, run them: ```shell dagger generate ``` Review the changes. Apply them when requested. List the Checks: ```shell dagger check -l ``` If no Check validates your project, install another suitable module. If at least one does, run the Checks: ```shell dagger check ``` You are done when at least one Check runs and all Checks pass. --- # Try Dagger URL: https://docs.dagger.io/getting-started/try-dagger # Try Dagger In this guide, you will add a Check to an example project. You will fix a failure and run the Check successfully. ## Requirements - [Dagger CLI](./install.mdx) - [Git](https://git-scm.com/downloads) ## Clone the example project Clone the repository and open its directory: ```shell git clone --depth 1 https://github.com/dagger/hello-dagger.git cd hello-dagger ``` ## Add a Check Install the official Prettier module: ```shell dagger module install dagger.io/js/prettier ``` The command creates `dagger.toml`. It also adds `prettier:check` to the workspace. ## Run the Check ```shell dagger check ``` `prettier:check` reports unformatted files. The command exits with an error. ## Fix the formatting Run Prettier and apply its changeset: ```shell dagger api call prettier write -y ``` ## Verify the result Run the Check again: ```shell dagger check ``` You are done when the output shows that `prettier:check` passed. --- Here is an example call for this Dagger Function: ```shell dagger -c version ``` ```shell title="First type 'dagger' for interactive mode." version ``` ```shell dagger api call version ``` The result will be: ```shell VERSION_ID=3.14.0 ``` --- :::note This page documents an upcoming release of Dagger. This release is currently experimental and should not be considered production-ready. If you arrived at this page by accident, you can [return to the official documentation](../index.mdx). ::: --- Volume caching involves caching specific parts of the filesystem and reusing them on subsequent function calls if they are unchanged. This is especially useful when dealing with package managers such as `npm`, `maven`, `pip` and similar. Since these dependencies are usually locked to specific versions in the application's manifest, re-downloading them on every session is inefficient and time-consuming. The `CacheVolume` type represents a directory whose contents persist across Dagger sessions. By using a cache volume for dependencies, Dagger can reuse the cached contents across Dagger workflow runs and reduce execution time. --- The `Container` type represents the state of an OCI-compatible container. This `Container` object is not merely a string referencing an image on a remote registry. It is the actual state of a container, managed by the Dagger Engine, and passed to a Dagger Function's code as if it were just another variable. --- The `CurrentModule` type provides capabilities to introspect the Dagger Function's module and interface between the current execution environment and the Dagger API. --- Dagger Functions do not have access to the filesystem of the host you invoke the Dagger Function from (i.e. the host you execute a CLI command like `dagger` from). Instead, host files and directories need to be explicitly passed as command-line arguments to Dagger Functions. There are two important reasons for this. - Reproducibility: By providing a call-time mechanism to define and control the files available to a Dagger Function, Dagger guards against creating hidden dependencies on ambient properties of the host filesystem that could change at any moment. - Security: By forcing you to explicitly specify which host files and directories a Dagger Function "sees" on every call, Dagger ensures that you're always 100% in control. This reduces the risk of third-party Dagger Functions gaining access to your data. The `Directory` type represents the state of a directory. This could be either a local directory path or a remote Git reference. --- The `Env` type represents an environment consisting of inputs and desired outputs, for use by an `LLM`. For example, an environment might provide a `Directory`, a `Container`, a custom module, and a string variable as inputs, and request a `Container` as output. --- The `File` type represents a single file. --- The `GitRepository` type represents a Git repository. --- The `LLM` type initializes a Large Language Model (LLM). :::tip `ENV` TYPE You use an `LLM` in conjunction with `Env`. The `Env` type is used to represent the environment in which an LLM operates. It allows the LLM to interact with inputs and outputs, such as directories, containers, and custom modules. ::: --- Dagger allows you to utilize confidential information ("secrets") such as passwords, API keys, SSH keys and so on, without exposing those secrets in plaintext logs, writing them into the filesystem of containers you're building, or inserting them into the cache. The `Secret` type is used to represent these secret values. --- The `Service` type represents a content-addressed service providing TCP connectivity. --- # Address URL: https://docs.dagger.io/reference/api/address {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # AgentMessage URL: https://docs.dagger.io/reference/api/agent-message {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # AgentMiddlewareGroup URL: https://docs.dagger.io/reference/api/agent-middleware-group {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # AgentMiddleware URL: https://docs.dagger.io/reference/api/agent-middleware {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Agent URL: https://docs.dagger.io/reference/api/agent {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # All types URL: https://docs.dagger.io/reference/api/all import ApiTypeList from "@site/src/components/api/ApiTypeList"; Every published core API type has a reference page generated from the Dagger GraphQL schema. --- # CacheVolume URL: https://docs.dagger.io/reference/api/cache-volume import CacheVolumeType from "@daggerTypes/_cache-volume.mdx"; import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Changeset URL: https://docs.dagger.io/reference/api/changeset {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # CheckGroup URL: https://docs.dagger.io/reference/api/check-group {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Check URL: https://docs.dagger.io/reference/api/check {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # ClientFilesyncMirror URL: https://docs.dagger.io/reference/api/client-filesync-mirror {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Cloud URL: https://docs.dagger.io/reference/api/cloud {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Container URL: https://docs.dagger.io/reference/api/container import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import ContainerType from "@daggerTypes/_container.mdx"; import ApiType from "@site/src/components/api/ApiType"; ## Default addresses It is possible to assign a default address for a `Container` argument in a Dagger Function. Dagger will automatically use this default address to pull the container image when no value is specified for the argument. :::tip Default addresses are only available for `Container` arguments. They are commonly used to provide a sensible default base image for build or test operations. When a value is explicitly passed for the argument, it always overrides the default address. ::: Here's an example: The default address is set by adding a `defaultAddress` pragma on the corresponding Dagger Function `ctr` argument. ```go file=./snippets/default-address/go/main.go ``` The default address is set by adding a `DefaultAddress` annotation on the corresponding Dagger Function `ctr` argument. ```python file=./snippets/default-address/python/main.py ``` The default address is set by adding an `@argument` decorator with a `defaultAddress` parameter on the corresponding Dagger Function `ctr` argument. ```typescript file=./snippets/default-address/typescript/index.ts ``` The default address is set by adding a `#[DefaultAddress]` Attribute on the corresponding Dagger Function `ctr` argument. ```php file=./snippets/default-address/php/src/MyModule.php ``` The default address can be any valid container image reference, such as: - `alpine:latest` - Docker Hub image with tag - `alpine:3.19` - Docker Hub image with specific version - `ghcr.io/owner/image:tag` - GitHub Container Registry image - `gcr.io/project/image:tag` - Google Container Registry image ## Volatile variables `withVolatileVariable` sets a non-secret environment variable for future `withExec` calls without invalidating exec cache when only the variable's value changes. Typical examples include CI and reporting metadata such as commit SHAs, branch or ref names, and CI run IDs. :::warning `withVolatileVariable` is an expert-only escape hatch. Use it only when you are certain that changing the variable alone must not invalidate cached `withExec` results. If that assumption is wrong, Dagger may reuse stale or incorrect cached results. ::: Unlike `withEnvVariable`, volatile variables: - are visible only to future `withExec` calls - are not persisted into the container image config - are not returned by `envVariable` or `envVariables` - are not available to `expand: true` Use `withEnvVariable` for normal container configuration, `withSecretVariable` for sensitive values, and `withVolatileVariable` only for exec-time metadata that should not decide cache reuse. ## API reference --- # CurrentModule URL: https://docs.dagger.io/reference/api/current-module import CurrentModuleType from "@daggerTypes/_current-module.mdx"; import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # DiffStat URL: https://docs.dagger.io/reference/api/diff-stat {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Directory URL: https://docs.dagger.io/reference/api/directory import Directory from '@daggerTypes/_directory.mdx'; import ApiType from "@site/src/components/api/ApiType"; ## Reading workspace files Inside a module, you don't take the user's project directory as a function argument. Instead, your module's constructor receives a [`Workspace`](../sdks/index.mdx#developer-workflow) that Dagger auto-populates from the current workspace, and you read directories and files from it lazily with `Workspace.directory(path)` and `Workspace.file(path)` — nothing is uploaded until a function actually uses it. See your [SDK guide](../sdks/index.mdx) for the exact syntax. ## Filters When you pass a directory to a Dagger Function as argument, Dagger uploads everything in that directory tree to the Dagger Engine. For large monorepos or directories containing large-sized files, this can significantly slow down your Dagger Function while filesystem contents are transferred. To mitigate this problem, Dagger lets you apply filters to control which files and directories are uploaded. Dagger offers pre- and post-call filtering to mitigate this problem and optimize how your directories are handled. Filtering improves the performance of your Dagger Functions in three ways: - It reduces the size of the files being transferred from the host to the Dagger Engine, allowing the upload step to complete faster. - It ensures that minor unrelated changes in the source directory don't invalidate Dagger's build cache. - It enables different use-cases, such as setting up component/feature/service-specific workflows for monorepos. It is worth noting that Dagger already uses caching to optimize file uploads. Subsequent calls to a Dagger Function will only upload files that have changed since the preceding call. Filtering is an additional optimization that you can apply to improve the performance of your Dagger Function. ### Pre-call filtering Pre-call filtering means that a directory is filtered before it's uploaded to the Dagger Engine container. This is useful for: - Large monorepos. Typically your Dagger Function only operates on a subset of the monorepo, representing a specific component or feature. Uploading the entire worktree imposes a prohibitive cost. - Large files, such as audio/video files and other binary content. These files take time to upload. If they're not directly relevant, you'll usually want your Dagger Function to ignore them. :::tip The `.git` directory is a good example of both these cases. It contains a lot of data, including large binary objects, and for projects with a long version history, it can sometimes be larger than your actual source code. ::: - Dependencies. If you're developing locally, you'll typically have your project dependencies installed locally: `node_modules` (Node.js), `.venv` (Python), `vendor` (PHP) and so on. When you call your Dagger Function locally, Dagger will upload all these installed dependencies as well. This is both bad practice and inefficient. Typically, you'll want your Dagger Function to ignore locally-installed dependencies and only operate on the project source code. :::note Dagger Functions are not aware of the host filesystem, so they cannot automatically read exclusion patterns from existing `.dockerignore` or `.gitignore` files. You need to manually implement the same patterns in your Dagger Function. At the time of writing, Dagger [does not read exclusion patterns from existing `.dockerignore`/`.gitignore` files](https://github.com/dagger/dagger/issues/6627). If you already use these files, you'll need to manually implement the same patterns in your Dagger Function. ::: To implement a pre-call filter in your Dagger Function, add an `ignore` parameter to your `Directory` argument. The `ignore` parameter follows the [`.gitignore` syntax](https://git-scm.com/docs/gitignore). Some important points to keep in mind are: - The order of arguments is significant: the pattern `"**", "!**"` includes everything but `"!**", "**"` excludes everything. - Prefixing a path with `!` negates a previous ignore: the pattern `"!foo"` has no effect, since nothing is previously ignored, while the pattern `"**", "!foo"` excludes everything except `foo`. Here's an example of a Dagger Function that excludes everything in a given directory except Go source code files: ```go file=./snippets/fs-filters/pre-call/go/main.go ``` Here's an example of a Dagger Function that excludes everything in a given directory except Python source code files: ```python file=./snippets/fs-filters/pre-call/python/main.py ``` Here's an example of a Dagger Function that excludes everything in a given directory except TypeScript source code files: ```typescript file=./snippets/fs-filters/pre-call/typescript/index.ts ``` Here's an example of a Dagger Function that excludes everything in a given directory except PHP source code files: ```php file=./snippets/fs-filters/pre-call/php/src/MyModule.php ``` Here's an example of a Dagger Function that excludes everything in a given directory except Java source code files: ```java file=./snippets/fs-filters/pre-call/java/MyModule.java ``` Here are a few examples of useful patterns: ```go // exclude Go tests and test data // +ignore=["**_test.go", "**/testdata/**"] // exclude binaries // +ignore=["bin"] // exclude Python dependencies // +ignore=["**/.venv", "**/__pycache__"] // exclude Node.js dependencies // +ignore=["**/node_modules"] // exclude Git metadata // +ignore=[".git", "**/.gitignore"] ```` You can also split them into multiple lines: ```go // +ignore=[ // "**_test.go", // "**/testdata/**" // ] ```` ```python # exclude Pytest tests and test data Ignore(["tests/", ".pytest_cache"]) # exclude binaries Ignore(["bin"]) # exclude Python dependencies Ignore(["**/.venv", "**/__pycache__"]) # exclude Node.js dependencies Ignore(["**/node_modules"]) # exclude Git metadata Ignore([".git", "**/.gitignore"]) ```` ```typescript // exclude Mocha tests @argument({ ignore: ["**.spec.ts"] }) // exclude binaries @argument({ ignore: ["bin"] }) // exclude Python dependencies @argument({ ignore: ["**/.venv", "**/__pycache__"] }) // exclude Node.js dependencies @argument({ ignore: ["**/node_modules"] }) // exclude Git metadata @argument({ ignore: [".git", "**/.gitignore"] }) ```` ```php // exclude PHPUnit tests and test data #[Ignore('tests/', '.phpunit.cache', '.phpunit.result.cache')] // exclude binaries #[Ignore('bin')] // exclude Composer dependencies #[Ignore('vendor/')] // exclude Node.js dependencies #[Ignore('**/node_modules')] // exclude Git metadata #[Ignore('.git/', '**/.gitignore')] ```` ```java // exclude Java tests and test data @Ignore({"src/test"}) // exclude binaries @Ignore({"bin"}) // exclude Python dependencies @Ignore({"**/.venv", "**/__pycache__"}) // exclude Node.js dependencies @Ignore({"**/node_modules"}) // exclude Git metadata @Ignore({".git", "**/.gitignore"}) ```` ### Post-call filtering Post-call filtering means that a directory is filtered after it's uploaded to the Dagger Engine. This is useful when working with directories that are modified "in place" by a Dagger Function. When building an application, your Dagger Function might modify the source directory during the build by adding new files to it. A post-call filter allows you to use that directory in another operation, only fetching the new files and ignoring the old ones. A good example of this is a multi-stage build. Imagine a Dagger Function that reads and builds an application from source, placing the compiled binaries in a new sub-directory (stage 1). Instead of then transferring everything to the final container image for distribution (stage 2), you could use a post-call filter to transfer only the compiled files. To implement a post-call filter in your Dagger Function, use the `DirectoryWithDirectoryOpts` or `ContainerWithDirectoryOpts` structs, which support `Include` and `Exclude` patterns for `Directory` objects. Here's an example: ```go file=./snippets/fs-filters/post-call/go/main.go ``` To implement a post-call filter in your Dagger Function, use the `include` and `exclude` parameters when working with `Directory` objects. Here's an example: ```python file=./snippets/fs-filters/post-call/python/main.py ``` To implement a post-call filter in your Dagger Function, use the `include` and `exclude` parameters when working with `Directory` objects. Here's an example: ```typescript file=./snippets/fs-filters/post-call/typescript/index.ts ``` To implement a post-call filter in your Dagger Function, use the `include` and `exclude` parameters when working with `Directory` objects. Here's an example: ```php file=./snippets/fs-filters/post-call/php/src/MyModule.php ``` To implement a post-call filter in your Dagger Function, use the `Container.WithDirectoryArguments` class which support `withInclude` and `withExclude` functions when working with `Directory` objects. Here's an example: ```java file=./snippets/fs-filters/post-call/java/MyModule.java ``` Here are a few examples of useful patterns: ```go // exclude all Markdown files dirOpts := dagger.ContainerWithDirectoryOpts{ Exclude: "*.md*", } // include only the build output directory dirOpts := dagger.ContainerWithDirectoryOpts{ Include: "build", } // include only ZIP files dirOpts := dagger.DirectoryWithDirectoryOpts{ Include: "\*.zip", } // exclude Git metadata dirOpts := dagger.DirectoryWithDirectoryOpts{ Exclude: "\*.git", } ```` ```python # exclude all Markdown files dir_opts = {"exclude": ["*.md*"]} # include only the build output directory dir_opts = {"include": ["build"]} # include only ZIP files dir_opts = {"include": ["*.zip"]} # exclude Git metadata dir_opts = {"exclude": ["*.git"]} ``` ```typescript // exclude all Markdown files const dirOpts = { exclude: ["*.md*"] } // include only the build output directory const dirOpts = { include: ["build"] } // include only ZIP files const dirOpts = { include: ["*.zip"] } // exclude Git metadata const dirOpts = { exclude: ["*.git"] } ``` ```php // exclude all Markdown files $dirOpts = ['exclude' => ['*.md*']]; // include only the build output directory $dirOpts = ['include' => ['build']]; // include only ZIP files $dirOpts = ['include' => ['*.zip']]; // exclude Git metadata $dirOpts = ['exclude' => ['*.git']]; ``` ```java // exclude all Markdown files var dirOpts = new Container.WithDirectoryArguments() .withExclude(List.of("*.md*")); // include only the build output directory var dirOpts = new Container.WithDirectoryArguments() .withInclude(List.of("build")); // include only ZIP files var dirOpts = new Container.WithDirectoryArguments() .withInclude(List.of("*.zip")); // exclude Git metadata var dirOpts = new Container.WithDirectoryArguments() .withExclude(List.of("*.git")); ``` ### Mounts When working with directories and files, you can choose whether to copy or mount them in the containers created by your Dagger Function. The Dagger API provides the following methods: - `Container.withDirectory()` returns a container plus a directory written at the given path - `Container.withFile()` returns a container plus a file written at the given path - `Container.withMountedDirectory()` returns a container plus a directory mounted at the given path - `Container.withMountedFile()` returns a container plus a file mounted at the given path Mounts only take effect within your workflow invocation; they are not copied to, or included, in the final image. In addition, any changes to mounted files and/or directories will only be reflected in the target directory and not in the mount sources. :::tip Besides helping with the final image size, mounts are more performant and resource-efficient. The rule of thumb should be to always use mounts where possible. ::: ## Debugging ### Using logs Both Dagger Cloud and the Dagger TUI provide detailed information on the patterns Dagger uses to filter your directory uploads - look for the upload step in the TUI logs or Trace: ![Dagger TUI](/img/current_docs/reference/api/fs-filters-tui.png) ![Dagger Cloud Trace](/img/current_docs/reference/api/fs-filters-trace.png) ### Inspecting directory contents Another way to debug how directories are being filtered is to create a function that receives a `Directory` as input, and returns the same `Directory`: ```go func (m *MyModule) Debug( ctx context.Context, // +ignore=["*", "!analytics"] source *dagger.Directory, ) *dagger.Directory { return source } ```` ```python @function async def foo( self, source: Annotated[ dagger.Directory, Ignore(["*", "!analytics"]) ], ) -> dagger.Directory: return source ``` ```typescript @func() debug( @argument({ ignore: ["*", "!analytics"] }) source: Directory, ): Directory { return source } ``` ```php #[DaggerFunction] public function debug( #[Ignore('*'/, '!analytics')] Directory $source, ): Directory { return $source; } ``` ```java @Function public Directory debug(@Ignore({"*", "!analytics"}) Directory source) { return source; } ``` Calling the function will show you the directory’s digest and top level entries. The digest is content addressed, so it changes if there are changes in the contents of the directory. Looking at the entries field you may be able to spot an interloper: ` You can open the directory in an interactive terminal to inspect the filesystem: You can export the filtered directory to your host and check it with local tools: ## API reference --- # EngineCacheEntrySet URL: https://docs.dagger.io/reference/api/engine-cache-entry-set {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # EngineCacheEntry URL: https://docs.dagger.io/reference/api/engine-cache-entry {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # EngineCache URL: https://docs.dagger.io/reference/api/engine-cache {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Engine URL: https://docs.dagger.io/reference/api/engine {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # EnumTypeDef URL: https://docs.dagger.io/reference/api/enum-type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # EnumValueTypeDef URL: https://docs.dagger.io/reference/api/enum-value-type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # EnvFile URL: https://docs.dagger.io/reference/api/env-file {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # EnvVariable URL: https://docs.dagger.io/reference/api/env-variable {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # ErrorValue URL: https://docs.dagger.io/reference/api/error-value {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Error URL: https://docs.dagger.io/reference/api/error {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Exportable URL: https://docs.dagger.io/reference/api/exportable {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # FieldTypeDef URL: https://docs.dagger.io/reference/api/field-type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # File URL: https://docs.dagger.io/reference/api/file import FileType from "@daggerTypes/_file.mdx"; import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # FunctionArg URL: https://docs.dagger.io/reference/api/function-arg {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # FunctionCallArgValue URL: https://docs.dagger.io/reference/api/function-call-arg-value {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # FunctionCall URL: https://docs.dagger.io/reference/api/function-call {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Function URL: https://docs.dagger.io/reference/api/function {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # GeneratedCode URL: https://docs.dagger.io/reference/api/generated-code {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # GeneratorGroup URL: https://docs.dagger.io/reference/api/generator-group {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Generator URL: https://docs.dagger.io/reference/api/generator {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # GitBundleRef URL: https://docs.dagger.io/reference/api/git-bundle-ref {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # GitBundle URL: https://docs.dagger.io/reference/api/git-bundle {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # GitCommit URL: https://docs.dagger.io/reference/api/git-commit {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # GitRef URL: https://docs.dagger.io/reference/api/git-ref {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # GitRepository URL: https://docs.dagger.io/reference/api/git-repository import GitRepositoryType from "@daggerTypes/_git-repository.mdx"; import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # HealthcheckConfig URL: https://docs.dagger.io/reference/api/healthcheck-config {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Host URL: https://docs.dagger.io/reference/api/host {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # HTTPState URL: https://docs.dagger.io/reference/api/http-state {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # API URL: https://docs.dagger.io/reference/api/index The CLI, modules, checks, generators, and services all run on one GraphQL API served by the engine. In addition to basic types (string, boolean, integer, arrays...), the Dagger API also provides powerful types which you can use as both arguments and return values for Dagger Functions. Each type page combines hand-curated guidance, when available, with a complete schema-generated API reference. The sidebar highlights commonly used core API types; see [All types](all.mdx) for the complete generated list. To call the API from your own program, see [Client libraries](../client-libraries/index.mdx). The following table highlights commonly used types: | Type | Description | |------|-------------| | [`CacheVolume`](cache-volume.mdx) | A directory whose contents persist across runs | | [`Container`](container.mdx) | An OCI-compatible container | | [`CurrentModule`](current-module.mdx) | The current Dagger module and its context | | [`Engine`](engine.mdx) | The Dagger Engine configuration and state | | [`Directory`](directory.mdx) | A directory (local path or Git reference) | | [`EnvVariable`](env-variable.mdx) | An environment variable name and value | | [`File`](file.mdx) | A file | | [`GitRepository`](git-repository.mdx) | A Git repository | | [`GitRef`](git-ref.mdx) | A Git reference (tag, branch, or commit) | | [`Host`](host.mdx) | The Dagger host environment | | [`LLM`](llm.mdx) | A Large Language Model (LLM) | | [`Module`](module.mdx) | A Dagger module | | [`Port`](port.mdx) | A port exposed by a container | | [`Secret`](secret.mdx) | A secret credential like a password, access token or key) | | [`Service`](service.mdx) | A content-addressed service providing TCP connectivity | | [`Socket`](socket.mdx) | A Unix or TCP/IP socket that can be mounted into a container | | [`Terminal`](terminal.mdx) | An interactive terminal session | :::tip In addition to the default Dagger types, you can create and add your own custom types to Dagger. These custom types can be used in Dagger modules and can be composed with other types to create complex workflows. Learn more about [creating custom types and developing Dagger modules](../sdks/index.mdx). ::: --- # InputTypeDef URL: https://docs.dagger.io/reference/api/input-type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # InterfaceTypeDef URL: https://docs.dagger.io/reference/api/interface-type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # JSONValue URL: https://docs.dagger.io/reference/api/json-value {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Label URL: https://docs.dagger.io/reference/api/label {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # ListTypeDef URL: https://docs.dagger.io/reference/api/list-type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # LLMContentBlock URL: https://docs.dagger.io/reference/api/llm-content-block {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # LLMMessageOrigin URL: https://docs.dagger.io/reference/api/llm-message-origin {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # LLMMessage URL: https://docs.dagger.io/reference/api/llm-message {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # LLMSkill URL: https://docs.dagger.io/reference/api/llm-skill {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # LLMTokenUsage URL: https://docs.dagger.io/reference/api/llm-token-usage {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # LLM URL: https://docs.dagger.io/reference/api/llm import LlmType from "@daggerTypes/_llm.mdx"; import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # ModuleConfigClient URL: https://docs.dagger.io/reference/api/module-config-client {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # ModuleSource URL: https://docs.dagger.io/reference/api/module-source {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Module URL: https://docs.dagger.io/reference/api/module {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Node URL: https://docs.dagger.io/reference/api/node {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # ObjectTypeDef URL: https://docs.dagger.io/reference/api/object-type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Port URL: https://docs.dagger.io/reference/api/port {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Query URL: https://docs.dagger.io/reference/api/query {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # RemoteGitMirror URL: https://docs.dagger.io/reference/api/remote-git-mirror {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # ScalarTypeDef URL: https://docs.dagger.io/reference/api/scalar-type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Schema URL: https://docs.dagger.io/reference/api/schema {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # SDKConfig URL: https://docs.dagger.io/reference/api/sdk-config {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # SearchResult URL: https://docs.dagger.io/reference/api/search-result {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # SearchSubmatch URL: https://docs.dagger.io/reference/api/search-submatch {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Secret URL: https://docs.dagger.io/reference/api/secret import SecretType from "@daggerTypes/_secret.mdx"; import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Service URL: https://docs.dagger.io/reference/api/service import ServiceType from "@daggerTypes/_service.mdx"; import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Socket URL: https://docs.dagger.io/reference/api/socket {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # SourceMap URL: https://docs.dagger.io/reference/api/source-map {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Stat URL: https://docs.dagger.io/reference/api/stat {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Syncer URL: https://docs.dagger.io/reference/api/syncer {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # TerminalGroup URL: https://docs.dagger.io/reference/api/terminal-group {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # TerminalTarget URL: https://docs.dagger.io/reference/api/terminal-target {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Terminal URL: https://docs.dagger.io/reference/api/terminal {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # TypeDef URL: https://docs.dagger.io/reference/api/type-def {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # UpGroup URL: https://docs.dagger.io/reference/api/up-group {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Up URL: https://docs.dagger.io/reference/api/up {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Volume URL: https://docs.dagger.io/reference/api/volume {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # WorkspaceGit URL: https://docs.dagger.io/reference/api/workspace-git {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # WorkspaceMigrationStep URL: https://docs.dagger.io/reference/api/workspace-migration-step {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # WorkspaceMigration URL: https://docs.dagger.io/reference/api/workspace-migration {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # WorkspaceModuleSetting URL: https://docs.dagger.io/reference/api/workspace-module-setting {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # WorkspaceModule URL: https://docs.dagger.io/reference/api/workspace-module {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # WorkspaceSDK URL: https://docs.dagger.io/reference/api/workspace-sdk {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # Workspace URL: https://docs.dagger.io/reference/api/workspace {/* Generated by plugins/dagger-api-reference/generate-stubs.js — do not edit. Content comes from docs-graphql/schema.graphqls; edit the schema. */} import ApiType from "@site/src/components/api/ApiType"; ## API reference --- # CLI reference URL: https://docs.dagger.io/reference/cli/index ## dagger A tool to run composable workflows in containers ``` dagger [options] [subcommand | file...] ``` ### Options ``` -c, --command string Execute a Dagger script -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger agent](#dagger-agent) - Compose your installed agent modules and drop into an interactive prompt. * [dagger api](#dagger-api) - Interact with the Dagger API (advanced) * [dagger check](#dagger-check) - Verify your project — tests, linters, type checks, security scans, etc. * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud * [dagger generate](#dagger-generate) - Generate derived files for your project — code, SDKs, types, docs, etc. * [dagger init](#dagger-init) - Initialize a workspace and show the next commands * [dagger install](#dagger-install) - Install a module into your workspace * [dagger llm](#dagger-llm) - Manage LLM configuration * [dagger module](#dagger-module) - Install, use, and develop Dagger modules * [dagger sdk](#dagger-sdk) - Inspect and configure SDKs * [dagger settings](#dagger-settings) - Get, set, or unset module settings * [dagger shell](#dagger-shell) - Open a terminal for a container or directory in your project * [dagger uninstall](#dagger-uninstall) - Uninstall a module from your workspace * [dagger up](#dagger-up) - Run your project's services for local development — databases, APIs, dev servers, etc. * [dagger update](#dagger-update) - Update installed module versions and lockfile state * [dagger version](#dagger-version) - Print dagger version * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger agent Compose your installed agent modules and drop into an interactive prompt. ### Synopsis Compose your installed agent modules — their tools and system prompts — onto a base LLM, and drop into the interactive prompt with them all live. Each installed module that exposes an @agent function contributes its toolset and system prompt. With no arguments, every installed agent is composed, in alphabetical order. Name one or more agents to compose only those. With --trace, a past session is restored from the trace it published to Dagger Cloud: every agent it ran comes back under the same identity, with the conversation and lifecycle state it had, and the old session's whole progress view is scrolled back beside your prompt. Two caveats. Restoring a trace whose agents are still running FORKS them — the restored instances are new runtimes in this session, not a hand-off of the live ones. And messages that were enqueued but never consumed are not in the trace at all, so they are not restored; anything a turn actually consumed is part of its conversation and is. Examples: dagger agent # Compose all installed agents and start the prompt dagger agent -l # List all available agents dagger agent editor dagger-go # Compose only the 'editor' and 'dagger-go' agents dagger agent -r # Resume a saved session (interactive picker) dagger agent -r=<session> # Resume a specific saved session dagger agent --trace <id> # Restore a past session from its Dagger Cloud trace ``` dagger agent [options] [name...] ``` ### Options ``` --agent string With --trace, focus this restored agent (runtime handle or name) instead of the top-level one -l, --list List available agents --partial With --trace, restore what the trace carries enough to restore instead of failing on the first agent it does not -r, --resume session[=picker] Resume a saved session (interactive picker if no id given) --trace string Restore a past session from its Dagger Cloud trace: its agents, their conversations, and its scrollback ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger api Interact with the Dagger API (advanced) ### Synopsis Every Dagger command — check, up, generate, even install — ultimately runs against a GraphQL API served by the Dagger engine, combining Dagger's core types with schema extensions loaded from modules. The "api" group surfaces direct access for scripting and advanced automation. Most users will never type these commands. See https://docs.dagger.io/api for the full overview. ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers * [dagger api call](#dagger-api-call) - Call one or more functions, interconnected into a pipeline * [dagger api functions](#dagger-api-functions) - List available functions * [dagger api query](#dagger-api-query) - Send API queries to a dagger engine * [dagger api with-session](#dagger-api-with-session) - Run a command with a connected Dagger API session (DAGGER_SESSION_PORT/TOKEN injected) ## dagger api call Call one or more functions, interconnected into a pipeline ``` dagger api call [options] [function]... ``` ### Options ``` --allow-llm strings List of URLs of remote modules allowed to access LLM APIs, or 'all' to bypass restrictions for the entire session --eager-runtime load module runtime eagerly -j, --json Present result as JSON -m, --load-module string Use a one-off module (local path or git ref) -M, --no-load-module Don't load any module for this command -o, --output string Save the result to a local file or directory ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger api](#dagger-api) - Interact with the Dagger API (advanced) ## dagger api functions List available functions ### Synopsis List available functions in a module. This is similar to `dagger api call --help`, but only focused on showing the available functions. Examples: dagger api functions # List top-level functions in current workspace dagger api functions container # List functions on container dagger -m core api functions # List core functions dagger -W github.com/acme/ws api functions # List top-level functions in explicit workspace dagger -W github.com/acme/ws api functions container from ``` dagger api functions [options] [function]... ``` ### Options ``` --allow-llm strings List of URLs of remote modules allowed to access LLM APIs, or 'all' to bypass restrictions for the entire session --eager-runtime load module runtime eagerly -m, --load-module string Use a one-off module (local path or git ref) ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger api](#dagger-api) - Interact with the Dagger API (advanced) ## dagger api query Send API queries to a dagger engine ### Synopsis Send API queries to a dagger engine. When no document file is provided, reads query from standard input. Can optionally provide the GraphQL operation name if there are multiple queries in the document. ``` dagger api query [options] [operation] ``` ### Examples ``` dagger api query <... ``` ### Examples ``` dagger api with-session go run main.go dagger api with-session node index.mjs dagger api with-session python main.py ``` ### Options ``` --cleanup-timeout duration max duration to wait between SIGTERM and SIGKILL on interrupt (default 10s) --focus Only show output for focused commands. ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger api](#dagger-api) - Interact with the Dagger API (advanced) ## dagger check Verify your project — tests, linters, type checks, security scans, etc. ### Synopsis Verify your project — tests, linters, type checks, security scans, etc. Examples: dagger check # Run all checks dagger check -l # List all available checks dagger check go:lint # Run the go:lint check and any subchecks dagger check --skip '**e2e' # Run all checks except those matching '**e2e' dagger -W github.com/acme/ws check go:lint # Run check(s) against explicit workspace ``` dagger check [options] [pattern...] ``` ### Options ``` --allow-llm strings List of URLs of remote modules allowed to access LLM APIs, or 'all' to bypass restrictions for the entire session --eager-runtime load module runtime eagerly --failfast Cancel remaining checks on first failure --generate Only run generate-as-checks, skip annotated check functions -l, --list List available checks -m, --load-module string Use a one-off module (local path or git ref) --no-generate Only run annotated check functions, skip generate-as-checks --skip stringArray Skip checks matching the specified patterns ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger cloud Manage Dagger Cloud ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers * [dagger cloud billing](#dagger-cloud-billing) - Manage Dagger Cloud billing * [dagger cloud checks](#dagger-cloud-checks) - Manage Cloud-side automated checks for this workspace * [dagger cloud integration](#dagger-cloud-integration) - Manage Dagger Cloud integration providers * [dagger cloud login](#dagger-cloud-login) - Log in to Dagger Cloud * [dagger cloud logout](#dagger-cloud-logout) - Log out from Dagger Cloud * [dagger cloud logs](#dagger-cloud-logs) - Print the full logs for a Dagger Cloud trace, or a check/test/span within it * [dagger cloud org](#dagger-cloud-org) - Manage Dagger Cloud organizations * [dagger cloud rerun](#dagger-cloud-rerun) - Re-run checks on Dagger Cloud for the current commit * [dagger cloud signup](#dagger-cloud-signup) - Create or select a Dagger Cloud account and organization ## dagger cloud billing Manage Dagger Cloud billing ``` dagger cloud billing ``` ### Options ``` --json Print JSON output ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud * [dagger cloud billing manage](#dagger-cloud-billing-manage) - Open the billing portal for a Dagger Cloud org * [dagger cloud billing plans](#dagger-cloud-billing-plans) - List Dagger Cloud plans available at signup ## dagger cloud billing manage Open the billing portal for a Dagger Cloud org ``` dagger cloud billing manage [org] ``` ### Options ``` --open Open the billing portal in a browser ``` ### Options inherited from parent commands ``` --json Print JSON output -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud billing](#dagger-cloud-billing) - Manage Dagger Cloud billing ## dagger cloud billing plans List Dagger Cloud plans available at signup ``` dagger cloud billing plans ``` ### Options inherited from parent commands ``` --json Print JSON output -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud billing](#dagger-cloud-billing) - Manage Dagger Cloud billing ## dagger cloud checks Manage Cloud-side automated checks for this workspace ``` dagger cloud checks ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud * [dagger cloud checks list](#dagger-cloud-checks-list) - List Cloud-side checks for this workspace * [dagger cloud checks off](#dagger-cloud-checks-off) - Disable a Cloud-side check (by name; defaults to the workspace remote's default check) * [dagger cloud checks on](#dagger-cloud-checks-on) - Enable a Cloud-side check (by name; defaults to the workspace remote's default check) * [dagger cloud checks status](#dagger-cloud-checks-status) - Show the status of a Cloud-side check (by name; defaults to the workspace remote's default check) ## dagger cloud checks list List Cloud-side checks for this workspace ``` dagger cloud checks list [version] ``` ### Options ``` --failed Only list failed checks ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger cloud checks](#dagger-cloud-checks) - Manage Cloud-side automated checks for this workspace ## dagger cloud checks off Disable a Cloud-side check (by name; defaults to the workspace remote's default check) ``` dagger cloud checks off [name] ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger cloud checks](#dagger-cloud-checks) - Manage Cloud-side automated checks for this workspace ## dagger cloud checks on Enable a Cloud-side check (by name; defaults to the workspace remote's default check) ``` dagger cloud checks on [name] ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger cloud checks](#dagger-cloud-checks) - Manage Cloud-side automated checks for this workspace ## dagger cloud checks status Show the status of a Cloud-side check (by name; defaults to the workspace remote's default check) ``` dagger cloud checks status [name] ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger cloud checks](#dagger-cloud-checks) - Manage Cloud-side automated checks for this workspace ## dagger cloud integration Manage Dagger Cloud integration providers ``` dagger cloud integration ``` ### Options ``` --json Print JSON output ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud * [dagger cloud integration create](#dagger-cloud-integration-create) - Create a new integration of the given provider type * [dagger cloud integration list](#dagger-cloud-integration-list) - List configured integrations (optionally filtered by provider type) * [dagger cloud integration rm](#dagger-cloud-integration-rm) - Remove a configured integration ## dagger cloud integration create Create a new integration of the given provider type ``` dagger cloud integration create ``` ### Examples ``` dagger cloud integration create github ``` ### Options ``` --open Open the setup URL in a browser ``` ### Options inherited from parent commands ``` --json Print JSON output -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud integration](#dagger-cloud-integration) - Manage Dagger Cloud integration providers ## dagger cloud integration list List configured integrations (optionally filtered by provider type) ``` dagger cloud integration list [type] ``` ### Options inherited from parent commands ``` --json Print JSON output -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud integration](#dagger-cloud-integration) - Manage Dagger Cloud integration providers ## dagger cloud integration rm Remove a configured integration ``` dagger cloud integration rm ``` ### Options inherited from parent commands ``` --json Print JSON output -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud integration](#dagger-cloud-integration) - Manage Dagger Cloud integration providers ## dagger cloud login Log in to Dagger Cloud ``` dagger cloud login [options] [org] ``` ### Options ``` --switch-account Choose a different Dagger Cloud account ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud ## dagger cloud logout Log out from Dagger Cloud ``` dagger cloud logout ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud ## dagger cloud logs Print the full logs for a Dagger Cloud trace, or a check/test/span within it ### Synopsis Stream the full logs for a trace. Use this as a follow-up to 'dagger trace' to inspect a failure in detail, addressing it by name rather than an opaque span ID. Redirect to a file to grep large logs in a controlled way: dagger cloud logs <trace-id> --check build:lint -o span.log grep -i error span.log With no --span/--check/--test, the whole trace's logs are streamed. --check and --test roll up their subtree; --span is just that span (add --descendants to roll up its subtree too). ``` dagger cloud logs [--span | --check | --test ] ``` ### Options ``` --check string Read a check's logs, by name (rolls up its subtree) --descendants With --span, roll up the span's subtree logs too -o, --output string Write logs to a file instead of stdout --span string Read just this span's logs, by span ID --test string Read a test's logs, by name (rolls up its subtree) --timeout duration Max time to spend streaming logs (default 2m0s) ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud ## dagger cloud org Manage Dagger Cloud organizations ``` dagger cloud org [flags] ``` ### Options ``` --json Print JSON output ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud * [dagger cloud org info](#dagger-cloud-org-info) - Show Dagger Cloud organization status * [dagger cloud org list](#dagger-cloud-org-list) - List Dagger Cloud organizations * [dagger cloud org use](#dagger-cloud-org-use) - Select the current Dagger Cloud organization ## dagger cloud org info Show Dagger Cloud organization status ``` dagger cloud org info [org] [flags] ``` ### Options inherited from parent commands ``` --json Print JSON output -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud org](#dagger-cloud-org) - Manage Dagger Cloud organizations ## dagger cloud org list List Dagger Cloud organizations ``` dagger cloud org list [flags] ``` ### Options inherited from parent commands ``` --json Print JSON output -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud org](#dagger-cloud-org) - Manage Dagger Cloud organizations ## dagger cloud org use Select the current Dagger Cloud organization ``` dagger cloud org use [flags] ``` ### Options inherited from parent commands ``` --json Print JSON output -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud org](#dagger-cloud-org) - Manage Dagger Cloud organizations ## dagger cloud rerun Re-run checks on Dagger Cloud for the current commit ### Synopsis Re-run checks on Dagger Cloud, against the commit CI already ran on. By default this targets the commit at the current HEAD (matched by SHA, falling back to the branch or PR it belongs to) and re-runs the checks that failed. Pass --check to pick specific checks by name, --all to re-run everything, or --commit/--pr to target a different commit. Only outermost checks can be re-run; sub-checks run as part of their parent check, so name the parent (e.g. "ci:bootstrap", not "ci:bootstrap:lint"). This re-runs a check that already exists in Cloud for the commit. If CI hasn't run on the commit yet there's nothing to re-run -- use 'dagger check' to run checks locally against your working tree. ``` dagger cloud rerun [--check NAME ...] [--failed | --all] ``` ### Options ``` --all Re-run every check, including ones that passed --check stringArray Re-run a specific check by name (repeatable; outermost checks only) --clean-slate Re-run without reusing cache (experimental; requires an org feature) --commit string Target a specific commit SHA instead of the current HEAD --dry-run Show which checks would be re-run without triggering anything --failed Re-run the failed checks (the default when no --check is given) --json Print JSON output --pr string Target a specific pull request number ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud ## dagger cloud signup Create or select a Dagger Cloud account and organization ### Synopsis Create or select a Dagger Cloud account and organization. Use the same account and organization flow as dagger cloud login. If human action is required, non-interactive mode returns instructions without opening a browser or waiting for input. ``` dagger cloud signup [options] [org] ``` ### Options ``` --switch-account Choose a different Dagger Cloud account ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger cloud](#dagger-cloud) - Manage Dagger Cloud ## dagger engine How to select the engine that runs your workflows ### Synopsis Dagger runs your workflows on an engine. Use --engine to select one. VALUES Dagger Cloud: cloud Start from an OCI image: image://IMAGE image+docker://IMAGE image+apple://IMAGE image+podman://IMAGE image+finch://IMAGE image+nerdctl://IMAGE Use a running engine container: container://NAME container+docker://NAME container+apple://NAME container+podman://NAME container+finch://NAME container+nerdctl://NAME Connect directly: tcp://HOST:PORT (no authentication) tls://HOST[:PORT] ssh://[USER@]HOST[:PORT] kube-pod://POD unix://PATH Legacy Docker schemes: docker-image://IMAGE docker-container://NAME PRIORITY The CLI uses the first of these that is set: 1. --engine 2. --cloud (deprecated; the same as --engine=cloud) 3. DAGGER_ENGINE (takes the same values as --engine) 4. DAGGER_CLOUD_ENGINE (deprecated; any value selects Dagger Cloud) 5. _EXPERIMENTAL_DAGGER_RUNNER_HOST (deprecated) 6. The engine that this CLI version ships with The two deprecated variables still work as a fallback, in the same way as the deprecated --cloud flag. --engine and DAGGER_ENGINE replace them: --engine=cloud instead of DAGGER_CLOUD_ENGINE --engine=URI instead of _EXPERIMENTAL_DAGGER_RUNNER_HOST EXAMPLES Run on Dagger Cloud: dagger call --engine=cloud build Run on an engine container that is already running: dagger call --engine=container://dagger-engine build Run on a remote host over SSH: dagger call --engine=ssh://user@host build Select an engine for every command in a shell session: export DAGGER_ENGINE=cloud ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger generate Generate derived files for your project — code, SDKs, types, docs, etc. ### Synopsis Generate derived files for your project — code, SDKs, types, docs, etc. Examples: dagger generate # Generate all assets dagger generate -l # List all available generators dagger generate --no-apply # Show generated changes without applying them dagger generate go:bin # Generate by selecting the generator function dagger -W github.com/acme/ws generate go:bin # Generate against explicit workspace ``` dagger generate [options] [pattern...] ``` ### Options ``` -l, --list List available generators --no-apply Compute and show a summary of generated changes without applying them --require-load Fail if any workspace module cannot be loaded (default: report as a warning and generate the rest) ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger init Initialize a workspace and show the next commands ### Synopsis Initialize a workspace and show the next commands. Use an existing dagger.toml when present. If a legacy dagger.json has workspace settings, stop and direct the user to dagger workspace migrate. Otherwise, create an empty dagger.toml. Existing module files remain unchanged. Run this command in a local Git repository. Run this command again to inspect the current initialization state. ``` dagger init ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger install Install a module into your workspace ### Synopsis Install a module from a local path or Git source into the current workspace. To change an installed version, use dagger mod update. If no workspace config is selected, this creates one at the workspace root first. Use --here to create the workspace config at the workspace cwd instead. ``` dagger install [options] SOURCE ``` ### Examples ``` dagger module install github.com/shykes/daggerverse/hello@v0.3.0 ``` ### Options ``` --here Write workspace config at the selected workspace cwd -n, --name string Name to use for the module in the workspace. Defaults to the name of the module being installed. ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger llm Manage LLM configuration ### Synopsis Manage LLM provider configuration, API keys, and default models. ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers * [dagger llm add-key](#dagger-llm-add-key) - Add or update API key for a provider * [dagger llm config](#dagger-llm-config) - Display current LLM configuration * [dagger llm remove-key](#dagger-llm-remove-key) - Remove API key for a provider * [dagger llm reset](#dagger-llm-reset) - Reset LLM configuration (removes all stored credentials) * [dagger llm set-default](#dagger-llm-set-default) - Set default provider and optionally model * [dagger llm setup](#dagger-llm-setup) - Configure LLM authentication interactively * [dagger llm show-config](#dagger-llm-show-config) - Show raw LLM configuration (JSON) ## dagger llm add-key Add or update API key for a provider ### Synopsis Add or update API key for a provider. Supported providers: - openrouter: Unified access to 100+ models (https://openrouter.ai/keys) - anthropic: Claude models (https://console.anthropic.com/settings/keys) - openai: GPT models (https://platform.openai.com/api-keys) - google: Gemini models (https://aistudio.google.com/app/apikey) ``` dagger llm add-key ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger llm](#dagger-llm) - Manage LLM configuration ## dagger llm config Display current LLM configuration ``` dagger llm config ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger llm](#dagger-llm) - Manage LLM configuration ## dagger llm remove-key Remove API key for a provider ``` dagger llm remove-key ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger llm](#dagger-llm) - Manage LLM configuration ## dagger llm reset Reset LLM configuration (removes all stored credentials) ``` dagger llm reset ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger llm](#dagger-llm) - Manage LLM configuration ## dagger llm set-default Set default provider and optionally model ``` dagger llm set-default [model] ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger llm](#dagger-llm) - Manage LLM configuration ## dagger llm setup Configure LLM authentication interactively ``` dagger llm setup ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger llm](#dagger-llm) - Manage LLM configuration ## dagger llm show-config Show raw LLM configuration (JSON) ``` dagger llm show-config ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger llm](#dagger-llm) - Manage LLM configuration ## dagger module Install, use, and develop Dagger modules ``` dagger module ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers * [dagger module client](#dagger-module-client) - Manage generated clients for modules * [dagger module init](#dagger-module-init) - Initialize a new module for development with an SDK * [dagger module install](#dagger-module-install) - Install a module into your workspace * [dagger module list](#dagger-module-list) - List installed modules * [dagger module migrate](#dagger-module-migrate) - Migrate one local module in place * [dagger module recommend](#dagger-module-recommend) - Find recommended modules and select which modules to install * [dagger module search](#dagger-module-search) - Search for modules you can install * [dagger module settings](#dagger-module-settings) - Get, set, or unset module settings * [dagger module uninstall](#dagger-module-uninstall) - Uninstall a module from your workspace * [dagger module update](#dagger-module-update) - Update installed module versions and lockfile state * [dagger module version](#dagger-module-version) - Print the version request for an installed module ## dagger module client Manage generated clients for modules ``` dagger module client [flags] ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules * [dagger module client add](#dagger-module-client-add) - Add and generate a module client * [dagger module client list](#dagger-module-client-list) - List generated module clients * [dagger module client rm](#dagger-module-client-rm) - Remove a module client * [dagger module client scope](#dagger-module-client-scope) - Print the current client-generation scope * [dagger module client update](#dagger-module-client-update) - Update module clients ## dagger module client add Add and generate a module client ### Synopsis Add a module client to one SDK scope and generate that scope. Use an explicit local path such as ./api or ../api, or a module address. Installed module names are not supported. With no --sdk, select the deepest scope found across installed SDKs. If several SDKs have that scope, use --sdk to select one. ``` dagger module client add [--sdk=SDK] ``` ### Options ``` --sdk SDK Select an installed SDK (default: deepest scope) ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module client](#dagger-module-client) - Manage generated clients for modules ## dagger module client list List generated module clients ### Synopsis List recorded clients in scopes that contain the current directory. Use --all to list clients in every scope. SCOPE is relative to the workspace root. To remove a row, run 'dagger module client rm TARGET --sdk=SDK' from SCOPE. ``` dagger module client list [flags] ``` ### Options ``` --all List clients in all scopes --sdk string Filter by SDK module ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module client](#dagger-module-client) - Manage generated clients for modules ## dagger module client rm Remove a module client ### Synopsis Remove a recorded module client and regenerate its SDK scope. Use the exact TARGET from 'dagger module client list'. Select the deepest matching scope. If several SDKs have that scope, use --sdk to select one. If invalid targets remain, save the removal and skip generation until they are corrected or removed. ``` dagger module client rm [--sdk=SDK] ``` ### Options ``` --sdk SDK Select an installed SDK (default: deepest matching scope) ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module client](#dagger-module-client) - Manage generated clients for modules ## dagger module client scope Print the current client-generation scope ``` dagger module client scope [flags] ``` ### Options ``` --sdk string SDK module to query ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module client](#dagger-module-client) - Manage generated clients for modules ## dagger module client update Update module clients ### Synopsis Update the recorded module clients and regenerate their SDK scopes. With no argument, updates every client target in the current scope. Only the lock entries that the selected targets reach are rewritten. ``` dagger module client update [module...] [flags] ``` ### Examples ``` dagger module client update ``` ### Options ``` --all Update clients in all scopes --sdk string Filter by SDK module ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module client](#dagger-module-client) - Manage generated clients for modules ## dagger module init Initialize a new module for development with an SDK ### Synopsis Initialize a new module for development with an SDK ``` dagger module init SDK [flags] ``` ### Options ``` --entrypoint Install and select the module as entrypoint (default: select when --path and --name are omitted) --install Install the module (default: install when --path is omitted) -n, --name string Module name (inferred when omitted) --no-apply Show generated changes without applying them --path string Module path (default: .dagger/modules/ beside dagger.toml) ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module install Install a module into your workspace ### Synopsis Install a module from a local path or Git source into the current workspace. To change an installed version, use dagger mod update. If no workspace config is selected, this creates one at the workspace root first. Use --here to create the workspace config at the workspace cwd instead. ``` dagger module install [options] SOURCE [flags] ``` ### Examples ``` dagger module install github.com/shykes/daggerverse/hello@v0.3.0 ``` ### Options ``` --here Write workspace config at the selected workspace cwd -n, --name string Name to use for the module in the workspace. Defaults to the name of the module being installed. ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module list List installed modules ``` dagger module list [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module migrate Migrate one local module in place ### Synopsis Migrate one local dagger.json to dagger-module.toml in place. PATH defaults to the workspace current directory. Relative paths start there; absolute paths start at the workspace root. A dagger.toml is not required. If one exists, migration also registers the module's SDK scope. Otherwise, only the requested module is converted; no workspace config is created. Configurations with workspace fields require workspace migration. Review module and SDK configuration changes together. Use --auto-apply to apply without a prompt, or --no-apply to preview without changing files. ``` dagger module migrate [PATH] [flags] ``` ### Options ``` --no-apply Preview migration without changing files ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module recommend Find recommended modules and select which modules to install ### Synopsis Find modules for the current workspace and select which modules to install. Already installed modules are skipped. Use --auto-apply to install all recommended modules without a prompt. Without --auto-apply, print recommendations and install commands in non-interactive mode. ``` dagger module recommend [flags] ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module search Search for modules you can install ### Synopsis Search the module registry by name or description. With no query, lists all known modules and SDK modules. ``` dagger module search [query] [flags] ``` ### Examples ``` dagger module search wolfi ``` ### Options ``` --sdk Only show modules that provide SDK capabilities ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module settings Get, set, or unset module settings ``` dagger module settings [module] [key] [value...] [flags] ``` ### Options ``` -g, --global Store the setting in user-level config instead of the repository, keyed by the workspace's git remote --here Write workspace config at the selected workspace cwd -u, --unset Remove the setting from workspace config ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module uninstall Uninstall a module from your workspace ### Synopsis Uninstall a module from the current workspace, removing it from dagger.toml. Match an installed name first, then a source without a version. The source must match exactly one installation. Version selectors are not accepted. ``` dagger module uninstall [options] NAME|SOURCE [flags] ``` ### Examples ``` dagger module uninstall hello ``` ### Options ``` --here Write workspace config at the selected workspace cwd ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module update Update installed module versions and lockfile state ### Synopsis Update an installed module by name or source. Use --version VERSION or append @VERSION to set a new version request. Match an installed name first. Source matching ignores the version and must select exactly one installation. Without a new version, refresh the existing request. With no arguments, this refreshes all installed modules. It does not refresh client targets or runtime targets. If a client scope targets an updated module, the command regenerates that scope. Use dagger workspace update to refresh all entries in dagger.lock. ``` dagger module update [NAME|SOURCE...] [flags] ``` ### Options ``` --version string New version request for one installed module ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger module version Print the version request for an installed module ### Synopsis Print the version request from dagger.toml, such as v1 or main. Match an installed name first, then a source without a version. The source must match exactly one installation. Source-match details go to stderr. Local modules and sources without an explicit version request return an error. ``` dagger module version NAME|SOURCE [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger module](#dagger-module) - Install, use, and develop Dagger modules ## dagger sdk Inspect and configure SDKs ``` dagger sdk ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers * [dagger sdk list](#dagger-sdk-list) - List known SDKs * [dagger sdk scope](#dagger-sdk-scope) - Inspect and configure SDK scopes ## dagger sdk list List known SDKs ``` dagger sdk list [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger sdk](#dagger-sdk) - Inspect and configure SDKs ## dagger sdk scope Inspect and configure SDK scopes ### Synopsis Inspect and configure SDK scopes. Field edits update dagger.toml only. They do not generate files. ``` dagger sdk scope [flags] ``` ### Options ``` --path string Select a scope by path instead of the workspace CWD ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger sdk](#dagger-sdk) - Inspect and configure SDKs * [dagger sdk scope is-module](#dagger-sdk-scope-is-module) - Get or set whether an SDK scope contains a module * [dagger sdk scope list](#dagger-sdk-scope-list) - List SDK scopes * [dagger sdk scope name](#dagger-sdk-scope-name) - Get or set an SDK scope name * [dagger sdk scope sdk](#dagger-sdk-scope-sdk) - Get or set the SDK that owns a scope ## dagger sdk scope is-module Get or set whether an SDK scope contains a module ``` dagger sdk scope is-module [BOOL] [flags] ``` ### Options ``` -u, --unset Remove the value ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) --path string Select a scope by path instead of the workspace CWD -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger sdk scope](#dagger-sdk-scope) - Inspect and configure SDK scopes ## dagger sdk scope list List SDK scopes ``` dagger sdk scope list [flags] ``` ### Options ``` --is-module Filter by whether the scope contains a module --name string Filter by scope name --sdk string Filter by SDK name ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) --path string Select a scope by path instead of the workspace CWD -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger sdk scope](#dagger-sdk-scope) - Inspect and configure SDK scopes ## dagger sdk scope name Get or set an SDK scope name ``` dagger sdk scope name [NAME] [flags] ``` ### Options ``` -u, --unset Remove the value ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) --path string Select a scope by path instead of the workspace CWD -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger sdk scope](#dagger-sdk-scope) - Inspect and configure SDK scopes ## dagger sdk scope sdk Get or set the SDK that owns a scope ``` dagger sdk scope sdk [SDK] [flags] ``` ### Options ``` -u, --unset Remove the value ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) --path string Select a scope by path instead of the workspace CWD -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger sdk scope](#dagger-sdk-scope) - Inspect and configure SDK scopes ## dagger settings Get, set, or unset module settings ``` dagger settings [module] [key] [value...] ``` ### Options ``` -g, --global Store the setting in user-level config instead of the repository, keyed by the workspace's git remote --here Write workspace config at the selected workspace cwd -u, --unset Remove the setting from workspace config ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger shell Open a terminal for a container or directory in your project ### Synopsis Open a terminal for a container or directory in your project. Examples: dagger shell -l # List all available shells dagger shell go:dev # Open the go:dev shell dagger sh go:dev # Use the short command alias ``` dagger shell [options] [pattern] ``` ### Options ``` -l, --list List available shells ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger uninstall Uninstall a module from your workspace ### Synopsis Uninstall a module from the current workspace, removing it from dagger.toml. Match an installed name first, then a source without a version. The source must match exactly one installation. Version selectors are not accepted. ``` dagger uninstall [options] NAME|SOURCE ``` ### Examples ``` dagger module uninstall hello ``` ### Options ``` --here Write workspace config at the selected workspace cwd ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger up Run your project's services for local development — databases, APIs, dev servers, etc. ### Synopsis Run your project's services for local development — databases, APIs, dev servers, etc. Examples: dagger up # Start all services dagger up -l # List all available services dagger up web # Start only the 'web' service ``` dagger up [options] [pattern...] ``` ### Options ``` -l, --list List available services ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger update Update installed module versions and lockfile state ### Synopsis Update an installed module by name or source. Use --version VERSION or append @VERSION to set a new version request. Match an installed name first. Source matching ignores the version and must select exactly one installation. Without a new version, refresh the existing request. With no arguments, this refreshes all installed modules. It does not refresh client targets or runtime targets. If a client scope targets an updated module, the command regenerates that scope. Use dagger workspace update to refresh all entries in dagger.lock. ``` dagger update [NAME|SOURCE...] ``` ### Options ``` --version string New version request for one installed module ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger version Print dagger version ``` dagger version ``` ### Options ``` --check Check for updates -q, --quiet Print only the canonical build identifier ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers ## dagger workspace Inspect or configure your workspace (cwd, remotes, config, etc.) ### Synopsis Inspect or configure your workspace. A workspace is a project configured to use Dagger — a directory holding a dagger.toml that records installed modules, environment overlays, and settings. Most commands (install, check, generate, up, settings, ...) operate on the workspace reachable from the current directory. The -W flag selects a different workspace (local path or git ref); dagger.toml is the source of truth. ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger](#dagger) - A tool to run composable workflows in containers * [dagger workspace activity](#dagger-workspace-activity) - Show recent activity (runs, traces, etc.) for this workspace * [dagger workspace cat](#dagger-workspace-cat) - Print files from the selected workspace * [dagger workspace config](#dagger-workspace-config) - Get or set workspace configuration * [dagger workspace config-file](#dagger-workspace-config-file) - Print the selected workspace config file * [dagger workspace cwd](#dagger-workspace-cwd) - Print the workspace cwd * [dagger workspace entrypoint](#dagger-workspace-entrypoint) - Get or set the workspace entrypoint * [dagger workspace exec](#dagger-workspace-exec) - Execute a command in a container with the selected workspace mounted * [dagger workspace export](#dagger-workspace-export) - Export a file or directory from the selected workspace * [dagger workspace find](#dagger-workspace-find) - Find paths in the selected workspace * [dagger workspace git](#dagger-workspace-git) - Inspect Git metadata for the selected workspace * [dagger workspace grep](#dagger-workspace-grep) - Search file contents in the selected workspace * [dagger workspace ls](#dagger-workspace-ls) - List directories or files in the selected workspace * [dagger workspace migrate](#dagger-workspace-migrate) - Migrate the workspace and its installed local modules * [dagger workspace remote](#dagger-workspace-remote) - Print the selectable remote address for the current workspace * [dagger workspace remotes](#dagger-workspace-remotes) - List selectable remote workspace addresses * [dagger workspace root](#dagger-workspace-root) - Print the workspace root * [dagger workspace update](#dagger-workspace-update) - Refresh all workspace lockfile state ## dagger workspace activity Show recent activity (runs, traces, etc.) for this workspace ``` dagger workspace activity [flags] ``` ### Options ``` -a, --all Show activity from all remotes in the current workspace ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace cat Print files from the selected workspace ### Synopsis Print file contents from the selected workspace in argument order. Relative paths start at the workspace's current directory. Absolute paths start at the workspace root. The output preserves line endings and does not add a final newline. ``` dagger workspace cat PATH [PATH...] [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace config Get or set workspace configuration ### Synopsis Get or set workspace configuration values in dagger.toml. With no arguments, prints the full configuration. With one argument, prints the value at the given key. With two arguments, sets the value at the given key. With one argument and --unset, removes the value at the given key. Explicit env.* keys address raw overlay storage. Local module source values are stored relative to dagger.toml. ``` dagger workspace config [key] [value] [flags] ``` ### Options ``` -g, --global Write to user-level config instead of the repository, keyed by the workspace's git remote --here Write workspace config at the selected workspace cwd -u, --unset Remove the value at the given key ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace config-file Print the selected workspace config file ``` dagger workspace config-file [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace cwd Print the workspace cwd ``` dagger workspace cwd [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace entrypoint Get or set the workspace entrypoint ### Synopsis Print the installed name of the current entrypoint. Print nothing if no entrypoint is set. With NAME, select that installed module and clear the previous entrypoint. Use --unset to clear the selection. NAME must be an exact installed name. Changes are written to the selected dagger.toml. ``` dagger workspace entrypoint [NAME] [flags] ``` ### Options ``` --unset Clear the entrypoint ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace exec Execute a command in a container with the selected workspace mounted ### Synopsis Execute a command in a container with the selected workspace mounted at /ws. Run the command in /ws/<workspace cwd>. The command and its arguments are executed directly. Use an explicit shell, such as sh -c, for shell syntax. The command's output is shown in Dagger's normal progress output. By default, show a prompt before applying workspace changes. Use --auto-apply to apply without a prompt, or --no-apply to preview changes without applying them. ``` dagger workspace exec [OPTIONS] [--] COMMAND [ARGS...] [flags] ``` ### Examples ``` dagger ws exec -- go test ./... dagger ws exec --from=golang:1.26 -- gofmt -w . dagger ws exec --no-apply -- sh -c 'printf "hello\n" > hello.txt' ``` ### Options ``` --exclude stringArray Exclude workspace paths that match the glob pattern (repeatable) --from string Base container address (default "alpine:3.22.1") --include stringArray Include workspace paths that match the glob pattern (repeatable) --no-apply Compute and show workspace changes without applying them ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace export Export a file or directory from the selected workspace ### Synopsis Export a file or directory from the selected workspace to the local client. PATH defaults to the workspace's current directory. Relative paths start at that directory. Absolute paths start at the workspace root. For a directory, merge its contents into DEST. Use --include and --exclude to filter directory contents. These options can be repeated and use patterns relative to PATH. They cannot be used with a file. ``` dagger workspace export [PATH] -o DEST [flags] ``` ### Examples ``` dagger ws export -o ./workspace dagger ws export ./dist -o ./download dagger -W github.com/dagger/dagger ws export /README.md -o ./README.md ``` ### Options ``` --exclude stringArray Exclude directory paths that match the glob pattern (repeatable) --include stringArray Include directory paths that match the glob pattern (repeatable) -o, --output string Local destination path ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace find Find paths in the selected workspace ### Synopsis Find files and directories in the selected workspace. PATH defaults to the workspace's current directory. Relative paths start at that directory. Absolute paths start at the workspace root. Without --name, print each target followed by its contents, one path per line. Directory entries end with /. Hidden entries are included. Use --name to print paths with a base name that matches a glob pattern. Use --name more than once to match any of the patterns. ``` dagger workspace find [PATH...] [flags] ``` ### Options ``` --name stringArray Print paths with a base name that matches the glob pattern ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace git Inspect Git metadata for the selected workspace ### Synopsis Inspect Git metadata for the selected local or remote workspace. These commands use the workspace's selected Git ref and repository. They do not load installed modules. ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) * [dagger workspace git dirty](#dagger-workspace-git-dirty) - Print whether the workspace has uncommitted changes * [dagger workspace git log](#dagger-workspace-git-log) - Print the workspace commit history * [dagger workspace git ref](#dagger-workspace-git-ref) - Print the resolved ref name, or commit hash if unnamed * [dagger workspace git sha](#dagger-workspace-git-sha) - Print the full commit hash * [dagger workspace git url](#dagger-workspace-git-url) - Print the resolved Git URL, including the selected subdirectory ## dagger workspace git dirty Print whether the workspace has uncommitted changes ### Synopsis Print true if the workspace has uncommitted changes, or false if it is clean. Changes include staged files, unstaged files, and untracked files. Git ignore rules apply to untracked files. Both true and false return exit status 0. ``` dagger workspace git dirty [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace git](#dagger-workspace-git) - Inspect Git metadata for the selected workspace ## dagger workspace git log Print the workspace commit history ### Synopsis Print commit history from the workspace's selected Git ref. Each line contains a short commit hash and the first line of its message. The default limit is 10 commits. The history covers the whole repository. Use --json to print an array with full commit hashes, short hashes, author and committer names, email addresses, dates, messages, and parent hashes. Dates use RFC3339 format. ``` dagger workspace git log [flags] ``` ### Options ``` --json Print commit metadata as JSON --limit int Maximum number of commits (must be greater than zero) (default 10) ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace git](#dagger-workspace-git) - Inspect Git metadata for the selected workspace ## dagger workspace git ref Print the resolved ref name, or commit hash if unnamed ``` dagger workspace git ref [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace git](#dagger-workspace-git) - Inspect Git metadata for the selected workspace ## dagger workspace git sha Print the full commit hash ``` dagger workspace git sha [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace git](#dagger-workspace-git) - Inspect Git metadata for the selected workspace ## dagger workspace git url Print the resolved Git URL, including the selected subdirectory ### Synopsis Print the resolved Git URL, including the selected subdirectory. Use the repository and ref loaded for the selected workspace. The output has the form protocol://repository#ref:subdirectory. Omit the subdirectory when the workspace is at the repository root. For local workspaces, use the origin remote and the loaded ref. A local workspace must have an origin remote. Use the output with dagger -W to select the same remote location. ``` dagger workspace git url [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace git](#dagger-workspace-git) - Inspect Git metadata for the selected workspace ## dagger workspace grep Search file contents in the selected workspace ### Synopsis Search file contents in the selected workspace, including subdirectories. PATTERN is a case-sensitive regular expression. Use -F for a literal string. PATH defaults to the workspace's current directory. Relative paths start at that directory. Absolute paths start at the workspace root. Honor .gitignore, .ignore, and .rgignore files by default. Include hidden files. Use --all to include ignored files. Use -g to filter paths with glob patterns. Print each full matching line as path:line:text, with no surrounding lines. Output paths are relative to the workspace's current directory. Highlight matches when stdout is a terminal, unless NO_COLOR is set. Use -l for paths only, or --json for a JSON array of structured matches. The -l and --json options cannot be combined. Exit status is 0 if a match is found, 1 if no matches are found, and 2 on error. ``` dagger workspace grep PATTERN [PATH...] [flags] ``` ### Examples ``` dagger ws grep 'CurrentWorkspace' sdk/go core dagger -W github.com/dagger/dagger ws grep -i -g '*.md' 'workspace' dagger ws grep -l -F 'TODO' ``` ### Options ``` --all Include ignored files -l, --files-with-matches Print only paths of matching files -F, --fixed-strings Treat PATTERN as a literal string -g, --glob stringArray Include or exclude paths with a glob pattern (repeatable; prefix ! to exclude) -i, --ignore-case Ignore case when matching --json Print a JSON array of structured matches --multiline Allow matches to span multiple lines --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace ls List directories or files in the selected workspace ### Synopsis List directories or files in the selected workspace, one entry per line. PATH defaults to the workspace's current directory. Relative paths start at that directory. Absolute paths start at the workspace root. Directory listings include hidden entries and end directory names with /. For a file, print the supplied path. With multiple paths, list targets in argument order and label directories. ``` dagger workspace ls [PATH...] [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace migrate Migrate the workspace and its installed local modules ### Synopsis Plan migration of the workspace and its installed local modules. Other legacy module configurations are optional candidates. They start unselected and are skipped in non-interactive mode, including with --auto-apply. Use dagger module migrate PATH to select one explicitly. Review all selected changes together. Use --auto-apply to apply without a prompt, or --no-apply to preview without changing files. ``` dagger workspace migrate [flags] ``` ### Options ``` --module stringArray Also migrate this module explicitly (repeatable) --no-apply Preview migration without changing files ``` ### Options inherited from parent commands ``` -y, --auto-apply Automatically apply changes when an output is returned -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -E, --no-exit Leave the TUI running after completion --progress string Progress output format (auto, plain, tty, dots, logs, report) (default "auto") -q, --quiet count Reduce verbosity (show progress, but clean up at the end) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -s, --silent Do not show progress at all -v, --verbose count Increase verbosity (use -vv or -vvv for more) -w, --web Open trace URL in a web browser -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace remote Print the selectable remote address for the current workspace ``` dagger workspace remote [flags] ``` ### Options inherited from parent commands ``` -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace remotes List selectable remote workspace addresses ``` dagger workspace remotes [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace root Print the workspace root ``` dagger workspace root [flags] ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) ## dagger workspace update Refresh all workspace lockfile state ### Synopsis Refresh all installed-module, client-target, and runtime entries in dagger.lock. Regenerate SDK client scopes unless --no-generate is set. ``` dagger workspace update [flags] ``` ### Examples ``` "dagger workspace update" ``` ### Options ``` --no-generate Update the lockfile without regenerating SDK client scopes ``` ### Options inherited from parent commands ``` -d, --debug Enable engine and trace diagnostics --engine string Select the engine: cloud, image://, container://, tcp://, tls://, ssh://, kube-pod://, unix:// (or set DAGGER_ENGINE; run 'dagger help engine' for details) -i, --shell-on-error Open a shell when a container exec fails (needs an interactive terminal) -v, --verbose count Increase verbosity (use -vv or -vvv for more) -W, --workspace string Select the workspace location to load from (local path or git ref) ``` ### SEE ALSO * [dagger workspace](#dagger-workspace) - Inspect or configure your workspace (cwd, remotes, config, etc.) --- # Client libraries URL: https://docs.dagger.io/reference/client-libraries/index # Client libraries Most of the time you call the Dagger API through the CLI or from inside a [module](../sdks/index.mdx). A client is for your own program, whether a script, a service, or a test harness, that needs to talk to the engine directly. There are two ways to get one. ## Generated clients A generated client is a typed binding to one module's API, including the core API and every module it depends on. It lives in an SDK scope. `dagger.toml` records the scope and target module, and `dagger generate` regenerates it with the other generated files. Install the SDK for the required language. For Go, run the client command from a project with a `go.mod` file, then add the target module: ```shell dagger module install dagger.io/sdk/go dagger module client add ./.dagger/modules/api --sdk=go ``` The module argument must be an explicit local path, such as `./api` or `../api`, or a module address. Installed module names and unmarked local paths are not supported. A bare `api` never means `./api`. Saved local targets are relative to the directory containing `dagger.toml`. They keep an explicit path marker. For each SDK, the engine compares the deepest registered scope that contains the current directory with the detected client root. It uses the deeper path. Without `--sdk`, the engine selects the unique deepest scope across installed SDKs. If several SDKs have that scope, the command fails and asks you to select one with `--sdk`. The `--sdk` help text lists installed SDKs. The command accepts no SDK settings flags. Generation uses saved settings. The engine records the target in one scope in `dagger.toml`, and the SDK writes the bindings. ```shell dagger module client list # clients in scopes that contain the current directory dagger module client list --all # clients in every scope dagger generate # regenerate bindings after the bound module changes ``` The list shows `SCOPE`, `SDK`, and the complete `TARGET` removal key. Scope paths are relative to the workspace root. To remove a specific row, run this from its listed scope: ```shell dagger module client rm --sdk= ``` Removal uses recorded client targets and selects the deepest matching scope. It fails if several SDKs match at that scope. Use `--sdk` to select one. List also shows invalid old targets. Remove them with their exact listed target. If invalid targets remain, removal succeeds and generation is skipped with a warning. Correct or remove those targets to resume generation. The current [Go SDK](../sdks/go.mdx) supports clients in ordinary Go projects. Python, Dang, and Java currently require a Dagger module in the scope. TypeScript and PHP still need an update to the new SDK interface. The bindings pin the engine version the bound module requires, so a client and the module it came from stay in step. ## Standalone client libraries Each SDK also publishes a plain client library for the core API to its language's package registry. Use it when you don't need module-specific bindings, or in a language that has no generated clients yet. | Language | Package | Source | |---|---|---| | Go | `dagger.io/dagger` | [`sdk/go`](https://github.com/dagger/dagger/tree/main/sdk/go) | | TypeScript | `@dagger.io/dagger` (npm) | [`sdk/typescript`](https://github.com/dagger/dagger/tree/main/sdk/typescript) | | Python | `dagger-io` (PyPI) | [`sdk/python`](https://github.com/dagger/dagger/tree/main/sdk/python) | | PHP | `dagger/dagger` (Packagist) | [`sdk/php`](https://github.com/dagger/dagger/tree/main/sdk/php) | | Java | `io.dagger:dagger-java-sdk` (Maven) | [`sdk/java`](https://github.com/dagger/dagger/tree/main/sdk/java) | | Elixir | `dagger` (Hex) | [`sdk/elixir`](https://github.com/dagger/dagger/tree/main/sdk/elixir) | | Rust | `dagger-sdk` (crates.io) | [`sdk/rust`](https://github.com/dagger/dagger/tree/main/sdk/rust) | | .NET | `Dagger.SDK` | [`sdk/dotnet`](https://github.com/dagger/dagger/tree/main/sdk/dotnet) | A client library needs a session with the engine. The simplest way to get one is to run your program under `dagger api with-session`. It starts a session and sets `DAGGER_SESSION_PORT` and `DAGGER_SESSION_TOKEN` in the program's environment: ```shell dagger api with-session go run main.go dagger api with-session node index.mjs dagger api with-session python main.py ``` Every library reads those two variables. Progress renders in the same TUI as any other Dagger command, and the run shows up in Dagger Cloud like a `dagger check` would. ## Raw GraphQL The API is GraphQL underneath, so any HTTP client works. With a session from `dagger api with-session`, post queries to `http://127.0.0.1:$DAGGER_SESSION_PORT/query` with the token as the basic-auth username: ```shell jq -n '{query:"{container{id}}"}' | \ dagger api with-session sh -c 'curl -s \ -u $DAGGER_SESSION_TOKEN: \ -H "content-type:application/json" \ -d @- \ http://127.0.0.1:$DAGGER_SESSION_PORT/query' ``` The [API reference](../api/index.mdx) documents the schema. --- # dagger.toml URL: https://docs.dagger.io/reference/config-files/dagger-toml # dagger.toml A workspace is configured by a `dagger.toml` file at its root. It records the modules installed in the workspace and their settings. `dagger module install` creates it on first install. The machine-readable schema is published at [dagger-workspace.schema.json](/reference/dagger-workspace.schema.json). ## Top level | Key | Type | Description | | --- | --- | --- | | `modules` | table | Installed modules, keyed by install name. See [`[modules.]`](#modulesname). | | `sdks` | table | Installed SDK providers and their generation scopes. See [`[sdks.]`](#sdksname). | | `ports` | table | Host port mappings for services. See [`[ports.]`](#portsname). | | `ignore` | array | Path patterns excluded when loading the workspace. | | `defaults_from_dotenv` | bool | Read module constructor defaults from a `.env` file. | | `check-generated` | bool | Run generators as checks during `dagger check`, failing when generated files are stale. Defaults to `true`; CLI flags override it. | Resolved image and Git lookups are pinned in `dagger.lock` alongside the config, and refreshed with `dagger workspace update`. ## `[modules.]` ```toml [modules.eslint] source = "dagger.io/js/eslint@v0.3.0" [modules.eslint.settings] packageManager = "yarn" ``` | Key | Type | Description | | --- | --- | --- | | `source` | string | Module address — a workspace-relative path, or a Git ref such as `github.com/org/mod@version`. | | `pin` | string | Resolved version for `source`. | | `settings` | table | Module settings. Keys are defined by the module; set them with `dagger module settings`. | | `entrypoint` | bool | Marks this module as the workspace entrypoint. | | `legacy-default-path` | bool | Compatibility flag recorded by workspace migration. | | `check` | table | `skip = [...]` — check functions to exclude from `dagger check`. | | `generate` | table | `skip = [...]` — generators to exclude from `dagger generate`. | | `up` | table | `skip = [...]` — services to exclude from `dagger up`. | Settings can point at another module's output instead of a literal value — see [Module wiring](../../config/module-wiring.mdx). ## `[sdks.]` ```toml [modules.go-sdk] source = "dagger.io/sdk/go" [sdks.go] module = "go-sdk" [sdks.go.scopes.".dagger/modules/demo"] is-module = true name = "demo" clients = ["github.com/acme/api"] [sdks.go.scopes.".dagger/modules/demo".settings] template = "default" ``` | Key | Type | Description | | --- | --- | --- | | `module` | string | Name of the installed module that provides this SDK. | | `scopes` | table | Generation scopes, keyed by paths relative to `dagger.toml`. | Each scope can contain one module, module clients, or both. Client paths are relative to the directory containing `dagger.toml`. Use `./api`, `../api`, `.` or `..`; a bare `api` is not a local path. | Scope key | Type | Description | | --- | --- | --- | | `is-module` | bool | The scope contains a Dagger module. | | `name` | string | Optional scope name. Overrides the inferred module name when `is-module` is true. | | `clients` | array | Explicit local paths or module addresses for generated clients. Installed module names are not supported. | | `settings` | table | SDK settings for this scope. | `module init` saves a scope name only when you set `--name`. It preserves an existing saved name. Before generation, an unnamed module scope uses the name of its local entrypoint installation, if one exists. Otherwise, it uses the scope directory name. A scope at the workspace root uses the config-parent or workspace name with `-dev`. Multiple matching entrypoint names require an explicit scope name. The inferred name is not written to this field. Use `dagger sdk scope name -u` to remove an override. An unnamed scope that contains only clients passes an empty name to the SDK. ## `[ports.]` | Key | Type | Description | | --- | --- | --- | | `backendService` | string | Service that backs this host port. | | `backendPort` | int | Port on the backing service. | ## User-level file The same module settings can be set per-user, outside the repository, in `~/.config/dagger/config.toml`. See [User configuration](../../config/user.mdx). --- # Configuration files URL: https://docs.dagger.io/reference/config-files/index # Configuration files Schemas for the files that configure Dagger. | File | Configures | | --- | --- | | [`dagger.toml`](./dagger-toml.mdx) | A workspace: its installed modules, their settings, and environment overlays. | --- # PSScriptAnalyzer URL: https://docs.dagger.io/reference/modules/dotnet/PsScriptAnalyzer # PSScriptAnalyzer The PSScriptAnalyzer module checks PowerShell scripts with PSScriptAnalyzer, applying the same review bar to your `.ps1`, `.psm1`, and `.psd1` files as you would to application code. Reach for it whenever PowerShell is part of the project and you want to enforce style, safety, and ruleset expectations in CI and Dagger Cloud. Its workspace alias is `ps-analyzer`, so its check and settings use that name. ## Add it to your workspace ```bash dagger module install dagger.io/dotnet/PsScriptAnalyzer ``` ## Run the check ```bash dagger check # run every check in the workspace dagger check ps-analyzer:check # run PSScriptAnalyzer on discovered PowerShell scripts ``` `ps-analyzer:check` finds every `.ps1`, `.psm1`, and `.psd1` file in the workspace and runs `Invoke-ScriptAnalyzer` recursively, failing on any diagnostic it reports. ## Configure it List the current settings and their values with `dagger module settings ps-analyzer`, then set one with `dagger module settings ps-analyzer `. They live in `dagger.toml` under `[modules.ps-analyzer.settings]`: - `version` (default `1.22.0`) is the PSScriptAnalyzer release to install. Pin it so the same ruleset and analyzer behavior run locally and in CI. - `exclude` (default: none) is a list of script paths to skip. - `includeExtraFiles` (default empty) are extra non-PowerShell paths to mount, for scripts that read data files or other project context during analysis. ```bash dagger module settings ps-analyzer version 1.22.0 ``` The list-valued settings are edited directly in `dagger.toml`: ```toml [modules.ps-analyzer.settings] version = "1.22.0" exclude = ["tests/"] includeExtraFiles = ["data/"] ``` ## Working with other modules Use this module for repos with PowerShell automation, Windows support scripts, or PowerShell modules. It complements ShellCheck in mixed shell workspaces. [source code](https://github.com/dagger/PsScriptAnalyzer) --- # golangci-lint URL: https://docs.dagger.io/reference/modules/go/golangci-lint # golangci-lint The golangci-lint module runs Go linters as a Dagger check, using the same configuration locally and in CI. It discovers `go.mod` files and runs golangci-lint separately in each selected Go module. ## Add it to your workspace ```bash dagger module install dagger.io/go/golangci-lint ``` `dagger module recommend` suggests this module when it finds `.golangci.yml`, `.golangci.yaml`, `.golangci.toml`, or `.golangci.json` in the workspace. ## Run the check ```bash dagger check # run every check in the workspace dagger check golangci-lint:lint-all # lint the selected Go modules ``` `lint-all` discovers Go modules at or below the working directory, including the enclosing module when run from a subdirectory. Run it from the workspace root to check every module. Each check runs `golangci-lint run` with the workspace's `.golangci.*` configuration files and reports diagnostics with workspace-relative paths. Go and C/C++ sources, embedded assets, and local `go.mod` replacement dependencies within the workspace are included automatically. Go module, build, and golangci-lint caches are shared between runs. ## Configure it List the current settings with `dagger module settings golangci-lint`, then change one with `dagger module settings golangci-lint `. Settings live in `dagger.toml` under `[modules.golangci-lint.settings]`: - `version` (default `2.11.4`) selects the golangci-lint version, without the `v` prefix. The default Alpine image is pinned by digest and includes a C/C++ toolchain for cgo typechecking. - `base` is an optional custom container with Go, golangci-lint, and any required system dependencies installed. It cannot be combined with `version`. - `includeExtraFiles` (default empty) adds workspace-root path patterns for files that automatic source discovery misses. - `lint` (default `["**"]`) selects module roots to lint. A bare path or `path/**` includes that module and its descendants; `!path` excludes them. Exclusions always win. `"**"` and `"*"` match every module. With no positive pattern, every module is included unless excluded; `["!**"]` skips all modules. Other glob forms are not supported by this setting. Edit list-valued settings directly in `dagger.toml`: ```toml [modules.golangci-lint.settings] version = "2.11.4" lint = ["**", "!legacy-service"] includeExtraFiles = ["shared/config/**"] ``` ## Working with other modules Use this module alongside [Go](../go.mdx) for tests and code generation. [source code](https://github.com/dagger/golangci-lint) --- # Staticcheck URL: https://docs.dagger.io/reference/modules/go/staticcheck # Staticcheck The Staticcheck module analyzes Go source as a Dagger check. It discovers `go.mod` files and runs Staticcheck separately in each selected Go module, using the same configuration locally and in CI. ## Add it to your workspace ```bash dagger module install dagger.io/go/staticcheck ``` `dagger module recommend` suggests this module when it finds `staticcheck.conf` in the workspace. ## Run the check ```bash dagger check # run every check in the workspace dagger check staticcheck:lint-all # analyze the selected Go modules ``` `lint-all` discovers Go modules at or below the working directory, including the enclosing module when run from a subdirectory. Run it from the workspace root to check every module. It runs `staticcheck ./...` in each selected module and fails if any module has diagnostics. Test files are analyzed too; tests and generators are not executed. The module includes `staticcheck.conf` files with their directory structure intact, preserving configuration inheritance. Go and native sources, embedded assets, and local `go.mod` replacement dependencies within the workspace are included automatically. Go module, build, and Staticcheck caches are shared between runs. ## Configure it List the current settings with `dagger module settings staticcheck`, then change one with `dagger module settings staticcheck `. Settings live in `dagger.toml` under `[modules.staticcheck.settings]`: - `version` (default `v0.8.1`) selects the Staticcheck release installed with `go install`. - `goVersion` (default `1.26`) selects the Go version for the default Alpine container. Choose a toolchain supported by the Staticcheck release and new enough for your project. The default container includes a C/C++ toolchain. - `base` is an optional custom Go container with any required native dependencies. Staticcheck is built in this container. It cannot be combined with `goVersion`. - `includeExtraFiles` (default empty) adds workspace-root path patterns for files that automatic source discovery misses. - `lint` (default `["**"]`) selects module roots to analyze. A bare path or `path/**` includes that module and its descendants; `!path` excludes them. Exclusions always win. `"**"` and `"*"` match every module. With no positive pattern, every module is included unless excluded; `["!**"]` skips all modules. Other glob forms are not supported by this setting. Edit list-valued settings directly in `dagger.toml`: ```toml [modules.staticcheck.settings] version = "v0.8.1" goVersion = "1.26" lint = ["**", "!legacy-service"] includeExtraFiles = ["shared-assets/**"] ``` ## Working with other modules Use this module alongside [Go](../go.mdx) for tests and code generation. [source code](https://github.com/dagger/staticcheck) --- # Go URL: https://docs.dagger.io/reference/modules/go # Go The Go module gives a Go workspace one shared way to test and run `go generate`. It scans the workspace for `go.mod` files, treats each one as a Go module, and exposes workspace-level functions that run across all of them. That makes it a good fit for monorepos, service repos, and libraries with generated code, especially when several modules should share one Go version and the same CI checks. ## Add it to your workspace ```bash dagger module install dagger.io/go ``` ## Run the checks ```bash dagger check # run every check in the workspace dagger check go:test-all # run Go tests across every module ``` `test-all` discovers every `go.mod` in the workspace, treats each as a Go module, and runs tests against all of them. Tests run through an OpenTelemetry-aware runner, so individual Go tests appear as spans in the Dagger TUI and Dagger Cloud. ## Generate code Run the generator when generated Go files are part of normal development, such as mocks, embedded assets, protobuf output, or anything produced by `go generate`: ```bash dagger generate go:generate-all ``` `generate-all` runs only in modules that contain a `//go:generate` directive, and returns the result as a changeset to review before applying. ## Configure it List the current settings with `dagger module settings go`, then change one with `dagger module settings go `. They live in `dagger.toml` under `[modules.go.settings]`: - `version` (default `1.26`) is the Go toolchain version used to build the test and generate containers (`golang:-alpine`). Set this so every module is tested and generated against the same Go version. - `includeExtraFiles` (default empty) are extra workspace-root path patterns mounted alongside each module's Go source. Go source, `go.mod`/`go.sum`/`go.work`, and `testdata/` directories are already included automatically; use this for inputs those patterns miss, such as embedded non-Go assets, generator inputs, or fixtures kept outside `testdata/`. - `test` and `generate` (each defaults to `["**"]`) are module-root selector arrays for `test-all` and `generate-all`. A bare pattern includes modules, a `!`-prefixed pattern excludes them, and exclusions always win. `"**"` and `"*"` match every module; `"path"` and `"path/**"` match the module at `path` and any modules below it. With no positive pattern, every module is included unless excluded, so `["!**"]` disables that workflow everywhere. ```bash # Pin the Go version for the whole workspace dagger module settings go version 1.25 ``` List-valued settings are edited directly in `dagger.toml`: ```toml [modules.go.settings] version = "1.25" includeExtraFiles = ["Makefile", "tools/**"] test = ["**", "!legacy-service"] # Test all modules except legacy-service and modules below it ``` ## Working with other modules Reach for this module whenever the repo contains one or more Go modules, especially when they should share the same Go version and CI checks. To exclude a single module from a workflow, add a `!`-prefixed module path to the corresponding selector array rather than splitting the repo into separate check systems. For Go lint checks, install [golangci-lint](./go/golangci-lint.mdx) or [Staticcheck](./go/staticcheck.mdx) alongside this module. [source code](https://github.com/dagger/go) --- # Modules URL: https://docs.dagger.io/reference/modules/index # Modules These guides walk through setting up the official Dagger modules in your project. They are the standard library of tools that give a workspace useful work to do: test code, run a test runner, lint and format source, or validate Helm charts. Reach for a module when you want a real tool that runs the same way locally, in CI, and in Dagger Cloud. Most expose checks, generators, or repair workflows, so the work stays consistent everywhere it runs. Add any module to your project with `dagger module install`: ```bash dagger module install dagger.io/go ``` Each guide explains what the module is for, which checks or generators to reach for first, the settings that tune it, and how it fits into your project. | Guide | What it covers | | --- | --- | | [Go](./go.mdx) | Test and run generators across every Go module in a workspace. | | [golangci-lint](./go/golangci-lint.mdx) | Lint Go modules with golangci-lint. | | [Staticcheck](./go/staticcheck.mdx) | Analyze Go modules with Staticcheck. | | [Deno](./js/deno.mdx) | Test, lint, format, and type-check every Deno project in a workspace. | | [Jest](./js/jest.mdx) | Run Jest tests for JavaScript and TypeScript. | | [Vitest](./js/vitest.mdx) | Run Vitest tests for JavaScript and TypeScript. | | [Playwright](./js/playwright.mdx) | Run Playwright browser tests, wired to the service under test. | | [ESLint](./js/eslint.mdx) | Lint JavaScript and TypeScript source. | | [Prettier](./js/prettier.mdx) | Check and rewrite source formatting. | | [Biome](./js/biome.mdx) | Lint and format JavaScript and TypeScript with one tool. | | [Pytest](./python/pytest.mdx) | Run Python tests with Pytest. | | [ShellCheck](./shellcheck.mdx) | Check shell scripts. | | [PSScriptAnalyzer](./dotnet/PsScriptAnalyzer.mdx) | Check PowerShell scripts. | | [Helm](./kubernetes/helm.mdx) | Lint Helm charts and check rendered templates. | --- # Biome URL: https://docs.dagger.io/reference/modules/js/biome # Biome The Biome module runs Biome linting over your JavaScript and TypeScript source and can return a fixed version when Biome can repair issues. Reach for it when a project uses Biome as its main code quality tool, so a single combined tool handles both lint and format rules instead of a separate ESLint and Prettier pair. ## Add it to your workspace ```bash dagger module install dagger.io/js/biome ``` ## Run the check ```bash dagger check # run every check in the workspace dagger check biomejs:lint # run Biome against the workspace source ``` `biomejs:lint` installs dependencies and runs `biome check` over the whole workspace using the project's own `biome.json`. Biome's `check` covers both linting and formatting, which is why this single check replaces a separate ESLint and Prettier pair. ## Fix issues Biome also exposes a `fix` function that runs `biome check --write` and returns the repaired source as a changeset (covering `.js`, `.ts`, `.jsx`, `.tsx`). It is a regular function rather than a check, so it does not run during `dagger check`; call it with `dagger api call` (run `dagger api functions` to see available functions). ## Configure it List the current settings and their values with `dagger module settings biomejs`, then set one with `dagger module settings biomejs `. Settings are stored in `dagger.toml` under `[modules.biomejs.settings]`: - `baseImageAddress` (default `node:25-alpine`) is the Node base image Biome runs in. Pin it to the project's Node version, for example `node:22-alpine`. Biome installs with npm, so unlike the ESLint and Prettier modules there is no package-manager setting. ```toml [modules.biomejs.settings] baseImageAddress = "node:22-alpine" ``` ## Working with other modules Use Biome for projects that already have Biome config. If your project uses ESLint and Prettier separately, use those modules instead. [source code](https://github.com/dagger/biomejs) --- # Deno URL: https://docs.dagger.io/reference/modules/js/deno # Deno The Deno module gives a Deno workspace one shared way to test, lint, format, and type-check. It scans the workspace for `deno.json`/`deno.jsonc` files, treats each as a Deno project, reads the `workspace` array to fan out across members, and exposes workspace-level functions that run across all of them. That makes it a good fit for monorepos and repos where every project should share one Deno version and the same CI checks. Rather than wrapping the `deno` CLI, it models a Deno project as a typed object graph and maps Deno's toolchain onto Dagger's first-class verbs, so the same checks run locally, in CI, and in Dagger Cloud. ## Add it to your workspace ```bash dagger module install dagger.io/js/deno ``` ## Run the checks ```bash dagger check # run every check in the workspace dagger check deno:test-all # deno test across every project dagger check deno:lint-all # deno lint across every project dagger check deno:type-check-all # deno check across every project dagger check deno:format-check-all # deno fmt --check across every project ``` The `-all` checks discover every `deno.json`/`deno.jsonc` in the workspace, treat each as a Deno project, and run against all of them. Deno permissions come from each project's `deno.json` (under `test.permissions` or `permissions.default`), not from flags. To run a check against a single project, call it directly: ```bash dagger call deno project --path . test dagger call deno project --path apps/api type-check ``` ## Format the source `format-check` only reports whether files are formatted; `format` rewrites them. Because formatting mutates the workspace, `format` returns a changeset. Dagger prints the diff and asks before writing. Add `-y` to apply it without prompting: ```bash dagger call deno project --path . format # preview the diff dagger -y call deno project --path . format # apply it ``` ## Compile a binary `compile` builds a standalone executable from an entrypoint and returns it as a file to export: ```bash dagger call deno project --path . \ compile --entrypoint main.ts --target x86_64-unknown-linux-gnu \ export --path ./bin/app ``` ## Configure it List the current settings with `dagger module settings deno`, then change one with `dagger module settings deno `. They live in `dagger.toml` under `[modules.deno.settings]`: - `version` (default `2.9.3`) is the Deno version used to build the check containers. Pin this so every project is tested, linted, and formatted against the same Deno release. - `base` is the base container image Deno runs in (for example `docker.io/denoland/deno:debian`). Override it to control the OS or runtime the toolchain runs on. `base` and `version` are mutually exclusive. When `base` is set it already pins a Deno release, so `version` is ignored. ```bash # Pin the Deno version for the whole workspace dagger module settings deno version 2.9.3 ``` ```toml [modules.deno.settings] version = "2.9.3" base = "docker.io/denoland/deno:debian" ``` ## Working with other modules Reach for this module whenever the repo contains one or more Deno projects, especially when they should share the same Deno version and CI checks. It composes with the rest of your workspace. The `base` and `install` functions expose a Deno-ready container you can hand to other modules, and `version` prints the configured toolchain version. [source code](https://github.com/dagger/deno) --- # ESLint URL: https://docs.dagger.io/reference/modules/js/eslint # ESLint The ESLint module checks JavaScript and TypeScript source with ESLint, running the same lint check locally and in CI so issues get caught before a PR lands. Reach for it when the repo already has ESLint config and you want linting to run as a Dagger check, with a repair workflow available for fixable issues. ## Add it to your workspace ```bash dagger module install dagger.io/js/eslint ``` ## Run the check ```bash dagger check # run every check in the workspace dagger check eslint:lint # run ESLint against the workspace source ``` `eslint:lint` installs dependencies and runs `eslint .` over the whole workspace using the project's own ESLint configuration, so the result matches what developers see locally and fails on lint errors. ## Fix issues ESLint also exposes a `fix` function that runs `eslint . --fix` and returns the repaired source as a changeset (excluding `node_modules`). It is a regular function rather than a check, so it does not run during `dagger check`; call it with `dagger api call` (run `dagger api functions` to see available functions). ## Configure it List the current settings and their values with `dagger module settings eslint`, then set one with `dagger module settings eslint `. Settings are stored in `dagger.toml` under `[modules.eslint.settings]`. - `packageManager` (default `npm`) is the package manager used to install dependencies before linting. Set it to `yarn` or `pnpm` to match the project, so the same lockfile and dependency versions are used. - `baseImageAddress` (default `node:25-alpine`) is the Node base image ESLint runs in. Pin it to the project's Node version, for example `node:22-alpine`. ```toml [modules.eslint.settings] packageManager = "pnpm" baseImageAddress = "node:22-alpine" ``` ## Working with other modules Use ESLint with Prettier when the project separates lint rules from formatting. Use Biome when the project has moved linting and formatting into one tool. [source code](https://github.com/dagger/eslint) --- # Jest URL: https://docs.dagger.io/reference/modules/js/jest # Jest The Jest module runs Jest tests for JavaScript and TypeScript projects. Reach for it when Jest is already the test runner for the repo and you want those tests to become a Dagger check that runs in CI and Dagger Cloud, whether you're testing one app or package in a larger workspace or debugging which tests get discovered. ## Add it to your workspace ```bash dagger module install dagger.io/js/jest ``` ## Run the tests ```bash dagger check # run every check in the workspace dagger check jest:test # run the Jest suite ``` `jest:test` installs dependencies and runs the Jest suite over the workspace (excluding `node_modules`, `dist`, and `build`). It automatically registers an OpenTelemetry hook, so individual tests appear as spans in the Dagger TUI and Dagger Cloud without changing the project's Jest config. ## Test options The `jest:test` check runs with default options. Call the `test` function directly with `dagger api call` to override them: - `files` limits the run to specific test files. - `build` runs the project's build script before testing. - `useEnv` uses the project's own Jest environment instead of the module's automatic OpenTelemetry environment. - `flags` are extra flags passed through to `jest`. Use `list` to print the tests Jest discovers when test selection is unclear. ## Configure it List the current settings and their values with `dagger module settings jest`, then set one with `dagger module settings jest `. Settings are stored in `dagger.toml` under `[modules.jest.settings]`: - `packageManager` (default `npm`) is the package manager used to install dependencies before testing. Set it to `yarn` or `pnpm` to match the project. - `baseImageAddress` (default `node:25-alpine`) is the Node base image tests run in. Pin it to the project's Node version, for example `node:22-alpine`. ```toml [modules.jest.settings] packageManager = "pnpm" baseImageAddress = "node:22-alpine" ``` ## Working with other modules Use this module when Jest is the test runner. Use Vitest instead for projects built around Vitest or Vite-first test workflows. [source code](https://github.com/dagger/jest) --- # Playwright URL: https://docs.dagger.io/reference/modules/js/playwright # Playwright The Playwright module runs your [Playwright](https://playwright.dev) browser tests in a container whose browsers always match your Playwright version, the same way locally, in CI, and in Dagger Cloud. Its distinctive feature is first-class [module wiring](../../../config/module-wiring.mdx). If another module in your workspace serves your app, one line of configuration points the tests at it. No glue module, no port juggling. ## Add it to your workspace ```bash dagger module install dagger.io/js/playwright ``` Pin your project's `@playwright/test` version exactly (or commit a lockfile). The module runs your tests in the `mcr.microsoft.com/playwright` image matching the version installed per `package-lock.json`. Without a lockfile it uses the version declared in `package.json`, where a floating range like `^1.58.2` can install a newer Playwright than the image's browsers. ## Run the check ```bash dagger check # run every check in the workspace dagger check playwright:test # just the Playwright suite ``` `playwright:test` finds the directory containing `playwright.config.*`, installs your project's dependencies, and runs `npx playwright test`. If your config declares a [`webServer`](https://playwright.dev/docs/test-webserver), Playwright starts your app inside the container exactly as it does on your machine, with no further setup needed. ## Wire in the service under test If another module already serves your app, wire it into the tests instead of duplicating that knowledge in `webServer`. Two steps: **1. Set the `service` setting to a module reference.** That is the install name of another module in your `dagger.toml`, and a function on it that returns a `Service`. Run `dagger up -l` to list the candidates in copyable form: ```toml [modules.playwright.settings] service = "myapp:serve" ``` **2. Add `PLAYWRIGHT_BASE_URL` to your `playwright.config`.** The module binds the service into the test container and communicates its address through this environment variable. If your config doesn't read it, your tests will ignore the wired service and keep targeting whatever `baseURL` hardcodes: ```js use: { baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000', }, ``` Keep your local URL as the fallback so the same config works on your machine. By default the service is bound as `frontend`; set `serviceHostname` if your tests need a different hostname. ### Secure contexts (service workers, WebCrypto, PWA testing) Browser APIs that require a secure context don't work against `http://frontend:`, since only `localhost` or HTTPS origins qualify. If any of your tests exercise service workers, WebCrypto, or other secure-context APIs, enable the localhost proxy: ```toml [modules.playwright.settings] service = "myapp:serve" localhostProxy = true ``` Then point those tests at `PLAYWRIGHT_LOCALHOST_BASE_URL`, which the proxy sets, for example as the `baseURL` of a dedicated project in your config: ```js { name: 'chromium-pwa', use: { ...devices['Desktop Chrome'], baseURL: process.env.PLAYWRIGHT_LOCALHOST_BASE_URL || 'http://localhost:3000', }, }, ``` ## Prepare your config for the container The module sets `CI=true`, so review what your `playwright.config` keys off `process.env.CI`, such as `retries`, `forbidOnly`, and especially `workers`: - **Replace a `workers: process.env.CI ? 1 : undefined` clamp with a bounded value like `4`.** The container is isolated, so the usual shared-CI reason to serialize doesn't apply, and a `1` clamp can make the suite many times slower. Don't go unbounded either, since too many workers starve the browsers and blow test timeouts. - **Remove branded-browser projects or exclude them with the `args` setting.** Those browsers (`channel: 'msedge'`, `channel: 'chrome'`) are not present in the Playwright images; the bundled `chromium` covers the same engine. ## Configure it List the current settings with `dagger module settings playwright`, then change one with `dagger module settings playwright `. They live in `dagger.toml` under `[modules.playwright.settings]`: - `sourcePath` (default: discover) is the workspace path of the Playwright project. Set it when the workspace holds more than one `playwright.config.*`. - `service` is the module reference (`"module:function"`) of the service under test. - `serviceHostname` (default `frontend`) is the hostname the service is bound as inside the test container. - `baseImageAddress` (default: derive) overrides the derived `mcr.microsoft.com/playwright:v-noble` image. - `baseCtr` is a full `Container` override. It is also wireable, e.g. `baseCtr = "base-images:chromium"`. - `packageManager` (default `npm`) is the package manager used to install dependencies. Set it to `yarn`, `pnpm`, or `bun` to match your project. - `localhostProxy` (default `false`) enables the localhost proxy described under secure contexts above. - `args` (default `[]`) are extra `playwright test` arguments, e.g. `["--project", "chromium"]`. - `shards` (default `1`) splits the check across that many parallel containers. Shards run concurrently against the same wired service and fail fast on the first failure. ```toml [modules.playwright.settings] service = "myapp:serve" shards = 4 ``` ## Get the HTML report To inspect a failing run, call `report`. It runs the suite tolerating failures and returns the HTML report directory. Include the `html` reporter in your config, then export the report to your machine: ```bash dagger api call playwright report -o ./playwright-report ``` ## Working with other modules Playwright covers browser-level end-to-end testing; pair it with [Jest](./jest.mdx) or [Vitest](./vitest.mdx) for unit tests. Any module whose function returns a `Service` can be the app under test. That's the [wiring contract](../../../config/module-wiring.mdx), not a special integration. [source code](https://github.com/dagger/playwright) --- # Prettier URL: https://docs.dagger.io/reference/modules/js/prettier # Prettier The Prettier module keeps formatting boring, consistent, and enforced before code review. It checks formatting locally, in CI, and in Dagger Cloud, and can rewrite source to match, always using your project's own Prettier config. Keep it alongside a linter so formatting policy stays separate from lint rules. ## Add it to your workspace ```bash dagger module install dagger.io/js/prettier ``` ## Run the check ```bash dagger check # run every check in the workspace dagger check prettier:check # just check formatting ``` `prettier:check` installs dependencies and runs `prettier --check .` across the workspace, failing if any file isn't formatted. ## Fix formatting Prettier also exposes a `write` function that runs `prettier --write .` across the workspace and returns the reformatted source as a changeset (everything except `node_modules`). Which files it touches is governed by your project's own Prettier configuration and ignore files. It's a regular function, not a check, so it doesn't run during `dagger check`. Call it directly: ```bash dagger api call prettier write ``` ## Configure it List the current settings with `dagger module settings prettier`, then change one with `dagger module settings prettier `. They live in `dagger.toml` under `[modules.prettier.settings]`: - `packageManager` (default `npm`) is the package manager used to install dependencies before checking. Set it to `yarn` or `pnpm` to match the project. - `baseImageAddress` (default `node:25-alpine`) is the Node base image Prettier runs in. Pin it to your project's Node version, e.g. `node:22-alpine`. ```toml [modules.prettier.settings] packageManager = "pnpm" baseImageAddress = "node:22-alpine" ``` ## Working with other modules Prettier pairs well with ESLint. ESLint checks code quality, Prettier owns formatting. If your project uses Biome for both, use the [Biome](./biome.mdx) module instead. [source code](https://github.com/dagger/prettier) --- # Vitest URL: https://docs.dagger.io/reference/modules/js/vitest # Vitest The Vitest module runs Vitest tests for JavaScript and TypeScript projects. Reach for it when the project uses Vitest, especially Vite apps and modern frontend packages, whether you want to run the suite as a workspace check, validate frontend packages and libraries, or list discovered tests when test selection is unclear. ## Add it to your workspace ```bash dagger module install dagger.io/js/vitest ``` ## Run the tests ```bash dagger check # run every check in the workspace dagger check vitest:test # run the Vitest suite ``` `vitest:test` installs dependencies and runs the Vitest suite over the workspace (excluding `node_modules`, `dist`, and `build`). It automatically registers an OpenTelemetry hook, so individual tests appear as spans in the Dagger TUI and Dagger Cloud without changing the project's Vitest config. ## Test options The `vitest:test` check runs with default options. Call the `test` function directly with `dagger api call` to override them: - `files` limits the run to specific test files. - `build` runs the project's build script before testing. - `flags` are extra flags passed through to `vitest`. Use `list` to print the tests Vitest discovers when test selection is unclear. ## Configure it List the current settings and their values with `dagger module settings vitest`, then set one with `dagger module settings vitest `. They live in `dagger.toml` under `[modules.vitest.settings]`: - `packageManager` (default `npm`) is the package manager used to install dependencies before testing. Set it to `yarn` or `pnpm` to match the project; with `pnpm`, the module enables Corepack automatically. - `baseImageAddress` (default `node:25-alpine`) is the Node base image tests run in. Pin it to the project's Node version, for example `node:22-alpine`. ```toml [modules.vitest.settings] packageManager = "pnpm" baseImageAddress = "node:22-alpine" ``` ## Working with other modules Use this module when Vitest is the test runner. Use Jest instead for projects that already depend on Jest conventions and config. [source code](https://github.com/dagger/vitest) --- # Helm URL: https://docs.dagger.io/reference/modules/kubernetes/helm # Helm The Helm module validates Helm charts across your workspace. It discovers charts, lints them, and checks that values files render with `helm template --dry-run=client`. That catches structural and templating problems before a PR ever reaches a cluster. Reach for it in any repo that ships Kubernetes applications with Helm, where it makes a strong PR check by validating chart changes before deploy tooling sees them, and it shares one pinned Helm version across the whole workspace. ## Add it to your workspace ```bash dagger module install dagger.io/kubernetes/helm ``` ## Run the checks ```bash dagger check # run every check in the workspace dagger check helm:lint # lint every discovered chart dagger check helm:assert-template # render discovered values files and fail on errors ``` The module discovers charts by finding every `Chart.yaml` in the workspace and treats the containing directory as the chart root, so templates, subcharts, CRDs, and files referenced through `.Files` are all available to Helm. You can see which charts the module finds with `charts`. `lint` runs `helm lint` against each chart's default values, and once more for each discovered values file. `assert-template` renders each values file with `helm template --dry-run=client` and fails if templating breaks; it intentionally skips the bare chart, since some charts only render with one of their explicit values files. ## Configure it List the current settings and their values with `dagger module settings helm`, then set one with `dagger module settings helm `. Settings are stored in `dagger.toml` under `[modules.helm.settings]`: - `version` (default `3.18.4`) is the Helm version used for `lint` and `template`. Pin it so local runs and CI use the same Helm release. The module resolves it as the Wolfi `helm~` package, which now tracks the 4.x line, so 3.x versions no longer resolve. - `valuesGlob` (default `ci/*-values.yaml`) is a glob, relative to each chart root, that selects the values files to check. Every matching file becomes a separate scenario in both `lint` and `assert-template`. The default follows Helm chart-testing's CI convention, so files like `ci/prod-values.yaml` are picked up automatically. ```bash dagger module settings helm version 4.0.1 dagger module settings helm valuesGlob "ci/*-values.yaml" ``` To check a chart under several configurations, add more values files that match the glob. With the default glob, the chart below is rendered and linted once per file under `ci/`: ``` charts/api/ Chart.yaml values.yaml ci/ prod-values.yaml minimal-values.yaml ``` ## Working with other modules Use this module for repos that ship Kubernetes applications with Helm. It is a strong PR check because it validates chart changes before deploy tooling sees them. [source code](https://github.com/dagger/helm) --- # Pytest URL: https://docs.dagger.io/reference/modules/python/pytest # Pytest The Pytest module runs your Python tests with Pytest, the same way locally, in CI, and in Dagger Cloud. It discovers Python projects in your workspace. It can use a custom Python container when the default environment is not enough. ## Add it to your workspace ```bash dagger module install dagger.io/python/pytest ``` ## Run the tests ```bash dagger check # run every check in the workspace dagger check pytest:test-all # run Pytest against the selected projects ``` `pytest:test-all` injects `pytest_otel` automatically, so individual Python tests appear as spans in the Dagger TUI and Dagger Cloud with no project changes. The module uses uv to install the project dependencies, pytest, and `pytest_otel` together. ## Test options The `pytest:test-all` check uses the module settings: - `version` (default `3.14`) is the Python version the default container provisions, such as `3.13` or `3.12`. It cannot be combined with a custom `base`. - `args` (default `["-v"]`) are arguments passed straight to `pytest`, such as `["-x", "--tb=short"]`. Keep this module focused on tests; use separate modules for formatting, linting, shell scripts, or generated files. ## Configure it List the current settings and their values with `dagger module settings pytest`, then set one with `dagger module settings pytest `. Settings are stored in `dagger.toml` under `[modules.pytest.settings]`: - `scope` (default: `["**"]`) selects project roots to test. Paths are relative to the workspace root. - `base` (default: none) is a custom container that already has Python and uv installed. Set it when tests need system packages, a private index, or other tools. ```toml [modules.pytest.settings] scope = ["service"] ``` ## Working with other modules This module is a good first check for Python repos. Once it passes locally, it is a natural candidate for autocheck and PR validation. [source code](https://github.com/dagger/python/tree/main/pytest) --- # ShellCheck URL: https://docs.dagger.io/reference/modules/shellcheck # ShellCheck The ShellCheck module finds shell scripts in your workspace and checks them with ShellCheck, catching quoting, portability, and safety issues in CI and Dagger Cloud. Reach for it when scripts are part of the project and should be treated like code, not as untested glue. Deployment scripts, local dev scripts, and CI helpers all benefit. ## Add it to your workspace ```bash dagger module install dagger.io/shellcheck ``` ## Run the check ```bash dagger check # run every check in the workspace dagger check shellcheck:check # run ShellCheck on discovered shell scripts ``` `check` finds every `.sh` file in the workspace and runs ShellCheck on each one, so a script must use the `.sh` extension to be discovered. Use `scripts` to see exactly which files the module found. ## Configure it List the current settings and their values with `dagger module settings shellcheck`. The `exclude` setting is list-valued, so set it in `dagger.toml` under `[modules.shellcheck.settings]`: - `exclude` (default: none) is a list of script paths to skip. Use it for vendored or generated scripts the repo does not own. ```toml [modules.shellcheck.settings] exclude = ["vendor/", "third_party/"] ``` Keep the exclude list narrow so new scripts are checked automatically. ## Working with other modules This module is small and high value. It is a good default check for repos that contain deployment scripts, local dev scripts, or CI helper scripts. [source code](https://github.com/dagger/shellcheck) --- # Dang SDK URL: https://docs.dagger.io/reference/sdks/dang import { daggerVersion } from '../../partials/version.js'; # Dang SDK Dang is Dagger's native DSL. It maps directly to the Dagger API, so what you write is what runs. There is no codegen, no generated client files to commit, no build step, and no language runtime to carry around. In the common case a Dang module is one `main.dang` file plus a `dagger-module.toml`. Use Dang when your module mostly orchestrates the Dagger API: containers, files, directories, services, secrets, and other modules. If you need external libraries (a Go parser, a Python ML library, a Node.js bundler API), use the [Go](./go.mdx), [Python](./python.mdx), or [TypeScript](./typescript.mdx) SDK instead. Those give you a full host language alongside the Dagger client. Every SDK shares a few platform concepts. Read these first if they are new to you: - [SDKs overview](./index.mdx) explains what a module is and how it fits into a workspace. - [Types](../api/index.mdx) covers the types your functions accept and return. - [Generating code](../../using/generating.mdx) shows how Dagger represents file diffs. `init` and generators both use them. ## A note on tooling: Dang is delivered as a Dagger module The Dang SDK is a Dagger module, `dagger.io/sdk/dang`. Install it once, then create and maintain modules with the CLI module commands: ```shell # Install the Dang SDK into your workspace (once) dagger module install dagger.io/sdk/dang # Create a module dagger module init dang --name my-ci ``` The commands you will use most often: | Command | Purpose | |---|---| | `dagger module init dang` | Create a module and generate its configuration. | | `dagger module client add`, `rm`, `update`, `list` | Manage module clients from the module directory. | | `dagger generate` | Regenerate configuration for registered modules. | | `dagger sdk scope list --sdk=dang --is-module` | List Dang modules. | ## Create a module :::note Run these commands from inside a Git repository. That is where Dagger creates the new module. ::: Install the Dang SDK into your workspace, then create a new module with `dagger module init`. By default it creates the module beside the active `dagger.toml`: ``` /.dagger/modules/ ``` ```shell dagger module install dagger.io/sdk/dang dagger module init dang --name my-ci ``` `dagger module init` returns a [changeset](../../using/generating.mdx). Dagger shows it to you for review before writing any files into your workspace. `init` takes these arguments: - `--name` is optional. Without `--name` or `--path`, Dagger infers `-dev` and installs the module as the workspace entrypoint. - `--path` selects a directory relative to the current directory. A custom path is registered for generation but is not installed; use `dagger module install ` to install it. - `--template minimal` selects the starter template. `minimal` is the default. - `--fat` also generates a `dagger.json` file for older engines. It is disabled by default. ### Generated layout ``` .dagger/ modules/ my-ci/ dagger-module.toml main.dang ``` The generated `dagger-module.toml`: ```toml title=".dagger/modules/my-ci/dagger-module.toml" template name = "my-ci" engineVersion = "v{{ version }}" [runtime] source = "dang" ``` Setting `runtime.source` to `"dang"` tells Dagger to run this module with the Dang runtime. `engineVersion` declares the engine version the module requires. See [Engine version](#engine-version). The generated `main.dang` entry point: ```dang title=".dagger/modules/my-ci/main.dang" """ Starter Dang module generated by dang-sdk. """ type MyCi { """ Return a greeting from this Dang module. """ pub hello: String! { "hello from Dang" } } ``` Once you apply the changeset, call your module by pointing `-m` at it, or run from inside the module's workspace: ```shell dagger -m .dagger/modules/my-ci api call hello # hello from Dang ``` ## Language basics Dang is small on purpose. The whole language fits in a short list: - **Types.** Declare one with `type Name { ... }`. The first type in a module is the primary type and its entry point. - **Public members.** Use `pub`. Private members use `let`. Only `pub` members are visible to callers. - **Functions.** A function is a type member that returns a value: `pub build: Container! { ... }`. - **Arguments.** They go in parentheses: `pub build(source: Directory!): Container! { ... }`. - **Non-null.** Mark it with `!`. Nullable is the default and has no marker. - **Directives.** They modify behavior: `@check`, `@generate`, `@up`, `@cache`. - **Descriptions.** Put a triple-quoted string (`""" ... """`) above the thing it describes. - **Comments.** Start them with `#`. - **Module metadata.** A triple-quoted docstring at the top of the file, above the primary `type`. A minimal module is a type with at least one public function: ```dang """ CI for my project. """ type MyCi { """ Say hello. """ pub hello: String! { "Hello from Dagger!" } } ``` `pub` makes a member visible to callers. The docstring at the top of the file is the module's summary. `dagger api functions` and `dagger api call --help` show it. Per-member docstrings document individual functions and arguments. Try it: ```shell dagger api call hello # Hello from Dagger! ``` ### Expressions and chaining Dang chains method calls on the Dagger API. Each function body is a single expression, and the function returns whatever that last expression evaluates to. The chain reads top to bottom: ```dang container .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withExec(["npm", "install"]) ``` Every method returns a new immutable value. Nothing mutates in place. Dagger caches each step by its inputs, so a re-run skips unchanged work. It is the same model as Docker layer caching, applied to the entire API. See the [type reference](../api/index.mdx) for the underlying model. ## Define objects and functions A module is a `type`. Functions are its `pub` members. The function body returns a value of the declared return type: ```dang """ CI for my web application. """ type MyCi { pub build: Container! { container .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withExec(["npm", "install"]) .withExec(["npm", "run", "build"]) } } ``` ### Private state with `let` `let` defines a private binding, such as internal state or a helper that callers cannot see. Dang evaluates it lazily and caches the result. Use `let` for shared setup that several functions reuse: ```dang type Security { pub source: Directory! new(ws: Workspace!) { self.source = ws.directory("/") self } # Private: not callable by users let trivyBase = container .from("aquasec/trivy:0.68.2") .withMountedCache( path: "/root/.cache", cache: cacheVolume("trivy-cache"), sharing: CacheSharingMode.LOCKED, ) .withWorkdir("/home/trivy") # Public: callable by users pub scanSource: Void { trivyBase .withMountedDirectory(".", source) .withExec(["trivy", "fs", "--exit-code=1", "--severity=CRITICAL,HIGH", "."]) .sync null } } ``` ### Custom types Define additional `type`s to model what your module produces, for example to return several related values from one function: ```dang type MyCi { """ Build result containing the binary and metadata. """ type BuildResult { pub binary: File! pub version: String! pub platform: String! } pub build(platform: String! = "linux/amd64"): BuildResult! { let bin = container .from("golang:1.22") .withDirectory("/app", source) .withWorkdir("/app") .withExec(["go", "build", "-o", "/out/app", "."]) .file("/out/app") BuildResult { binary: bin, version: "1.0.0", platform: platform, } } } ``` Dagger prefixes custom type names in the API schema (for example `MyCiBuildResult`) to avoid conflicts when several modules load together. You reach a custom type by chaining from a function on the primary type. ### Enumerations Use `enum` to restrict an argument to a fixed set of values: ```dang type Security { enum Severity { UNKNOWN LOW MEDIUM HIGH CRITICAL } pub scan(ref: String!, severity: Severity!): String! { container .from("aquasec/trivy:latest") .withExec(["trivy", "image", "--severity", severity, ref]) .stdout } } ``` An invalid value produces an error that lists the allowed choices: ```shell dagger api call scan --ref=alpine:latest --severity=FOO # Error: value should be one of UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL ``` ### Interfaces Interfaces let your module accept types from other modules without depending on them. Declare an `interface` at the top level of the file, not nested inside a `type`. List the `pub` members you need as signatures only, with no body: ```dang """ Any object that can produce a container image. """ interface Buildable { pub build: Container! } type Deployer { pub deploy(app: Buildable!, registry: String!): String! { app.build.publish(registry + "/app:latest") } } ``` A concrete type declares that it satisfies an interface with `implements`: ```dang type WebApp implements Buildable { pub source: Directory! new(ws: Workspace!) { self.source = ws.directory("/") self } pub build: Container! { container .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withExec(["npm", "run", "build"]) } } ``` Across module boundaries Dagger also matches structurally. You can pass any object from another module whose functions match `Buildable` where the interface is expected, even without an explicit `implements` declaration. ## Arguments and return values Functions accept typed arguments in parentheses. An argument with a default value is optional; an argument with a `!` type and no default is required: ```dang type MyCi { pub build( """ Node.js version to use. """ nodeVersion: String! = "20", ): Container! { container .from("node:" + nodeVersion) .withDirectory("/app", source) .withWorkdir("/app") .withExec(["npm", "install"]) .withExec(["npm", "run", "build"]) } } ``` An argument can carry: - **Types.** `String!`, `Int!`, `Boolean!`, `Directory!`, `File!`, `Secret!`, `Container!`, custom types, enums, interfaces, and so on. - **Defaults.** `= "20"` makes the argument optional. - **Descriptions.** A triple-quoted string above the argument. - **Non-null markers.** `!` means required when there is no default. Without it the argument is nullable. ```shell dagger api call build dagger api call build --node-version=18 ``` Constructor arguments, which are members on the primary type set in `new(...)`, give users knobs they can override globally. The constructor also receives the user's [Workspace](#workspace-inputs). Dagger fills that in, and the module reads project files from it: ```dang type MyCi { pub source: Directory! pub nodeVersion: String! pub registry: String! new( ws: Workspace!, nodeVersion: String! = "20", registry: String! = "ghcr.io", ) { self.source = ws.directory("/") self.nodeVersion = nodeVersion self.registry = registry self } pub publish(tag: String!): String! { build.publish(registry + "/myorg/myapp:" + tag) } } ``` ```shell # CLI override dagger api call --node-version=18 build # Or in dagger.toml # [modules.my-ci.settings] # nodeVersion = "18" # registry = "docker.io" ``` ## Working with core Dagger types Dang exposes the full Dagger API directly. These are the types you will use most: ### Containers ```dang pub build: Container! { container .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withExec(["npm", "install"]) .withExec(["npm", "run", "build"]) } ``` ### Files and directories Functions can return `File!` or `Directory!`, and accept them as arguments. Reach into a container's filesystem with `.file(path)` or `.directory(path)`: ```dang pub binary: File! { build.file("/app/dist/server.js") } ``` ### Secrets Accept secrets as the `Secret` type, never as plain strings. Dagger scrubs secret values from all output streams, including crash reports: ```dang pub deploy( """ API token for deployment. """ token: Secret!, ): Void { container .from("alpine") .withSecretVariable("DEPLOY_TOKEN", token) .withExec(["sh", "-c", "deploy --token=$DEPLOY_TOKEN"]) .sync null } ``` Callers supply secrets through providers: ```shell dagger api call deploy --token=env:DEPLOY_TOKEN # environment variable dagger api call deploy --token=file:./token.txt # file dagger api call deploy --token=cmd:"gh auth token" # command output dagger api call deploy --token=op://vault/item/field # 1Password dagger api call deploy --token=vault://path/to/secret # HashiCorp Vault dagger api call deploy --token=gcp://secret-name # Google Cloud Secret Manager ``` A secret is scoped to the module that defines it. To share one across modules, pass it as a function argument. ### Services Start services for integration tests or dev environments. Services are content-addressed, so the same definition always gets the same hostname and there are no port conflicts: ```dang type MyCi { pub source: Directory! new(ws: Workspace!) { self.source = ws.directory("/") self } let db: Service { container .from("postgres:16") .withEnvVariable("POSTGRES_PASSWORD", "test") .withExposedPort(5432) .asService } pub integrationTest: Void @check { container .from("golang:1.22") .withDirectory("/app", source) .withServiceBinding("db", db) .withEnvVariable("DATABASE_URL", "postgres://postgres:test@db:5432/postgres") .withExec(["go", "test", "-tags=integration", "./..."]) .sync null } } ``` ### Cache volumes Use cache volumes for package manager caches and other persistent data that should survive across runs. `cacheVolume("name")` is keyed by name: ```dang pub build: Container! { container .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withMountedCache("/app/node_modules", cacheVolume("node-modules")) .withExec(["npm", "install"]) .withExec(["npm", "run", "build"]) } ``` A cache volume is scoped to the module that defines it. To share one across modules, pass a reference as a function argument. ## Module dependencies A Dang module can depend on modules written in any SDK. Run the client commands from the module directory: ```shell cd .dagger/modules/my-ci dagger module client add github.com/shykes/daggerverse/hello@v0.3.0 --sdk=dang dagger module client list --sdk=dang dagger module client update --sdk=dang dagger module client rm github.com/shykes/daggerverse/hello@v0.3.0 --sdk=dang ``` Client add and remove commands update targets in `dagger.toml`. Client update refreshes `dagger.lock`. All three regenerate the module configuration. Review and apply each changeset. The SDK writes the runtime dependencies shown below; generation replaces manual dependency edits in `dagger-module.toml`. The result lands in `dagger-module.toml`: ```toml title="dagger-module.toml" template name = "my-ci" engineVersion = "v{{ version }}" [runtime] source = "dang" [[dependencies]] name = "hello" source = "github.com/shykes/daggerverse/hello@v0.3.0" [[dependencies]] name = "local" source = "./path/to/module" ``` A dependency reference follows `[proto://]host/repo[/subpath][@version]`: ``` github.com/shykes/daggerverse/hello@v0.3.0 # ^^^^^^ ^^^^^^^^^^^^^ ^^^^^ ^^^^^^ # host repo path version ``` - `proto://` is optional (`ssh://` or `https://`). If you omit it, Dagger chooses based on the authentication available. - `@version` can be a tag, branch, or commit. If you omit it, Dagger uses the default branch. - Local dependencies use a relative path (`./path/to/module`). Once added, call a dependency in your code by its name, like a function: ```dang type MyCi { pub source: Directory! new(ws: Workspace!) { self.source = ws.directory("/") self } pub devContainer: Container! { # 'go' is the dependency module. Call it like a function go(source: source).env.withWorkdir("/app") } pub test: Void @check { devContainer.withExec(["go", "test", "./..."]).sync null } } ``` ## Generate and module metadata Most SDKs use a generate step to produce client bindings from the Dagger API schema, which you then commit. Dang has no such step. Because Dang maps directly to the Dagger API, there are no generated client files and nothing language-specific to check in. What you write in `main.dang` is what runs. `dagger generate` regenerates module configuration from the registered SDK scopes. Dang still produces no client source files. ```shell dagger generate dagger sdk scope list --sdk=dang --is-module ``` ## Engine version Each module declares its required engine version with `engineVersion` in `dagger-module.toml`. The Dang SDK writes this field during generation. A manual edit can be replaced by the next generation. The value must be a concrete version (for example v{daggerVersion}). ## Workspace inputs A module reads the surrounding project's files through a `Workspace` argument on its constructor. Dagger fills in this argument from the current workspace. The caller passes nothing, and nothing uploads up front. The module reads project content lazily, only when it uses it: ```dang type MyCi { """The source directory for the project.""" pub source: Directory! new(ws: Workspace!) { self.source = ws.directory("/") self } } ``` `Workspace` has three readers: - `ws.directory(path)` reads a directory from the workspace. - `ws.file(path)` reads a single file. - `ws.findUp(name:, from:)` searches upward from a start path for a file or directory by name and returns a nullable path. Use it to locate a config file that may live in a parent directory. Relative paths resolve from the workspace cwd, where the user invoked `dagger`. Absolute paths, which begin with `/`, resolve from the workspace root. ```dang type MyCi { pub source: Directory! pub config: File! new(ws: Workspace!) { # Absolute: from the workspace root self.source = ws.directory("/src") # Relative: from the workspace cwd self.config = ws.file("tsconfig.json") self } } ``` `ws.directory` accepts an `exclude` list to filter out files you don't need. This matters for caching. Excluding `node_modules`, `.git`, build output, and similar paths avoids needless cache invalidations: ```dang type MyCi { pub source: Directory! new(ws: Workspace!) { self.source = ws.directory("/", exclude: [ "node_modules", ".git", "dist", ]) self } } ``` **Read only what you need.** Don't load the whole repo if you only need `src/`. Read specific paths and use tight `exclude` lists to keep cache invalidations down. Reads are lazy, so content the module never touches never uploads. A complete example, modeled on [`dagger/eslint`](https://github.com/dagger/eslint): ```dang type Eslint { """The source directory for the project.""" pub source: Directory! pub baseImageAddress: String! new( ws: Workspace!, baseImageAddress: String! = "node:25-alpine", ) { self.source = ws.directory("/") self.baseImageAddress = baseImageAddress self } pub lint: Void @check { nodejs(source, baseImageAddress).base.withExec(["npx", "eslint", "."]).sync null } } ``` ## Checks, generators, services, directives Dang has three first-class function types. Each has a directive that marks it and a verb that runs it. A useful module provides at least one of them: | Directive | Returns | Run by | Purpose | |---|---|---|---| | `@check` | `Void` (or a value) | `dagger check` | Validate something, such as a lint, test, or scan. | | `@generate` | `Changeset` | `dagger generate` | Produce a diff of generated files for review. | | `@up` | `Service!` | `dagger up` | Start a long-running service. | ### Checks {#checks} A check validates something without requiring arguments. Mark it with `@check` and `dagger check` discovers and runs it. A check passes if it completes without error. It fails if any `withExec` returns a non-zero exit code. ```dang type MyCi { pub source: Directory! new(ws: Workspace!) { self.source = ws.directory("/") self } """ Lint the code. """ pub lint: Void @check { container .from("golangci/golangci-lint:latest") .withDirectory("/app", source) .withWorkdir("/app") .withExec(["golangci-lint", "run"]) .sync null } } ``` A check can also return `Container`. Dagger syncs it and uses the exit code: ```dang pub lint: Container @check { container .from("golangci/golangci-lint:latest") .withDirectory("/app", source) .withWorkdir("/app") .withExec(["golangci-lint", "run"]) } ``` ### Generators {#generators} A generator produces a [changeset](../../using/generating.mdx), a diff between the current source and freshly generated output. Mark it with `@generate`. `.changes(source)` computes the diff against the original source. `dagger generate` runs all generators and presents the combined changeset for review: ```dang pub generateProto: Changeset @generate { container .from("bufbuild/buf:latest") .withDirectory("/app", source) .withWorkdir("/app") .withExec(["buf", "generate"]) .directory(".") .changes(source) } ``` > These `@generate` generators are your module's own code generation pipelines, such as protobuf or OpenAPI. They have nothing to do with SDK client codegen, which Dang does not have. ### Services {#up-services} A service function returns a long-running `Service!`. Mark it with `@up` and `dagger up` starts it. Build the service from a container with `.asService`, and expose the ports it should listen on: ```dang pub web: Service! @up { container .from("nginx:alpine") .withExposedPort(80) .asService } ``` A module can expose several `@up` services, and `dagger up` starts each one. This differs from the private `let db: Service { ... }` pattern shown under [Services](#services) above. A `let` service is internal plumbing, such as a database wired into a check with `withServiceBinding`. An `@up` service is a public entry point that users start directly. ### Caching directives By default Dagger caches function results for up to 7 days, keyed by inputs (arguments, parent state, module source). Tune it per function with `@cache`: ```dang # Cache for 10 minutes (e.g. external data that changes) pub latestRelease: String! @cache(ttl: "10m") { ... } # Cache only for the current session pub sessionId: String! @cache(policy: "PerSession") { ... } # Never cache (always re-execute) pub currentTime: String! @cache(policy: "Never") { ... } ``` The `policy` values are `Default`, `PerSession`, and `Never`. A function cache hit skips the function entirely. A miss runs it, but individual operations inside may still hit the layer cache. `@cache(policy: "Never")` forces the function to run every call but does not disable layer caching for the operations inside it. ## Testing Dang modules The most direct way to test a Dang module is to call its functions and run its checks: ```shell # Smoke test. Does it build? dagger api call build # Run all checks dagger check # Run generators and verify there's no drift dagger check --generate ``` For more thorough testing, write a separate test module (in any SDK) that depends on yours, exercises its functions, and asserts on the results. ### CI Wire `dagger check` into CI. Pin the engine version (see [Engine version](#engine-version)) for reproducibility: ```yaml title=".github/workflows/ci.yml" jobs: dagger: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dagger/dagger-for-github@v6 with: verb: check ``` `dagger check` runs every `@check` function in the module and fails the build if any check fails. ## Packaging and release A Dang module is just source: `main.dang`, `dagger-module.toml`, and any extra `.dang` files. There is no build step. Run `dagger generate` to update the module configuration before publishing. To release: 1. Commit `dagger-module.toml`, `main.dang`, and any other source files. 2. Check the engine requirement in the generated `dagger-module.toml`. 3. Tag the repository (`git tag v0.1.0 && git push --tags`). Consumers install your module into their workspace with the `dagger` CLI: ```shell dagger module install github.com/yourorg/yourrepo/path@v0.1.0 ``` They can then call its functions (`dagger api call ...`) and run its checks (`dagger check`). ## Troubleshooting - **Module not found.** Check `dagger module list`. A module created with `--path` needs a separate `dagger module install `, or use `-m ` for one command. - **SDK not found.** Install `dagger.io/sdk/dang`, then check `dagger sdk list`. - **Parser and type errors.** Dang reports these straight from the module source. Check the non-null markers (`!`), that the last expression in a function body matches the declared return type, and that every custom type and enum name you reference exists. - **A `Void` function must end in `null`.** A `Void` function usually calls `.sync` on a container or service to force evaluation, then returns `null` as its final expression. - **Changeset not written.** `dagger module init`, module client commands, and `dagger generate` present a [changeset](../../using/generating.mdx). Review and apply it to write the files. - **Stale generation expectations.** Dang has no client codegen, so there are no generated bindings to regenerate. If a tutorial tells you to commit generated SDK files, skip that step. ## Next steps - [SDKs overview](./index.mdx) - [Types](../api/index.mdx) - [Generating code](../../using/generating.mdx) --- # Elixir SDK URL: https://docs.dagger.io/reference/sdks/elixir # Elixir SDK :::note The Elixir SDK module still uses the previous beta SDK interface. It needs an update before the current `dagger module init` and module client commands can use it. The module runtime is unchanged. ::: The Elixir SDK lets you write Dagger modules in Elixir. A module is an ordinary Mix project. Its main module uses `Dagger.Mod.Object` and declares functions with `defn`, and the SDK turns those into Dagger objects and functions that anyone can call from the CLI, from another module, or over the API. In return, your module gets a generated, typed Elixir client for the whole Dagger API, reachable through `dag()`. That covers containers, directories, files, secrets, services, and every module you depend on. This page is a standalone guide to the Elixir SDK. It assumes you already understand the platform concepts covered in the [SDKs overview](./index.mdx): - [Types](../api/index.mdx), which explains how SDK types map to the Dagger API - [Generating code](../../using/generating.mdx), which explains how generators and tooling return diffs for you to apply A useful, reusable module provides at least one of the three first-class function types: a check, a generator, or a service. The Elixir SDK supports checks only today. See [Checks, cache policy, directives, and ignore patterns](#checks-cache-policy-directives-and-ignore-patterns) for the Elixir syntax and the current limitations. The Elixir SDK is itself a Dagger module, `dagger.io/sdk/elixir`. Install it into your workspace once, then create and maintain Elixir modules with Dagger's module commands: ```shell # Install the Elixir SDK into your workspace (once) dagger module install dagger.io/sdk/elixir # Create an Elixir module dagger module init elixir --name my-module ``` :::note Use the full SDK reference for installation. With the current SDK interface, an installed `elixir-sdk` module provides the SDK name `elixir`. Use `dagger sdk list` to check the SDK names in your workspace. ::: ## Create a module :::note Run these commands from inside a Git repository. That is where Dagger creates the new module. ::: Install the Elixir SDK into your workspace, then create a new module. The SDK argument is required. This example also sets the optional module name: ```shell dagger module install dagger.io/sdk/elixir dagger module init elixir --name my-module ``` `dagger module init` returns a [changeset](../../using/generating.mdx), a structured diff of the files it wants to create. Dagger shows it to you for review before writing anything to disk. :::tip If the repository does not have a `dagger.toml` yet, use `dagger module install --here dagger.io/sdk/elixir` to create it in the current directory. ::: ### Where the module is created By default, `dagger module init` places the new module beside the `dagger.toml` it is editing: ``` /.dagger/modules/ ``` That is the workspace root unless the config lives in a subdirectory, as it does when several projects share one repository. Pass `--path` to choose a different location. The path is relative to your current directory, like any other path you type, and a leading `/` means the workspace root. ```shell dagger module init elixir --name my-module --path ci # ./ci dagger module init elixir --name my-module --path /ci # /ci ``` Dagger registers a custom path as a module authored by the SDK, but does not install it as a callable workspace module. Run `dagger module install ./ci` to install it too. Pass `--template` to pick a starter. `default` gives you a working module with two example functions; `empty` gives you a bare object module with no functions: ```shell dagger module init elixir --name my-module --template empty ``` List the Elixir SDK's module initialization options with: ```shell dagger module init elixir --help ``` ### Resulting file layout Once initialized and generated, an Elixir module is a regular Mix project: ```text my-module/ ├── dagger-module.toml ├── mix.exs # depends on ./dagger_sdk by path ├── .formatter.exs ├── README.md ├── lib/ │ └── my_module.ex # your code └── dagger_sdk/ # generated: the Elixir SDK + typed API bindings ├── mix.exs └── lib/ └── dagger/ ├── gen/ # bindings generated from your engine's schema └── ... ``` The Dagger module name determines the Elixir names: `my-module` becomes the Mix application `:my_module`, the source file `lib/my_module.ex`, and the Elixir module `MyModule`. The runtime looks up the entrypoint module by that derived name, so keep `defmodule` in step with the module name. :::note `dagger module init` writes `dagger-module.toml`, updates the workspace config, and seeds `mix.exs`, `.formatter.exs`, `README.md`, and `lib/my_module.ex` from the SDK's template. It then runs the Elixir SDK's generator for the new module as part of the same changeset. The generator writes `dagger_sdk/`, the SDK library plus API bindings. A fresh module's `mix.exs` depends on `./dagger_sdk`, so it does not compile until that directory exists. Commit `dagger_sdk/` along with the rest of the module. The runtime builds from the committed sources and does not regenerate them. ::: The module config records the runtime separately from the SDK that authors it: ```toml title="dagger-module.toml" template name = "my-module" engineVersion = "v{{ version }}" [runtime] source = "dagger.io/sdk/elixir/runtime" ``` Elixir has no built-in engine runtime, so `runtime.source` points at the Elixir SDK's own runtime module. That runtime compiles your module in an `elixir:*-alpine` container and runs `mix dagger.entrypoint.invoke `. The workspace's `dagger.toml` records the SDK under `sdks.elixir` and records the module path as one of its scopes. Elixir binding generation uses that scope to discover the module. Nobody writes the files in `dagger_sdk/` by hand; see [Regenerate bindings](#regenerate-bindings-and-generated-files). ## Define objects and functions An Elixir module is an ordinary Mix project. The main object is the Elixir module named after your Dagger module (`my-module` → `MyModule`), and it calls `use Dagger.Mod.Object, name: "MyModule"`. Every function declared with the `defn` macro becomes a callable Dagger Function. ```elixir title="lib/my_module.ex" defmodule MyModule do @moduledoc """ A simple example module to say hello. Further documentation for the module here. """ use Dagger.Mod.Object, name: "MyModule" @doc """ Return a greeting. """ defn hello(name: String.t(), greeting: String.t()) :: String.t() do "#{greeting}, #{name}!" end @doc """ Return a loud greeting. """ defn loud_hello(name: String.t(), greeting: String.t()) :: String.t() do String.upcase("#{greeting}, #{name}!") end end ``` Key rules: - `use Dagger.Mod.Object, name: "..."` marks the Elixir module as a Dagger object. The `name` is the object's name in the Dagger API. - `defn name(arg: type, ...) :: return_type do ... end` declares a Dagger Function. Argument and return types are ordinary Elixir typespecs, and the SDK converts them to Dagger types when it registers the module. - The SDK exposes only `defn` functions. Plain `def` and `defp` functions are private Elixir helpers that callers never see. - `use Dagger.Mod.Object` imports `dag/0`, which returns the client for the Dagger API. Use `dag()` to start any pipeline. - A function fails when it raises or returns an `{:error, reason}` tuple. The SDK accepts and unwraps `{:ok, value}`, so you can return the result of a leaf call such as `Dagger.Container.stdout/1` directly. Call your functions exactly like any other module, from the directory that contains `dagger-module.toml` or with `-m `: ```shell dagger api call hello --name=World --greeting=Hello # Hello, World! dagger api call loud-hello --name=World --greeting=Hello # HELLO, WORLD! ``` The CLI converts Elixir function and argument names to kebab-case (`loud_hello` → `loud-hello`, `string_arg` → `--string-arg`). ### Fields, state, and the constructor An object carries state in an `object do ... end` block of `field` declarations, which defines a struct. A `defn init` function becomes the module's constructor. Its arguments become arguments of the main object, and it returns the initialized struct. Use it for module-wide configuration and shared state. A function receives the object's state when its first parameter is a bare variable, conventionally `self`, placed before the keyword list of arguments: ```elixir title="lib/my_module.ex" defmodule MyModule do @moduledoc false use Dagger.Mod.Object, name: "MyModule" object do field :name, String.t(), doc: "Who to greet" end defn init(name: {String.t(), default: "world"}) :: MyModule.t() do %__MODULE__{name: name} end @doc """ Return a greeting for the configured name. """ defn greeting(self) :: String.t() do "Hello, #{self.name}!" end @doc """ Return a copy of this object with a different name. """ defn with_name(self, name: String.t()) :: MyModule.t() do %{self | name: name} end end ``` ```shell dagger api call --name=Elixir greeting # Hello, Elixir! dagger api call with-name --name=Dagger greeting # Hello, Dagger! ``` Fields are public state. They appear in the API, and Dagger serializes them between functions in a chain. A field typed `T | nil` is optional; every other field is required when you build the struct. `field` accepts `doc:` and `deprecated:` options. The Elixir SDK has no private-field marker, so keep values you don't want to expose out of `object do` and recompute them in a helper instead. A common constructor pattern accepts a `Dagger.Workspace.t()` so the module can read the project it runs against. Store the project directory once as a field and reuse it from every function. See [Workspace inputs](#workspace-inputs). ## Arguments and return values Dagger derives a function's argument and return types from the typespecs in the `defn` signature. The mapping is: | Elixir typespec | Dagger type | |---|---| | `String.t()` or `binary()` | `String` | | `integer()` | `Int` | | `float()` | `Float` | | `boolean()` | `Boolean` | | `list(T)` or `[T]` | `[T]` (list) | | `T \| nil` | optional `T` | | `Dagger.Directory.t()` | `Directory` | | `Dagger.File.t()` | `File` | | `Dagger.Container.t()` | `Container` | | `Dagger.Secret.t()` | `Secret` | | `Dagger.Service.t()` | `Service` | | `Dagger.Workspace.t()` | `Workspace` (auto-populated) | | `Dagger.Void.t()` | `Void` (no return value) | | `MyObject.t()` (a module using `Dagger.Mod.Object`) | object `MyObject` | | `MyEnum.t()` (a module using `Dagger.Mod.Enum`) | enum | Any other type generated under the `Dagger` namespace (`Dagger.CacheVolume.t()`, `Dagger.Platform.t()`, …) maps to the corresponding core type. An unsupported typespec raises `ArgumentError` at compile time. ### Documentation Doc attributes become API documentation, shown by `dagger api functions` and `dagger api call --help`. `@moduledoc` documents the module, and its first line is the short description. `@doc` directly above a `defn` documents the function. The `doc:` argument option documents an argument. To attach options to an argument, wrap its type and options in a tuple: ```elixir @doc """ Return a greeting. """ defn hello(name: {String.t(), doc: "Who to greet"}) :: String.t() do "Hello, #{name}!" end ``` The full set of argument options is: | Option | Meaning | |---|---| | `doc: "..."` | description shown in `--help` | | `default: value` | optional argument with a default value | | `default_path: "..."` | on `Dagger.Directory.t()` or `Dagger.File.t()`, load this path when the caller omits the argument | | `ignore: [...]` | on `Dagger.Directory.t()`, gitignore-style patterns to exclude from the loaded directory | | `deprecated: "..."` | mark the argument deprecated with a reason | ### Optional and default arguments Dagger arguments are required by default. Make one optional with a `| nil` union, or give it a default with the `default:` option: ```elixir title="optional" defn hello(name: String.t() | nil) :: String.t() do if name, do: "Hello, #{name}", else: "Hello, world" end ``` ```elixir title="default value" defn hello(name: {String.t(), default: "world"}) :: String.t() do "Hello, #{name}" end ``` - `T | nil` makes the argument optional. It is `nil` when the caller omits it, so you can tell that the caller left it out. - `default: value` makes the argument optional and supplies a default value when the caller omits it. The default also becomes the Elixir function's default argument, so calling `hello()` from other Elixir code in the module yields the same value. ### Nullability Use a `| nil` union (for example `Dagger.Secret.t() | nil`) when an argument may be absent. `nil` corresponds to a null or omitted value. Non-optional types are always present. Declare a function that returns nothing with `Dagger.Void.t()`. Dagger discards whatever it returns (`:ok` is conventional), and only a raise or an `{:error, _}` tuple fails the call. ### Enums Model a closed set of values with a module that uses `Dagger.Mod.Enum`. Dagger turns it into an enum and validates inputs. Each value's atom name is what callers pass. To give a value a description, or a different serialized name, use the keyword forms: ```elixir title="lib/my_module.ex" defmodule Severity do @moduledoc "Vulnerability severity levels" use Dagger.Mod.Enum, name: "Severity", values: [ UNKNOWN: [doc: "Undetermined risk; analyze further."], LOW: [doc: "Minimal risk; routine fix."], MEDIUM: [doc: "Moderate risk; timely fix."], HIGH: [doc: "Serious risk; quick fix needed."], CRITICAL: [doc: "Severe risk; immediate action."] ] end defmodule MyModule do @moduledoc false use Dagger.Mod.Object, name: "MyModule" defn scan(ref: String.t(), severity: {Severity.t(), default: Severity.high()}) :: String.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("aquasec/trivy:0.50.4") |> Dagger.Container.with_exec([ "trivy", "image", "--severity=#{Atom.to_string(severity)}", ref ]) |> Dagger.Container.stdout() end end ``` Inside the function the argument arrives as an atom (`:HIGH`). The enum module also defines a lowercase accessor per value (`Severity.high()`), which is the form to use for a `default:`. Passing a value outside the enum fails with an error that lists the allowed choices: ```shell dagger api call scan --ref=alpine:latest --severity=FOO # Error: value should be one of UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL ``` ### Custom object types Return a struct from another `Dagger.Mod.Object` module to expose a custom object. Its fields become readable values, and its `defn` functions become chainable functions. Dagger discovers custom objects by following function return types from the main object, so every custom object must be the direct return type of at least one function. In the API schema, Dagger prefixes their names with the module name to avoid collisions: ```elixir title="lib/my_module.ex" defmodule MyModule.Account do @moduledoc false use Dagger.Mod.Object, name: "Account" object do field :username, String.t() field :email, String.t() end defn url(self) :: String.t() do "https://github.com/#{self.username}" end end defmodule MyModule.Organization do @moduledoc false use Dagger.Mod.Object, name: "Organization" object do field :url, String.t() field :repositories, [Dagger.GitRepository.t()] end defn member(self, username: String.t()) :: MyModule.Account.t() do %MyModule.Account{username: username, email: "#{username}@example.com"} end end defmodule MyModule do @moduledoc false use Dagger.Mod.Object, name: "MyModule" defn dagger_organization() :: MyModule.Organization.t() do url = "https://github.com/dagger" %MyModule.Organization{ url: url, repositories: [Dagger.Client.git(dag(), url <> "/dagger")] } end end ``` You can then chain calls on the CLI and API: ```shell dagger api call dagger-organization member --username=jane url ``` ### Deprecation Deprecate a function with Elixir's standard `@deprecated "reason"` attribute (or `@doc deprecated: "reason"`), an argument or field with the `deprecated:` option, and a whole object with `@moduledoc deprecated: "reason"`. The reason appears in the API and in `--help`. ### Interfaces The Elixir SDK does not yet support declaring interfaces. Accept concrete core types or values from your own custom objects instead. ## Working with core Dagger types The generated client exposes the entire Dagger API. `dag()` returns a `Dagger.Client`. Every core type is a module under the `Dagger` namespace (`Dagger.Container`, `Dagger.Directory`, …), and its functions take the value as the first argument, so pipelines read well with `|>`. Functions that return a leaf value (`stdout`, `entries`, `contents`, `sync`, …) return `{:ok, value}` or `{:error, reason}`. Everything else returns a new lazy value. ### Containers Each builder function returns a new, immutable `Dagger.Container`. Nothing mutates in place. Every step is content-addressed, and Dagger caches it automatically. ```elixir @doc """ Build and return a container. """ defn build(source: Dagger.Directory.t()) :: Dagger.Container.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("node:20") |> Dagger.Container.with_directory("/app", source) |> Dagger.Container.with_workdir("/app") |> Dagger.Container.with_exec(["npm", "install"]) |> Dagger.Container.with_exec(["npm", "run", "build"]) end ``` ### Directories and files `Dagger.Directory` and `Dagger.File` are first-class, "just-in-time" artifacts. You can accept them as arguments, return them, mount them into containers, and export them to the host. Less common parameters go in a trailing keyword list. For example, `with_directory` accepts `exclude:`: ```elixir @doc """ Copy a directory into a container, excluding some paths. """ defn copy_directory_with_exclusions( source: {Dagger.Directory.t(), doc: "Source directory"}, exclude: {[String.t()] | nil, doc: "Exclusion patterns"} ) :: Dagger.Container.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("alpine:latest") |> Dagger.Container.with_directory("/src", source, exclude: exclude || []) end ``` The same pattern holds everywhere. Required parameters are positional, and optional ones live in a trailing keyword list whose keys are the snake_cased API argument names. ### Workspace inputs {#workspace-inputs} When a module needs to read the user's project, such as its source tree or config files, it takes a `Dagger.Workspace.t()` argument, almost always on the constructor. You don't pass it. Dagger fills it in from the current workspace and uploads nothing up front. Dagger pulls project content lazily, when a function actually reads a path, so a module can declare access to the whole workspace cheaply and only pay for what it touches. ```elixir title="lib/my_module.ex" defmodule MyModule do @moduledoc false use Dagger.Mod.Object, name: "MyModule" object do field :source, Dagger.Directory.t() end defn init(ws: Dagger.Workspace.t()) :: MyModule.t() do # Pull the workspace root as a Directory (lazy, no upload yet). %__MODULE__{source: Dagger.Workspace.directory(ws, "/")} end @doc """ Functions reuse the pulled Directory like any other. """ defn build(self) :: Dagger.Container.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("node:20") |> Dagger.Container.with_directory("/app", self.source) |> Dagger.Container.with_workdir("/app") |> Dagger.Container.with_exec(["npm", "install"]) |> Dagger.Container.with_exec(["npm", "run", "build"]) end end ``` The `Dagger.Workspace` client module exposes accessors for reading project content: | Accessor | Signature | Returns | |---|---|---| | `directory` | `Dagger.Workspace.directory(ws, path, opts \\ [])` | a `Dagger.Directory` at `path` | | `file` | `Dagger.Workspace.file(ws, path)` | a `Dagger.File` at `path` | | `find_up` | `Dagger.Workspace.find_up(ws, name, opts \\ [])` | `{:ok, path}`, the workspace path of `name`, searching upward | **Path resolution.** A relative path resolves from the workspace's current working directory. An absolute path (starting with `/`) resolves from the workspace root, also called the boundary. So `Dagger.Workspace.directory(ws, "/")` is the whole project root, while `Dagger.Workspace.directory(ws, ".")` is wherever the user invoked Dagger from. **Excluding files.** `directory` takes `exclude:`, `include:`, and `gitignore:` options to filter what gets pulled. Filter tightly. Every file you load is one more file that can invalidate the cache, and `_build` and `deps` change far more often than your code does: ```elixir defn init(ws: Dagger.Workspace.t()) :: MyModule.t() do %__MODULE__{ source: Dagger.Workspace.directory(ws, "/", exclude: ["_build", "deps", ".git"] # include: ["lib/", "mix.*"], # allowlist instead # gitignore: true # apply .gitignore rules ) } end ``` `find_up` walks up from a start path and returns the absolute workspace path of the first match, stopping at the workspace boundary. A relative start path resolves from the workspace cwd; pass `from: "..."` to change it. Use it to locate a project root marker such as `mix.exs`. :::tip To use the current workspace, declare a `Dagger.Workspace.t()` argument on the module constructor or function. Dagger injects it and omits it from the CLI arguments. ::: `default_path:` and `ignore:` remain available on `Dagger.Directory.t()` and `Dagger.File.t()` arguments for modules that want a path-defaulted argument the caller can override. Prefer a `Dagger.Workspace.t()` for project content. ### Secrets Accept sensitive values as `Dagger.Secret.t()`, never as plain strings. Dagger scrubs secret plaintext from logs, caches, and crash reports: ```elixir @doc """ Query the GitHub API. """ defn github_api(token: {Dagger.Secret.t(), doc: "GitHub API token"}) :: String.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("alpine:3.17") |> Dagger.Container.with_secret_variable("GITHUB_API_TOKEN", token) |> Dagger.Container.with_exec(["apk", "add", "curl"]) |> Dagger.Container.with_exec([ "sh", "-c", ~s(curl "https://api.github.com/repos/dagger/dagger/issues" --header "Authorization: Bearer $GITHUB_API_TOKEN") ]) |> Dagger.Container.stdout() end ``` Callers supply secrets through providers on the CLI: ```shell dagger api call github-api --token=env:GITHUB_TOKEN # environment variable dagger api call github-api --token=file:./token.txt # file dagger api call github-api --token=cmd:"gh auth token" # command output dagger api call github-api --token=op://vault/item/field # 1Password ``` ### Services Return `Dagger.Service.t()` to expose a long-running service, and bind it into other containers with `with_service_binding`. Services are content-addressed, so a given definition always gets the same hostname and port conflicts never come up: ```elixir @doc """ Start and return an HTTP service. """ defn http_service() :: Dagger.Service.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("python") |> Dagger.Container.with_workdir("/srv") |> Dagger.Container.with_new_file("index.html", "Hello, world!") |> Dagger.Container.with_exposed_port(8080) |> Dagger.Container.as_service(args: ["python", "-m", "http.server", "8080"]) end @doc """ Send a request to an HTTP service and return the response. """ defn get() :: String.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("alpine") |> Dagger.Container.with_service_binding("www", http_service()) |> Dagger.Container.with_exec(["wget", "-O-", "http://www:8080"]) |> Dagger.Container.stdout() end ``` ## Module dependencies A module can depend on other Dagger modules and call them through the generated client. The generator writes a dependency's objects and functions into `dagger_sdk/` alongside the core API, so a dependency named `hello` becomes a `Dagger.Hello` module that you reach through `Dagger.Client.hello(dag())`. `dagger-module.toml` records dependencies under `dependencies`: ```toml title="dagger-module.toml" template name = "dev" engineVersion = "v{{ version }}" [runtime] source = "dagger.io/sdk/elixir/runtime" [[dependencies]] name = "hello" source = "github.com/shykes/daggerverse/hello@v0.3.0" [[dependencies]] name = "wolfi" source = "../wolfi" ``` A `source` may be a local path (`../wolfi`) or a remote reference of the form `[proto://]host/repo[/subpath][@version]`, such as `github.com/shykes/daggerverse/hello@v0.3.0`. Manage dependencies by adding, updating, or removing `[[dependencies]]` entries in `dagger-module.toml`. After changing dependencies, [regenerate bindings](#regenerate-bindings-and-generated-files) so the new module's functions appear in `dagger_sdk/`. ## Regenerate bindings and generated files {#regenerate-bindings-and-generated-files} An Elixir module uses generated code alongside your handwritten `lib/`: - `dagger_sdk/`, a vendored copy of the Elixir SDK library (`lib/dagger/*.ex`, `mix.exs`, `mix.lock`, `.formatter.exs`, `LICENSE`). Its `lib/dagger/gen/` is replaced by bindings generated from your engine's schema: all core types (`Dagger.Container`, `Dagger.Directory`, …), every dependency's objects and functions, and `Dagger.Client`. You do not edit these files by hand, but you do commit them. Dagger does not regenerate them when it loads a module configured with `dagger-module.toml`, including a module installed from Git. Because `mix.exs` depends on `{:dagger, path: "./dagger_sdk"}`, the module does not compile without them. Mark the directory as generated so code review skips it: ```text title=".gitattributes" /dagger_sdk/** linguist-generated ``` Regenerate them whenever you change your module's functions, bump the engine version, or add or remove a dependency. Use `dagger generate`, which returns a changeset: ```shell # Review the regenerated files, then apply dagger generate ``` `dagger generate` discovers and runs every generator in the workspace. For modules registered under the Elixir SDK, that includes binding regeneration. To regenerate a single module, call the SDK's `mod` function directly: ```shell dagger api call elixir-sdk mod --path .dagger/modules/my-module generate ``` Apply and commit the resulting changeset. This keeps the generated client in sync with the functions and dependencies available to your module. :::note A `.dagger-elixir-sdk-skip-generate` marker in a module or one of its ancestors skips Elixir binding regeneration for that module. ::: :::tip Generated SDK bindings are not in `.gitignore`; commit them with your module. Do ignore Mix build output, `_build/` and `deps/`, which the runtime recreates inside its container. ::: If `dagger_sdk/mix.exs` is missing when the engine loads the module, the runtime vendors the SDK and generates the bindings on the fly so the module still loads. That fallback never writes to your workspace; run `dagger generate` to write the files. ## Engine version Each module declares the Dagger engine version it requires in the `engineVersion` field of `dagger-module.toml`. Set it to a concrete version. Bumping the engine version usually means the generated bindings should change too, so follow with `dagger generate`. ## Checks, cache policy, directives, and ignore patterns The three first-class function types are checks, generators, and services. A useful, reusable module provides at least one of them, so that the platform verbs (`dagger check`, `dagger generate`, `dagger up`) have something to run. See the [SDKs overview](./index.mdx) for the full treatment. In Elixir, you mark a function with a module attribute placed directly above its `defn`: | Attribute | Return type | Run by | Purpose | |---|---|---|---| | `@check true` | `Dagger.Void.t()` (or `Dagger.Container.t()`) | `dagger check` | validate the project (test/lint/scan) | :::note The Elixir SDK supports checks and cache policies only. It has no equivalent of the `@generate` and `@up` markers, so an Elixir module cannot expose generators to `dagger generate` or services to `dagger up`. A `defn` may still return `Dagger.Changeset.t()` or `Dagger.Service.t()` for callers to use explicitly. If you need generators or `up` services, write that module in [Go](./go.mdx), [Python](./python.mdx), [TypeScript](./typescript.mdx), or [Dang](./dang.mdx). This is the main limitation to weigh before picking Elixir for a module. ::: The Elixir-specific pieces are: ### Attributes and options Elixir modules use module attributes to add Dagger metadata that typespecs can't express: | Directive | Placement | Meaning | |---|---|---| | `@check true` | above a `defn` | mark the function as a [check](#checks) | | `@cache :never` / `@cache :per_session` / `@cache ttl: "10m"` | above a `defn` | set the function's [cache policy](#cache-policy) | | `@deprecated "reason"` | above a `defn` | mark the function deprecated | Each attribute applies to the next `defn` only; the SDK resets it once the function is declared. Per-argument metadata (`doc:`, `default:`, `default_path:`, `ignore:`, `deprecated:`) goes in [argument options](#documentation) instead. ### Ignore patterns A module reads the user's project through a `Dagger.Workspace.t()` argument (see [Workspace inputs](#workspace-inputs)). To filter what gets pulled, use the `exclude:` option when reading a workspace directory. Filter tightly. The less you load, the fewer cache invalidations you get: ```elixir defn init(ws: Dagger.Workspace.t()) :: MyModule.t() do %__MODULE__{ source: Dagger.Workspace.directory(ws, "/", exclude: ["_build", "deps", ".git"]) } end ``` For a path-defaulted `Dagger.Directory.t()` argument, the `ignore:` option does the same job (see [the argument options](#documentation)). ### Checks {#checks} Set `@check true` on a function to make it a check, a validation function (test, lint, scan) that takes no caller arguments. `dagger check` discovers and runs every check a module exposes. A check fails when it raises or returns `{:error, reason}`, or when it returns a `Dagger.Container.t()` whose execution exits non-zero. ```elixir @doc """ Lint the project. """ @check true defn lint(self) :: Dagger.Void.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("elixir:1.19-alpine") |> Dagger.Container.with_mounted_directory("/src", self.source) |> Dagger.Container.with_workdir("/src") |> Dagger.Container.with_exec(["mix", "format", "--check-formatted"]) |> Dagger.Container.sync() |> case do {:ok, _} -> :ok error -> error end end @doc """ A check can also return a container; a non-zero exit fails the check. """ @check true defn build() :: Dagger.Container.t() do dag() |> Dagger.Client.container() |> Dagger.Container.from("alpine:3") |> Dagger.Container.with_exec(["true"]) end ``` You can also declare checks on custom object types to group them, for example a `Test` object with `lint` and `unit` checks. ### Cache policy {#cache-policy} By default Dagger caches function results keyed by their inputs. Tune it per function with the `@cache` attribute: ```elixir # Re-run at most every 10 minutes @cache ttl: "10m" defn latest_release() :: String.t() do # ... end # Cache for the current session only @cache :per_session defn session_id() :: String.t() do # ... end # Never cache (always re-execute) @cache :never defn current_time() :: String.t() do # ... end ``` A cache hit skips the function entirely. A miss runs it, but individual operations inside may still hit the layer cache. ## Testing Elixir modules Because an Elixir module is an ordinary Mix project, you can test it two ways, and the two cover different things. ### Idiomatic ExUnit tests You can unit-test pure Elixir logic, such as parsing a report or formatting a summary, with ExUnit and no engine involved. Add a `test/test_helper.exs` containing `ExUnit.start()` and write tests against your helpers: ```elixir title="test/my_module_test.exs" defmodule MyModuleTest do use ExUnit.Case, async: true test "summarizes an issue" do issue = %{file: "app/main.ex", line: 12, message: "undefined name"} assert MyModule.Report.summary(issue) == "app/main.ex:12 error: undefined name" end end ``` Run them like any Mix test suite (these don't require the engine, but they do need the generated `dagger_sdk/` so the project compiles): ```shell mix deps.get mix test ``` ### Functional tests via checks For behavior that exercises containers and the Dagger API, write functions in your module and invoke them, or model them as checks so they run under `dagger check`. A check that builds, lints, or tests your project doubles as both a CI gate and a smoke test: ```shell # Smoke test: does it build? dagger api call build # Run all checks dagger check # Run generators and confirm there's no drift dagger check --generate ``` ### In CI Run `dagger check` in CI to run every check the module exposes. The heavy lifting happens in content-addressed containers, so the same command behaves the same on a laptop and on a CI runner, with full caching: ```yaml title=".github/workflows/ci.yml" jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dagger/dagger-for-github@v6 with: verb: check ``` ## IDE and Mix setup A generated module is a self-contained Mix project. `mix.exs` depends on the vendored SDK by path, so the only thing local compilation and editor support (ElixirLS, Lexical, and similar) need is that `dagger_sdk/` exists. Run `dagger generate` if it is missing, then fetch the SDK's own dependencies and compile: ```shell mix deps.get mix compile ``` The template seeds `.formatter.exs` with `import_deps: [:dagger]`, so `mix format` understands `defn`. Keep Mix build output out of Git and out of the runtime container by adding it to `.gitignore`: ```text title=".gitignore" /_build /deps /cover /doc /erl_crash.dump /*.ez ``` **Elixir version.** The template requires `elixir: "~> 1.17"`. The runtime compiles your module in the Elixir SDK's pinned `elixir:*-otp-28-alpine` image with `MIX_ENV=prod`. The runtime module fixes the exact image, and `dagger-module.toml` cannot change it. **Third-party dependencies.** Add Hex packages to `deps/0` in `mix.exs` as usual. The runtime runs `mix deps.get --only prod` before compiling, so it fetches any dependency you add inside the runtime container: ```elixir title="mix.exs" defp deps do [ {:dagger, path: "./dagger_sdk"}, {:req, "~> 0.5"} ] end ``` **HTTP client.** The SDK talks to the engine through Erlang's built-in `httpc` by default. To use [Req](https://hex.pm/packages/req) instead, add it as a dependency and configure the client: ```elixir title="config/config.exs" import Config config :dagger, client: Dagger.Core.GraphQLClient.Req ``` ## Packaging and release You distribute an Elixir SDK module as a Git repository. There is no build artifact to publish. Consumers fetch the source by reference, and the runtime compiles it from the committed `dagger_sdk/` and `lib/`. Recommended release checklist: 1. **Pin the engine version.** Set `engineVersion` in `dagger-module.toml` to the oldest engine version your module supports. 2. **Commit the generated bindings.** Run `dagger generate`, review the changes, and commit `dagger_sdk/` with your module. 3. **Version with Git tags.** Tag a release (for example, `v1.2.0`) and push it. Consumers can pin that version with `@v1.2.0`. Before publishing, run `dagger check --generate` to confirm that the committed bindings are up to date. Consumers can install your module into a workspace with: ```shell dagger module install github.com/you/your-module@v1.2.0 ``` To add it as a dependency of another module, add a `[[dependencies]]` entry to that module's `dagger-module.toml`, then run `dagger generate`. A module reference follows `[proto://]host/repo[/subpath][@version]`. The version may be a tag, branch, or commit, and Dagger resolves it over HTTPS or SSH depending on the authentication available. ## Troubleshooting **`dagger init` / `dagger develop` not found.** Install the Elixir SDK with `dagger module install dagger.io/sdk/elixir`, scaffold with `dagger module init elixir --name `, and regenerate with `dagger generate`. **Nothing was written after `dagger module init`.** The command returns a changeset. Review and accept it, or rerun with `-y` to apply without prompting. **`mix deps.get` fails on `{:dagger, path: "./dagger_sdk"}`.** The generated SDK is missing. Run `dagger generate` and apply the changeset, then retry. **A new function or dependency doesn't show up.** Run `dagger generate`. The API you can call comes from the bindings in `dagger_sdk/`, which must match your code and `dagger-module.toml`. **`dagger generate` does not regenerate bindings.** A `.dagger-elixir-sdk-skip-generate` marker is likely present in the module or one of its ancestors. **`type ... is not supported` at compile time.** `defn` only accepts the typespecs listed under [Arguments and return values](#arguments-and-return-values). Use `String.t()` rather than `String`, `[T]` for lists, and `T | nil` for optional values. **`Cannot find module MyModule` from the runtime.** The runtime derives the entrypoint module name from the Dagger module name (`my-module` → `MyModule`). Rename the `defmodule` to match, or keep the names in step when renaming the module. **Engine version mismatch.** Align the module's `engineVersion` in `dagger-module.toml`, then regenerate. ## Next steps - [SDKs overview](./index.mdx), the platform concepts that apply to every SDK - [Types](../api/index.mdx) - [Generating code](../../using/generating.mdx) --- # Go SDK URL: https://docs.dagger.io/reference/sdks/go # Go SDK The Go SDK lets you write Dagger modules in Go. You define plain Go structs and methods; the SDK turns them into Dagger objects and functions that anyone can call from the CLI, from another module, or over the API. In return, your module gets a generated, fully-typed Go client (`dag`) for the entire Dagger API: containers, directories, files, secrets, services, and every module you depend on. This page is a standalone guide to the Go SDK. It assumes you already understand the platform concepts covered in the [SDKs overview](./index.mdx): - [Types](../api/index.mdx) explains how SDK types map to the Dagger API - [Generating code](../../using/generating.mdx) explains how generators and tooling return diffs for you to apply A useful, reusable module provides at least one of the three first-class function types: a **check**, a **generator**, or a **service**. See [Checks, generators, services, directives, and ignore patterns](#checks-generators-services-directives-and-ignore-patterns) for the Go syntax. The Go SDK is itself a Dagger module, `dagger.io/sdk/go`. Install it into your workspace once, then scaffold, generate, and maintain Go modules with Dagger's module commands: ```shell # Install the Go SDK into your workspace (once) dagger module install dagger.io/sdk/go # Create a Go module dagger module init go --name my-module ``` ## Create a module :::note Run these commands from inside a Git repository. That's where the new module is created. ::: Install the Go SDK into your workspace, then create a new module: ```shell dagger module install dagger.io/sdk/go dagger module init go --name my-module ``` `dagger module init` returns a [changeset](../../using/generating.mdx), a structured diff of the files to create. Dagger shows you the changeset to review before anything is written to disk. :::tip If the repository does not have a `dagger.toml` yet, use `dagger module install --here dagger.io/sdk/go` to create it in the current directory. ::: ### Where the module is created By default, `dagger module init go` places the new module beside the `dagger.toml` it is editing: ``` /.dagger/modules/ ``` That is the workspace root unless the config lives in a subdirectory, as it does when several projects share one repository. Pass `--path` to choose a different location. It is relative to your current directory, like any other path you type; a leading `/` means the workspace root. ```shell dagger module init go --name my-module --path ci # ./ci dagger module init go --name my-module --path /ci # /ci ``` A custom path is registered as a module authored by the SDK, but is not automatically installed as a callable workspace module. Use `dagger module install ./ci` to install it too. Pass `--template legacy` to use the Go SDK's legacy starter: ```shell dagger module init go --name my-module --template legacy ``` List the Go SDK's module initialization options with: ```shell dagger module init go --help ``` ### Resulting file layout Once initialized and generated, a Go module looks like this: ```text my-module/ ├── dagger-module.toml ├── go.mod ├── go.sum ├── main.go # your code ├── dagger.gen.go # generated: top-level helpers ├── .gitattributes # marks generated files for linguist ├── .gitignore # ignores local-only files such as .env └── internal/ └── dagger/ # generated: the typed Dagger client ├── dagger.gen.go └── my-module.gen.go ``` :::note `dagger module init` writes `dagger-module.toml` and `main.go`, updates the workspace config, and runs the Go SDK's generator for the new module, so `go.mod`, `go.sum`, and the generated Go files shown above are part of the same changeset. Commit the generated Go files along with the rest of the module. Dagger needs the generated client to load the module, both from your local checkout and from a Git reference. ::: The module config records the runtime separately from the SDK that authors it: ```toml title="dagger-module.toml" template name = "my-module" engineVersion = "v{{ version }}" [runtime] source = "go" ``` `runtime.source = "go"` tells the engine to use the Go runtime. The workspace's `dagger.toml` records the SDK under `sdks.go` and records the module path as one of its scopes. That scope is how Go binding generation discovers the module. The generated files in `dagger.gen.go` and `internal/` are not handwritten; see [Regenerate bindings](#regenerate-bindings-and-generated-files). ## Define objects and functions A Go module is an ordinary Go package named `main`. The **main object** is a struct whose name matches your module (Dagger PascalCases the module name): a module named `my-module` has a `MyModule` struct. Every **exported method** on that struct becomes a callable Dagger Function. ```go title="main.go" // A simple example module to say hello. // Further documentation for the module here. package main import ( "fmt" "strings" ) type MyModule struct{} // Return a greeting. func (m *MyModule) Hello( // Who to greet name string, // The greeting to display greeting string, ) string { return fmt.Sprintf("%s, %s!", greeting, name) } // Return a loud greeting. func (m *MyModule) LoudHello( // Who to greet name string, // The greeting to display greeting string, ) string { out := fmt.Sprintf("%s, %s!", greeting, name) return strings.ToUpper(out) } ``` Key rules: - The package is always `package main`. - Method receivers may be value (`func (m MyModule)`) or pointer (`func (m *MyModule)`). Be consistent within a type. - **Exported** (capitalized) methods become Dagger Functions. Unexported methods are private Go helpers, invisible to callers. - The first parameter may be a `context.Context`; it does not appear as a Dagger argument and is supplied by the runtime. - A method may return an error as its last value; a non-nil error fails the function. Call your functions exactly like any other module, from the directory that contains `dagger-module.toml` (or with `-m `): ```shell dagger api call hello --name=World --greeting=Hello # Hello, World! dagger api call loud-hello --name=World --greeting=Hello # HELLO, WORLD! ``` Go method and argument names are converted to kebab-case on the CLI (`LoudHello` → `loud-hello`, `name` → `--name`). ### The constructor If you define a `New` function in the same package, it becomes the module's **constructor**. Its arguments become arguments of the main object, and its return value is the initialized main object. Use it for module-wide configuration and shared state. A common pattern is to accept a `*dagger.Workspace` so the module can read the project it runs against (see [Workspace inputs](#workspace-inputs)). Dagger auto-populates it from the current workspace, and content is pulled lazily, so you store the project directory once and reuse it: ```go title="main.go" package main import ( "context" "dagger/my-module/internal/dagger" ) type MyModule struct { Source *dagger.Directory } func New( // The current workspace, auto-populated by Dagger. ws *dagger.Workspace, ) *MyModule { return &MyModule{ // Read the workspace root; nothing is uploaded until a function uses it. Source: ws.Directory("/"), } } func (m *MyModule) Foo(ctx context.Context) ([]string, error) { return dag.Container(). From("alpine:latest"). WithMountedDirectory("/app", m.Source). Directory("/app"). Entries(ctx) } ``` Exported struct fields (like `Source` above) are part of the object's state and are serialized between functions in a chain. To keep a field present in Go but hidden from the API, mark it private to Dagger with a `+private` comment: ```go type LintRun struct { // +private Source *dagger.Directory } ``` ## Arguments and return values Dagger derives a function's argument and return types from the Go signature. The mapping is: | Go type | Dagger type | |---|---| | `string` | `String` | | `int` | `Int` | | `float64` | `Float` | | `bool` | `Boolean` | | `[]T` | `[T]` (list) | | `*dagger.Directory` | `Directory` | | `*dagger.File` | `File` | | `*dagger.Container` | `Container` | | `*dagger.Secret` | `Secret` | | `*dagger.Service` | `Service` | | a custom struct `T` | object `T` | | a custom `string` type with consts | enum | ### Documentation Doc comments become API documentation, surfaced in `dagger api functions` and `dagger api call --help`. A comment directly above a method documents the function; a comment directly above a parameter documents that argument; a comment above the `package main` declaration documents the whole module. ```go // Return a greeting. func (m *MyModule) Hello( // Who to greet name string, ) string { return fmt.Sprintf("Hello, %s!", name) } ``` ### Optional and default arguments Dagger arguments are required by default. Make one optional, or give it a default, with a magic comment on the parameter: ```go title="optional" func (m *MyModule) Hello( ctx context.Context, // +optional name string, ) (string, error) { if name != "" { return fmt.Sprintf("Hello, %s", name), nil } return "Hello, world", nil } ``` ```go title="default value" func (m *MyModule) Hello( ctx context.Context, // +default="world" name string, ) (string, error) { return fmt.Sprintf("Hello, %s", name), nil } ``` - `+optional` makes the argument optional. For scalar types it defaults to the Go zero value; for pointer types (`*dagger.Directory`, etc.) it defaults to `nil`, so you can detect "not passed." - `+default="..."` makes the argument optional and supplies a default value when the caller omits it. ### Nullability Use a pointer to a core type (e.g. `*dagger.Secret`) when an argument or return value may be absent. A `nil` pointer corresponds to a null/omitted value. Non-pointer scalars (`string`, `int`, `bool`) are always present. ### Enums Model a closed set of string values as a named `string` type with a block of typed constants. Dagger turns it into an enum and validates inputs: ```go title="main.go" package main import "context" type MyModule struct{} // Vulnerability severity levels type Severity string const ( // Undetermined risk; analyze further. Unknown Severity = "UNKNOWN" // Minimal risk; routine fix. Low Severity = "LOW" // Moderate risk; timely fix. Medium Severity = "MEDIUM" // Serious risk; quick fix needed. High Severity = "HIGH" // Severe risk; immediate action. Critical Severity = "CRITICAL" ) func (m *MyModule) Scan(ctx context.Context, ref string, severity Severity) (string, error) { return dag.Container(). From("aquasec/trivy:0.50.4"). WithExec([]string{ "trivy", "image", "--severity=" + string(severity), ref, }).Stdout(ctx) } ``` Passing a value outside the enum produces a clear error listing the allowed choices: ```shell dagger api call scan --ref=alpine:latest --severity=FOO # Error: value should be one of UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL ``` ### Custom object types Return a struct to expose a custom object. Exported fields become readable values, and exported methods on the type become chainable functions. Dagger prefixes custom type names with the module name in the API schema (e.g. `MyModuleOrganization`) to avoid collisions: ```go title="main.go" package main import "dagger/my-module/internal/dagger" type MyModule struct{} func (module *MyModule) DaggerOrganization() *Organization { url := "https://github.com/dagger" return &Organization{ URL: url, Repositories: []*dagger.GitRepository{dag.Git(url + "/dagger")}, Members: []*Account{ {"jane", "jane@example.com"}, {"john", "john@example.com"}, }, } } type Organization struct { URL string Repositories []*dagger.GitRepository Members []*Account } type Account struct { Username string Email string } func (account *Account) URL() string { return "https://github.com/" + account.Username } ``` This enables chaining on the CLI and API: ```shell dagger api call dagger-organization members url ``` ### Interfaces Interfaces let your module accept arbitrary objects from other modules without depending on their concrete types. Declare a Go interface that embeds `DaggerObject` and lists the functions you need; any object that provides matching functions can be passed in: ```go title="main.go" package main import ( "context" "dagger/my-module/internal/dagger" ) type MyModule struct{} type Fooer interface { DaggerObject Foo(ctx context.Context, bar int) (string, error) } func (m *MyModule) Foo(ctx context.Context, fooer Fooer) (string, error) { return fooer.Foo(ctx, 42) } ``` The `DaggerObject` marker (provided by the generated client) is required so Dagger knows this interface describes a Dagger object rather than a plain Go interface. ## Working with core Dagger types The generated client exposes the entire Dagger API through a package-level variable named `dag`. You use it to build containers, mount directories and files, handle secrets, and run services. Core types live in the `internal/dagger` package, imported as `dagger`. ### Containers Each builder method returns a new, immutable `*dagger.Container`. Nothing mutates in place, and every step is content-addressed and cached automatically. ```go package main import ( "dagger/my-module/internal/dagger" ) type MyModule struct{} // Build and return a container func (m *MyModule) Build(source *dagger.Directory) *dagger.Container { return dag.Container(). From("node:20"). WithDirectory("/app", source). WithWorkdir("/app"). WithExec([]string{"npm", "install"}). WithExec([]string{"npm", "run", "build"}) } ``` ### Directories and files `*dagger.Directory` and `*dagger.File` are first-class, "just-in-time" artifacts you can accept as arguments, produce as return values, mount into containers, and export to the host. Some builder methods take an options struct for less-common parameters. For example, `WithDirectory` accepts `dagger.ContainerWithDirectoryOpts`: ```go func (m *MyModule) CopyDirectoryWithExclusions( ctx context.Context, // Source directory source *dagger.Directory, // Exclusion pattern // +optional exclude []string, ) *dagger.Container { return dag.Container(). From("alpine:latest"). WithDirectory("/src", source, dagger.ContainerWithDirectoryOpts{Exclude: exclude}) } ``` The pattern is consistent. Required parameters are positional Go arguments, and optional ones live in a generated `XxxOpts` struct. ### Workspace inputs {#workspace-inputs} When a module needs to read the user's project, it takes a `*dagger.Workspace` argument, almost always on the **constructor**. That is how it reaches the source tree, config files, and lockfiles. You don't pass it. Dagger **auto-populates** it from the current workspace, and nothing is uploaded up front. Project content is pulled **lazily, on demand** when a function actually reads a path, so a module can declare access to the whole workspace cheaply and only pay for what it touches. ```go title="main.go" package main import ( "dagger/my-module/internal/dagger" ) type MyModule struct { Source *dagger.Directory } func New( // The current workspace, auto-populated by Dagger. ws *dagger.Workspace, ) *MyModule { return &MyModule{ // Pull the workspace root as a Directory (lazy, no upload yet). Source: ws.Directory("/"), } } // Functions reuse the pulled Directory like any other. func (m *MyModule) Build() *dagger.Container { return dag.Container(). From("node:20"). WithDirectory("/app", m.Source). WithWorkdir("/app"). WithExec([]string{"npm", "install"}). WithExec([]string{"npm", "run", "build"}) } ``` The `Workspace` client type exposes accessors for reading project content: | Accessor | Signature | Returns | |---|---|---| | `Directory` | `ws.Directory(path string, opts ...dagger.WorkspaceDirectoryOpts) *dagger.Directory` | a `Directory` at `path` | | `File` | `ws.File(path string) *dagger.File` | a `File` at `path` | | `FindUp` | `ws.FindUp(ctx, name string, opts ...dagger.WorkspaceFindUpOpts) (string, error)` | the workspace path of `name`, searching upward | **Path resolution.** A **relative** path resolves from the workspace's current working directory; an **absolute** path (starting with `/`) resolves from the workspace root (boundary). So `ws.Directory("/")` is the whole project root, while `ws.Directory(".")` is wherever the user invoked Dagger from. **Excluding files.** `Directory` takes a `dagger.WorkspaceDirectoryOpts` options struct to filter what gets pulled. Tight filters matter for cache efficiency, since loading less means fewer cache invalidations: ```go func New(ws *dagger.Workspace) *MyModule { return &MyModule{ Source: ws.Directory("/", dagger.WorkspaceDirectoryOpts{ Exclude: []string{"node_modules", ".git", "dist"}, // Include: []string{"app/", "package.*"}, // allowlist instead // Gitignore: true, // apply .gitignore rules }), } } ``` `FindUp` walks up from a start path (relative paths resolve from the workspace cwd; pass `dagger.WorkspaceFindUpOpts{From: "..."}` to change it) and returns the absolute workspace path of the first match, stopping at the workspace boundary. Use it to locate a project root marker such as `package.json` or `go.mod`. :::tip To use the current workspace, declare a `*dagger.Workspace` argument on the module constructor or function. Dagger injects it automatically and omits it from the CLI arguments. ::: ### Secrets Accept sensitive values as `*dagger.Secret`, never as plain strings. Dagger scrubs secret plaintext from logs, caches, and crash reports: ```go package main import ( "context" "dagger/my-module/internal/dagger" ) type MyModule struct{} // Query the GitHub API func (m *MyModule) GithubApi( ctx context.Context, // GitHub API token token *dagger.Secret, ) (string, error) { return dag.Container(). From("alpine:3.17"). WithSecretVariable("GITHUB_API_TOKEN", token). WithExec([]string{"apk", "add", "curl"}). WithExec([]string{"sh", "-c", `curl "https://api.github.com/repos/dagger/dagger/issues" --header "Authorization: Bearer $GITHUB_API_TOKEN"`}). Stdout(ctx) } ``` Callers supply secrets through providers on the CLI: ```shell dagger api call github-api --token=env:GITHUB_TOKEN # environment variable dagger api call github-api --token=file:./token.txt # file dagger api call github-api --token=cmd:"gh auth token" # command output dagger api call github-api --token=op://vault/item/field # 1Password ``` ### Services Return `*dagger.Service` to expose a long-running service, and bind it into other containers with `WithServiceBinding`. Services are content-addressed, so a given definition always gets the same hostname, with no port conflicts: ```go package main import ( "context" "dagger/my-module/internal/dagger" ) type MyModule struct{} // Start and return an HTTP service func (m *MyModule) HttpService() *dagger.Service { return dag.Container(). From("python"). WithWorkdir("/srv"). WithNewFile("index.html", "Hello, world!"). WithExposedPort(8080). AsService(dagger.ContainerAsServiceOpts{Args: []string{"python", "-m", "http.server", "8080"}}) } // Send a request to an HTTP service and return the response func (m *MyModule) Get(ctx context.Context) (string, error) { return dag.Container(). From("alpine"). WithServiceBinding("www", m.HttpService()). WithExec([]string{"wget", "-O-", "http://www:8080"}). Stdout(ctx) } ``` ### A larger example Real modules combine these pieces. This is adapted from Dagger's own `ruff` module (a Go SDK module in the Dagger repo). Note custom types, chaining, error returns, and the use of `dag.CurrentModule()` to reach the module's own source: ```go package main import ( "context" "encoding/json" "errors" "fmt" "strings" "github.com/dagger/dagger/modules/ruff/internal/dagger" ) // Ruff is a fast Python linter implemented in Rust type Ruff struct{} // Lint a Python codebase func (ruff Ruff) Lint( // The Python source directory to lint source *dagger.Directory, ) *LintRun { return &LintRun{Source: source} } // The result of running the Ruff lint tool type LintRun struct { // +private Source *dagger.Directory } // Return a JSON report file for this run func (run LintRun) Report() *dagger.File { cmd := []string{"/ruff", "check", "--exit-zero", "--output-format", "json", "."} return dag. CurrentModule(). Source(). Directory("build"). DockerBuild(). WithMountedDirectory("", run.Source). WithExec(cmd, dagger.ContainerWithExecOpts{RedirectStdout: "ruff-report.json"}). File("ruff-report.json") } // Return an error if the run reported any issues func (run LintRun) Assert(ctx context.Context) error { contents, err := run.Report().Contents(ctx) if err != nil { return err } var issues []struct { Message string `json:"message"` } if err := json.Unmarshal([]byte(contents), &issues); err != nil { return err } if len(issues) > 0 { var lines []string for _, i := range issues { lines = append(lines, " - "+i.Message) } return errors.New(fmt.Sprintf("%d issues\n%s", len(issues), strings.Join(lines, "\n"))) } return nil } ``` ## Module dependencies A module can depend on other Dagger modules and call them through `dag`, for example `dag.Ruff()` once `ruff` is a dependency. Dependencies are recorded in `dagger-module.toml` under `dependencies`: ```toml title="dagger-module.toml" template name = "dev" engineVersion = "v{{ version }}" [runtime] source = "go" [[dependencies]] name = "go" source = "../../modules/go" [[dependencies]] name = "wolfi" source = "../wolfi" ``` A `source` may be a local path (`../wolfi`) or a remote reference of the form `[proto://]host/repo[/subpath][@version]`, e.g. `github.com/shykes/daggerverse/hello@v0.3.0`. Manage dependencies from the module directory with `dagger module client`: ```shell dagger module client add ../wolfi --sdk=go dagger module client list --sdk=go dagger module client update --sdk=go dagger module client rm --sdk=go ``` Use the exact `TARGET` from `client list` for removal. Client add and remove commands update targets in `dagger.toml`. Client update refreshes `dagger.lock`. All three regenerate the module. The SDK writes the corresponding `[[dependencies]]` entries shown above. Manual dependency edits in `dagger-module.toml` are replaced by generation. After changing dependencies, [regenerate bindings](#regenerate-bindings-and-generated-files) so the new module's functions appear on `dag`. ## Regenerate bindings and generated files {#regenerate-bindings-and-generated-files} A Go module uses generated code alongside your handwritten `main.go`: - `dagger.gen.go` holds top-level helpers - `internal/dagger/` is the typed Dagger client, including `dag`, all core types (`Container`, `Directory`, …), every dependency's functions, and the `XxxOpts` option structs - `internal/telemetry/` holds supporting generated code when required You do not edit these files by hand, but you do commit them. Dagger does not regenerate them when it loads a module configured with `dagger-module.toml`, including a module installed from Git. If required generated files are missing, the module cannot load. The generated `.gitattributes` marks them as `linguist-generated`: ```text title=".gitattributes" /dagger.gen.go linguist-generated /internal/dagger/** linguist-generated /internal/telemetry/** linguist-generated ``` Regenerate them whenever you change your module's functions, bump the engine version, or add or remove a dependency. Use `dagger generate`, which returns a changeset: ```shell # Review the regenerated files, then apply dagger generate ``` `dagger generate` discovers and runs every generator in the workspace; for modules registered under the Go SDK, that includes binding regeneration. Apply and commit the resulting changeset. This keeps the generated client in sync with the functions and dependencies available to your module. :::tip Generated SDK bindings are not added to `.gitignore`; commit them with your module. By default, Dagger still adds local-only files such as `.env` to `.gitignore`. Set `codegen.automaticGitignore = false` only if you want to manage all `.gitignore` entries yourself. ::: ## Engine version Each module declares its required engine version in the `engineVersion` field of `dagger-module.toml`. The Go SDK writes this field during generation. A manual edit can be replaced by the next generation. Bumping the engine version usually means the generated bindings should change too, so follow with `dagger generate`. ## Checks, generators, services, directives, and ignore patterns A useful, reusable module provides at least one of the three first-class function types: a **check**, a **generator**, or a **service**. That gives the platform verbs (`dagger check`, `dagger generate`, `dagger up`) something to run. These work the same in Go as in any SDK; see the [SDKs overview](./index.mdx) for the full treatment. In Go, you mark each one with a doc-comment **pragma** on the function: | Pragma | Return type | Run by | Purpose | |---|---|---|---| | `// +check` | `error` (or `*dagger.Container`) | `dagger check` | validate the project (test/lint/scan) | | `// +generate` | `*dagger.Changeset` | `dagger generate` | produce a diff to apply to the workspace | | `// +up` | `*dagger.Service` | `dagger up` | start a long-running service | The parts specific to Go are the `// +directive` comments and the `Exclude` option on workspace directories: ### Magic comment directives Go modules use `// +directive` comments to add Dagger metadata that Go's type system can't express: | Directive | Placement | Meaning | |---|---|---| | `// +optional` | above an argument | argument is optional | | `// +default="x"` | above an argument | optional with a default value | | `// +private` | above a struct field | keep the field out of the API | | `// +check` | above a method | mark the function as a [check](#checks) | | `// +generate` | above a method | mark the function as a [generator](#generators) | | `// +up` | above a method | mark the function as a [service](#up-services) | ### Ignore patterns A module reads the user's project through a `*dagger.Workspace` argument (see [Workspace inputs](#workspace-inputs)), not a path-defaulted `*dagger.Directory`. To filter what gets pulled, use the **exclude option** when reading a workspace directory. Tight filters are essential for cache efficiency (load less → fewer cache invalidations): ```go func New(ws *dagger.Workspace) *MyModule { return &MyModule{ Source: ws.Directory("/", dagger.WorkspaceDirectoryOpts{ Exclude: []string{"node_modules", ".git", "dist"}, }), } } ``` ### Checks {#checks} Mark a function with the `// +check` pragma to make it a **check**, a validation function (test, lint, scan) that takes no caller arguments. `dagger check` discovers and runs every check a module exposes. A check fails when it returns a non-nil `error`, or when it returns a `*dagger.Container` whose execution exits non-zero. ```go // Lint the project. // // +check func (m *MyModule) Lint(ctx context.Context) error { _, err := dag.Container(). From("golangci/golangci-lint:latest"). WithMountedDirectory("/src", m.Source). WithWorkdir("/src"). WithExec([]string{"golangci-lint", "run"}). Sync(ctx) return err } // A check can also return a container; a non-zero exit fails the check. // // +check func (m *MyModule) Build() *dagger.Container { return dag.Container().From("alpine:3").WithExec([]string{"true"}) } ``` The pragma may be placed on its own line within the doc comment (a blank `//` line separates the human-readable description from the pragma, as shown above). Checks can also be declared on custom object types, so you can group them (for example a `Test` object with `Lint` and `Unit` checks). ### Generators {#generators} Mark a function with the `// +generate` pragma to make it a **generator**. It returns a `*dagger.Changeset`. It runs a tool, captures the resulting directory, and diffs it against the source. `dagger generate` discovers and runs every generator and presents the combined [changeset](../../using/generating.mdx) for you to review and apply. ```go // Format the source. // // +generate func (m *MyModule) Format() *dagger.Changeset { formatted := dag.Container(). From("golang:latest"). WithMountedDirectory("/src", m.Source). WithWorkdir("/src"). WithExec([]string{"gofmt", "-w", "."}). Directory("/src") return formatted.Changes(m.Source) } ``` :::note This author-written `// +generate` function is distinct from the SDK's client binding generator. The user-facing `dagger generate` command runs both kinds of generator. ::: See [Generating code](../../using/generating.mdx). ### Services {#up-services} Mark a function with the `// +up` pragma to make it a **service**. It returns a `*dagger.Service`, and `dagger up` discovers and starts every service the module exposes, exposing their ports on the host. ```go // Run the web server. // // +up func (m *MyModule) Web() *dagger.Service { return dag.Container(). From("nginx:alpine"). WithExposedPort(80). AsService() } ``` Like checks, `// +up` services can also be declared on custom object types so you can group related services (for example an `Infra` object exposing a `Database` service). This is the same `*dagger.Service` type described under [Services](#services) above; the `// +up` pragma is what makes a service function runnable directly via `dagger up`. ## Testing Go modules Because a Go module is ordinary Go, you have two complementary testing strategies. ### Idiomatic Go tests Functions that contain pure Go logic can be unit-tested with the standard `testing` package, with no engine involved. Parsing reports, formatting summaries, and computing counts are all candidates: ```go title="main_test.go" package main import "testing" func TestIssueSummary(t *testing.T) { issue := Issue{ AbsFilename: "/src/app/main.py", Message: "undefined name", Location: Location{Row: 12}, } got := issue.Summary() want := "app/main.py:12 error: undefined name" if got != want { t.Fatalf("got %q, want %q", got, want) } } ``` Run them like any Go test (these don't require the engine): ```shell go test ./... ``` ### Functional tests via checks For behavior that exercises containers and the Dagger API, write functions in your module and invoke them, or model them as checks so they run under `dagger check`. A check that builds, lints, or tests your project doubles as both a CI gate and a smoke test: ```shell # Smoke test: does it build? dagger api call build # Run all checks dagger check # Run generators and confirm there's no drift dagger check --generate ``` ### In CI Run `dagger check` in CI to execute every check the module exposes. Because the heavy lifting happens in content-addressed containers, the same command runs identically on a laptop and on a CI runner, with full caching: ```yaml title=".github/workflows/ci.yml" jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dagger/dagger-for-github@v6 with: verb: check ``` ## IDE setup, `go.mod` and `go.work` `dagger module init go` creates a `go.mod` and `go.sum` inside your Dagger module. These files tell Go which dependencies the generated client needs. If the Dagger module lives inside a larger Go project, the additional `go.mod` can make an editor choose the wrong Go module for a file. Two options: 1. **Reuse a parent `go.mod`.** If a parent directory already has a `go.mod` you want to use, delete the module's generated `go.mod`/`go.sum`; Dagger will fall back to the parent. Avoid this if your module pulls in dependencies irrelevant to the rest of the project, since most consumers prefer a narrower dependency set. 2. **Use a Go workspace.** Keep the generated `go.mod` and stitch everything together with [`go.work`](https://go.dev/doc/tutorial/workspaces): ```shell # in the root of your repository go work init go work use ./ go work use ./path/to/module ``` Restart your IDE and features like go-to-definition will work across both modules. `go.work` should usually stay out of version control: ```shell echo go.work >> .gitignore echo go.work.sum >> .gitignore ``` ### Source maps The Go SDK attaches source maps to type and function definitions, `./path/to/file:line` annotations recorded during generation. Most IDEs can follow these (natively or via a plugin such as the [Open file plugin](https://marketplace.visualstudio.com/items?itemName=Fr43nk.seito-openfile) for VS Code), so you can click straight from a Dagger Function in one module to its declaration in another. ## Packaging and release A Go SDK module is distributed as a Git repository. There is no build artifact to publish. Consumers fetch the source by reference. Recommended release checklist: 1. **Pin the engine version.** Set `engineVersion` in `dagger-module.toml` to the oldest engine version your module supports. 2. **Commit the generated bindings.** Run `dagger generate`, review the changes, and commit the generated files with your module. 3. **Version with Git tags.** Tag a release (for example, `v1.2.0`) and push it. Consumers can pin that version with `@v1.2.0`. Before publishing, run `dagger check --generate` to confirm that the committed bindings are up to date. Consumers can install your module into a workspace with: ```shell dagger module install github.com/you/your-module@v1.2.0 ``` To add it as a dependency of another module, add a `[[dependencies]]` entry to that module's `dagger-module.toml`, then run `dagger generate`. A module reference follows `[proto://]host/repo[/subpath][@version]`; the version may be a tag, branch, or commit, and is resolved over HTTPS or SSH depending on available authentication. ## Troubleshooting **`dagger init` / `dagger develop` not found.** Install the Go SDK with `dagger module install dagger.io/sdk/go`, scaffold with `dagger module init go --name `, and regenerate with `dagger generate`. **Nothing was written after `dagger module init go`.** The command returns a changeset. Review and accept it, or rerun with `-y` to apply without prompting. **A new function or dependency doesn't show up.** Run `dagger generate`. The API you can call through `dag`, including dependencies, comes from the generated `internal/dagger` package, which must be in sync with your code and `dagger-module.toml`. **`dagger generate` does not regenerate bindings.** Check that the module is registered with `dagger sdk scope list --sdk=go`. **Build/import errors referencing `internal/dagger` or `dag`.** The generated client is stale or missing. Regenerate as above; if `go.mod`/`go.sum` drifted, ensure they match the regenerated code (a workspace `go.work` can help your IDE here). **Engine version mismatch.** Align the module's `engineVersion` in `dagger-module.toml`, then regenerate. ## Next steps - [SDKs overview](./index.mdx) covers platform concepts that apply to every SDK - [Types](../api/index.mdx) - [Generating code](../../using/generating.mdx) --- # SDKs URL: https://docs.dagger.io/reference/sdks/index # SDKs A module is a reusable, typed API for automation: functions, objects, checks, services, and generators that anyone can run from a workspace, in CI, in Cloud, or from an agent. This guide covers when to build a module, the development loop, and how to design one people will actually use. It's language-agnostic. For syntax, pick your [SDK](#choose-your-sdk). ## When to build a module Build a module when a workflow should become a durable, named interface rather than a remembered sequence of steps. That usually means something run often, in more than one place, that encodes a project's conventions or composes with other modules. A module earns its name by improving the caller's experience: better names, typed inputs, structured outputs, and clear errors. Wrapping a single command in a module just for ceremony isn't worth it. Install a module into a workspace (`dagger.toml`) to make it part of the commands a project offers; publish a reusable module when several projects need the same capability. See the [Quickstart](../../getting-started/quickstart.mdx). ## Developer workflow Each SDK is a Dagger module, such as `dagger.io/sdk/go` or `dagger.io/sdk/dang`. Install it with `dagger module install`, then use `dagger module init ` to create a module. See each [SDK page](#choose-your-sdk) for language options; the development loop is the same: 1. **Scaffold** a new module with `dagger module init `. It records the module in `dagger.toml` and generates its source and bindings in one [changeset](../../using/generating.mdx). 2. **Write** your objects and functions; the SDK maps them to the Dagger [type system](../api/index.mdx). 3. **Regenerate** the client bindings with `dagger generate` whenever the module's API changes. 4. **Add dependencies** with `dagger module client add --sdk=` from the module directory. It records the target in `dagger.toml` and regenerates the module. 5. **Check and test** with the module's checks and your own tests. The CLI uses the same module commands across SDKs. Each SDK supplies the language-specific generation code. ## Designing a good API Start from the call you want people to remember, then make it obvious. - **Name for intent.** Functions and objects should read like the caller's goal, not the implementation. - **Type inputs honestly.** Prefer real Dagger types over strings, defaults for conventions, settings for team-wide values, and `Secret` for credentials. Don't rely on magic environment variables or assumed host paths. - **Return rich values.** Return a `Container`, a `Directory`, or a `Changeset`, whatever lets the caller or another module continue the workflow, instead of flattening everything into a string. - **Make side effects explicit.** A function that edits files returns a changeset; a long-running dependency is a `Service`; external effects belong in clearly named publish or export functions. - **Keep the boundary clean.** Expose a small public API and keep helpers, intermediate containers, and generated files private. If a caller must know an internal path to use the module, the API is leaking. - **Fail usefully.** An error should say what failed, whose problem it is (an input, a credential, the network, or the module), and what to try next, in the caller's terms. Validate early, before expensive work begins. ## Checks, generators, and services A module can expose plain functions that just return values. That's valid and composes fine. But a module earns its place by plugging into Dagger's workflows. There are three first-class function types, and a useful reusable module provides at least one: - **Checks** validate the project: tests, linters, scans, policy. They run with `dagger check`, locally, in CI, and as Cloud Checks. - **Generators** produce source changes as reviewable [changesets](../../using/generating.mdx): codegen, formatting, lockfile updates. They run with `dagger generate`. - **Services** expose a long-running process such as a database, web server, or proxy, started with `dagger up`. You mark a function as one of these in your SDK with `@check`, `@generate`, `@up`, or the language's equivalent; see your [SDK guide](#choose-your-sdk). A module that offers none of them is often better left as a plain function call than packaged as a module. Round these out with the rest of the platform: settings and environments for team and per-context defaults, explicit `Secret` inputs for credentials, and Dagger Cloud for traces and remote execution. ## Quality A module is ready for other people when its public API is small and well-named, its functions and arguments are documented (so `dagger api call --help` is useful), its inputs and outputs are typed and composable, and its secrets, services, and side effects are explicit. Cover the main path and the important failure modes with tests. Treat the API as a contract: renaming a function or changing a default is a breaking change. Publish modules as versioned Git refs; consumers add them with `dagger module install`. ## Choose your SDK Pick a language to build in. Each guide covers its syntax, project layout, development workflow, dependencies, and testing: - **[Dang](./dang.mdx).** Dagger's native DSL. No codegen and no build step, so what you write is what runs. Best when your module orchestrates containers, files, and other Dagger objects. - **[Go](./go.mdx).** Plain structs and methods, plus a generated, typed client. - **[TypeScript](./typescript.mdx).** Decorators and a generated client, on Bun, Deno, or Node. - **[Python](./python.mdx).** Type hints, decorators, and a generated client. - **[Java](./java.mdx).** Annotations on plain classes. The SDK is vendored into the module and every generated file is committed, so `mvn package` builds it without Dagger. - **[PHP](./php.mdx).** Attributes on plain classes and a generated client. Checks work; generators and `up` services don't exist yet. - **[Elixir](./elixir.mdx).** `defn` and typespecs, with a generated client under `dagger_sdk/`. Checks work; generators and `up` services don't exist yet. --- # Java SDK URL: https://docs.dagger.io/reference/sdks/java # Java SDK The Java SDK lets you write Dagger modules in Java. You write plain Java classes and methods, mark them with a few annotations, and the SDK turns them into Dagger objects and functions that anyone can call from the CLI, from another module, or over the API. Your module also gets a generated, typed Java client, `dag()`, for the whole Dagger API: containers, directories, files, secrets, services, and every module you depend on. This page is a standalone guide to the Java SDK. It assumes you already know the platform concepts covered in the [SDKs overview](./index.mdx): - [Types](../api/index.mdx) covers how SDK types map to the Dagger API - [Generating code](../../using/generating.mdx) covers how generators and tooling return diffs for you to apply A module worth sharing provides at least one of the three first-class function types: a check, a generator, or a service. See [Checks, generators, services, directives, and ignore patterns](#checks-generators-services-directives-and-ignore-patterns) for the Java syntax. The Java SDK is itself a Dagger module, `dagger.io/sdk/java`. Install it into your workspace once, then use Dagger's module commands to scaffold, generate, and maintain Java modules: ```shell # Install the Java SDK into your workspace (once) dagger module install dagger.io/sdk/java # Create a Java module dagger module init java --name my-module ``` Java modules are self-contained. The SDK library, annotation processor, and generated client bindings live in the module as real, buildable source, and the generated entrypoint is committed next to your code. Nothing is generated when the module loads; the runtime builds and packages only what is in Git. The Go SDK works differently. The upside of the Java approach is that a plain `mvn package` builds the module in an IDE or CI without Dagger. The cost is that you commit generated files and regenerate them yourself when the module's shape changes. See [Resulting file layout](#resulting-file-layout). ## Create a module :::note Run these commands from inside a Git repository. That's where Dagger creates the new module. ::: Install the Java SDK into your workspace, then create a new module. The SDK argument is required. This example also sets the optional module name: ```shell dagger module install dagger.io/sdk/java dagger module init java --name my-module ``` `dagger module init` returns a [changeset](../../using/generating.mdx), a structured diff of the files to create. Dagger shows it to you for review before writing anything to disk. :::tip If the repository does not have a `dagger.toml` yet, use `dagger module install --here dagger.io/sdk/java` to create it in the current directory. ::: ### Where the module is created By default, `dagger module init` places the new module beside the `dagger.toml` it is editing: ``` /.dagger/modules/ ``` That is the workspace root unless the config lives in a subdirectory, as it does when several projects share one repository. Pass `--path` to choose a different location. The path is relative to your current directory, like any other path you type; a leading `/` means the workspace root. ```shell dagger module init java --name my-module --path ci # ./ci dagger module init java --name my-module --path /ci # /ci ``` Dagger registers a module at a custom path as authored by the SDK, but does not install it as a callable workspace module. Use `dagger module install ./ci` to install it too. The Java SDK ships three starter templates. Pick one with `--template`: | Template | Contents | |---|---| | `default` | A main object with a constructor that reads the workspace and a `container` function (shown below) | | `empty` | The `pom.xml` and an empty main object, for starting from scratch | | `legacy` | The classic `containerEcho` / `grepDir` starter from earlier Dagger versions | ```shell dagger module init java --name my-module --template empty ``` List the Java SDK's module initialization options with: ```shell dagger module init java --help ``` ### Resulting file layout Once initialized and generated, a Java module looks like this: ```text my-module/ ├── dagger-module.toml ├── pom.xml # Maven build; also registers the vendored sources ├── .gitattributes # marks generated files for linguist ├── .gitignore # ignores target/ and local-only files such as .env ├── src/ │ ├── main/java/io/dagger/modules/mymodule/ │ │ ├── MyModule.java # your code: the main object │ │ └── package-info.java # the @Module annotation │ └── generated/java/io/dagger/gen/entrypoint/ │ └── Entrypoint.java # generated: registers and dispatches your functions └── sdk/ # generated: the vendored Java SDK └── src/ ├── main/java/ # the SDK library (io.dagger.client, io.dagger.module.annotation) ├── processor/java/ # the annotation processor that produces Entrypoint.java ├── processor/resources/ # META-INF service descriptor for the processor └── generated/java/ # the typed client bindings from the engine schema ``` The module name drives every Java identifier: `my-module` becomes the `mymodule` package segment, the `MyModule` class, and the `my-module` Maven `artifactId`. :::note `dagger module init` writes `dagger-module.toml`, updates the workspace config, and applies the SDK template (`pom.xml`, `.gitignore`, `.gitattributes`, and the `src/main/java` sources). It then runs the Java SDK's generator for the new module, so `sdk/` and `src/generated/` land in the same changeset. Commit the generated files with the rest of the module. Dagger needs them to load the module, from your local checkout and from a Git reference alike, and never regenerates them at load time. ::: The module config records the runtime separately from the SDK that authors it. The Java SDK splits authoring, which is the code generation that `dagger generate` runs, from execution, which is the build and package step that runs when the module loads. So the runtime is the SDK's dedicated runtime module rather than the SDK itself: ```toml title="dagger-module.toml" template name = "my-module" engineVersion = "v{{ version }}" [runtime] source = "dagger.io/sdk/java/runtime" ``` The workspace's `dagger.toml` records the SDK under `sdks.java` and records the module path as one of its scopes. That scope is how the Java SDK discovers which modules to generate. You never write the files under `sdk/` and `src/generated/` by hand; see [Regenerate the SDK and entrypoint](#regenerate-bindings-and-generated-files). ### How the module is built and run When Dagger loads the module, the Java runtime mounts the committed sources into a Maven container, runs `mvn package -DskipTests`, and runs the resulting jar in a JRE container. The `pom.xml` compiles the module in two passes from that single command: first the vendored SDK, processor, and bindings; then your `io.dagger.modules.*` classes together with the committed `io.dagger.gen.*` entrypoint. The `pom.xml` switches the annotation processor off by default (`none`); only `dagger generate` turns it on, to regenerate `Entrypoint.java`. The build downloads third-party Maven dependencies and caches them in a shared Maven cache volume. The build container never takes `target/` from your checkout, so stale IDE build output cannot leak into the packaged module. ## Define objects and functions A Java module is a Maven project whose sources live in the package `io.dagger.modules.`. `package-info.java` annotates the package with `@Module`. The main object is a public class annotated with `@Object` whose name is the PascalCase form of your module name, so a module named `my-module` has a `MyModule` class. Every public method annotated with `@Function` becomes a callable Dagger Function. ```java title="src/main/java/io/dagger/modules/mymodule/package-info.java" /** A simple example module to say hello. */ @Module package io.dagger.modules.mymodule; import io.dagger.module.annotation.Module; ``` ```java title="src/main/java/io/dagger/modules/mymodule/MyModule.java" package io.dagger.modules.mymodule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object public class MyModule { /** * Return a greeting. * * @param name Who to greet * @param greeting The greeting to display */ @Function public String hello(String name, String greeting) { return "%s, %s!".formatted(greeting, name); } /** * Return a loud greeting. * * @param name Who to greet * @param greeting The greeting to display */ @Function public String loudHello(String name, String greeting) { return "%s, %s!".formatted(greeting, name).toUpperCase(); } } ``` The rules: - Every class annotated with `@Object` must be `public` and must have a public no-argument constructor (or no constructors at all). The runtime instantiates objects reflectively and restores their state between calls. - Dagger exposes only methods annotated with `@Function`, and they must be `public`. Other methods stay private Java helpers that callers never see. - A method may throw any exception. A thrown exception fails the function, and its message is the error the caller sees, so make it actionable (`throw new IllegalArgumentException("cannot divide by zero")`). Generated client calls that resolve against the engine declare `ExecutionException`, `DaggerQueryException`, and `InterruptedException`. A container command that exits non-zero throws `DaggerExecException`, which forwards the exit code, command, and output to the caller. Functions that use these calls declare those exceptions, or a broad `throws Exception`. - `@Function(value = "name")` renames a function in the API; `@Function(description = "...")` and `@Object(description = "...")` override the Javadoc description. Call your functions like any other module's, from the directory that contains `dagger-module.toml` or with `-m `: ```shell dagger api call hello --name=World --greeting=Hello # Hello, World! dagger api call loud-hello --name=World --greeting=Hello # HELLO, WORLD! ``` The CLI converts Java method and argument names to kebab-case: `loudHello` becomes `loud-hello`, and `name` becomes `--name`. ### The constructor If the main object declares a public constructor with parameters, that constructor becomes the module's constructor. Its parameters become arguments of the main object, and the instance it builds is the main object. Use it for module-wide configuration and shared state. The main object may declare only one such constructor, and it must keep a public no-argument constructor too. A common pattern is to accept a `Workspace` so the module can read the project it runs against (see [Workspace inputs](#workspace-inputs)). Dagger auto-populates it from the current workspace and pulls content lazily, so you store the project directory once and reuse it. The default template does exactly this: ```java title="MyModule.java" package io.dagger.modules.mymodule; import static io.dagger.client.Dagger.dag; import io.dagger.client.Container; import io.dagger.client.Directory; import io.dagger.client.Workspace; import io.dagger.module.annotation.Default; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object public class MyModule { private Directory source; private String baseImageAddress; public MyModule() {} /** * @param ws The current workspace, auto-populated by Dagger. * @param baseImageAddress The image to build on */ public MyModule(Workspace ws, @Default("alpine:3.24") String baseImageAddress) { // Read the workspace root; nothing is uploaded until a function uses it. this.source = ws.directory("/"); this.baseImageAddress = baseImageAddress; } /** A container with the workspace source, ready to build. */ @Function public Container container() { return dag() .container() .from(this.baseImageAddress) .withDirectory("/src", this.source) .withWorkdir("/src"); } } ``` ### Object state and fields Non-static, non-final fields are the object's state. Dagger serializes them between functions in a chain, whether they are public or private. What differs is API visibility: - A `public` field, or a field annotated with `@Function`, appears in the API as a readable value. - A `private` field without `@Function` stays as state but is hidden from callers. It is the Java equivalent of Go's `+private`. - A `transient` field is not state at all. Dagger never serializes it, so its value does not survive between function calls. ```java @Object public class LintRun { /** The report format */ public String format; @Function private String version; // exposed as `version`, despite being private private Directory source; // state only, not in the API private transient String scratch; // neither state nor API public LintRun() {} } ``` ## Arguments and return values Dagger derives a function's argument and return types from the Java signature. The mapping is: | Java type | Dagger type | |---|---| | `String` | `String` | | `int`, `long`, `short`, `byte` (and boxed forms) | `Int` | | `float`, `double` (and boxed forms) | `Float` | | `boolean` / `Boolean` | `Boolean` | | `void` | no return value (functions and checks) | | `List` or `T[]` | `[T]` (list) | | `Optional` | optional argument, or nullable object return | | `io.dagger.client.Directory` | `Directory` | | `io.dagger.client.File` | `File` | | `io.dagger.client.Container` | `Container` | | `io.dagger.client.Secret` | `Secret` | | `io.dagger.client.Service` | `Service` | | `io.dagger.client.Changeset` | `Changeset` | | a class annotated with `@Object` | object | | an enum annotated with `@Enum` | enum | ### Documentation Javadoc comments become API documentation, shown by `dagger api functions` and `dagger api call --help`. A method's Javadoc description documents the function, and each `@param` tag documents the matching argument. The Javadoc on an `@Object` class documents the object. The Javadoc on the `@Module` package declaration (or `@Module(description = "...")`) documents the whole module. ```java /** * Return a greeting. * * @param name Who to greet */ @Function public String hello(String name) { return "Hello, " + name + "!"; } ``` ### Optional and default arguments Dagger arguments are required by default. Make one optional by wrapping its type in `Optional`, or give it a default with the `@Default` annotation on the parameter: ```java title="optional" @Function public String hello(Optional name) { return "Hello, " + name.orElse("world"); } ``` ```java title="default value" @Function public String hello(@Default("world") String name) { return "Hello, " + name; } ``` - `Optional` makes the argument optional. When the caller omits it, the function receives `Optional.empty()`, so you can detect "not passed." - `@Default("...")` makes the argument optional and supplies a default value when the caller omits it. The value is a JSON literal: `@Default("true")` is a boolean, `@Default("3")` an integer. For `String` parameters the SDK adds the quotes for you, so `@Default("world")` and `@Default("\"world\"")` are equivalent. - The two combine. `@Default("world") Optional name` is optional with a default, and the `Optional` is always present. - `@Default("null")` on a non-primitive parameter marks it nullable with a `null` default. ### Nullability Use `Optional` for an argument or return value that may be absent. A function can return `Optional` (or any other object type) to signal "no result". Return `Optional.empty()` and the caller sees a null. Primitive scalars such as `int` and `boolean` are always present; use boxed types (`Integer`, `Boolean`) when the value itself may be null. :::note Nullable object return values require engine `v1.0.0-beta.10` or later. Against older engines the generated client returns object types directly. ::: ### Enums Model a closed set of values as a Java `enum` annotated with `@Enum`. Dagger turns it into an enum and validates inputs. Javadoc on the constants becomes the value descriptions: ```java title="Severity.java" package io.dagger.modules.mymodule; import io.dagger.module.annotation.Enum; /** Vulnerability severity levels */ @Enum public enum Severity { /** Undetermined risk; analyze further. */ UNKNOWN, /** Minimal risk; routine fix. */ LOW, /** Moderate risk; timely fix. */ MEDIUM, /** Serious risk; quick fix needed. */ HIGH, /** Severe risk; immediate action. */ CRITICAL } ``` ```java title="MyModule.java" package io.dagger.modules.mymodule; import static io.dagger.client.Dagger.dag; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object public class MyModule { /** * Scan an image for vulnerabilities. * * @param ref The image to scan * @param severity The minimum severity to report */ @Function public String scan(String ref, Severity severity) throws Exception { return dag() .container() .from("aquasec/trivy:0.50.4") .withExec(List.of("trivy", "image", "--severity=" + severity.name(), ref)) .stdout(); } } ``` Enums also work as return values and inside lists (`List`, `Severity[]`). Pass a value outside the enum and you get an error listing the allowed choices: ```shell dagger api call scan --ref=alpine:latest --severity=FOO # Error: value should be one of UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL ``` Enum simple names must be unique within a module, even across packages. ### Custom object types Return a class annotated with `@Object` to expose a custom object. Public fields become readable values, and `@Function` methods on the type become chainable functions. Like the main object, a custom object must be public with a public no-argument constructor. Dagger prefixes custom type names with the module name in the API schema (e.g. `MyModuleOrganization`) to avoid collisions: ```java title="MyModule.java" package io.dagger.modules.mymodule; import static io.dagger.client.Dagger.dag; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object public class MyModule { @Function public Organization daggerOrganization() { String url = "https://github.com/dagger"; Organization org = new Organization(); org.url = url; org.repositories = List.of(dag().git(url + "/dagger")); org.members = List.of(new Account("jane", "jane@example.com"), new Account("john", "john@example.com")); return org; } } ``` ```java title="Organization.java" package io.dagger.modules.mymodule; import io.dagger.client.GitRepository; import io.dagger.module.annotation.Object; import java.util.List; @Object public class Organization { public String url; public List repositories; public List members; public Organization() {} } ``` ```java title="Account.java" package io.dagger.modules.mymodule; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; @Object public class Account { public String username; public String email; public Account() {} public Account(String username, String email) { this.username = username; this.email = email; } @Function public String url() { return "https://github.com/" + username; } } ``` Only the main object's parameterized constructor becomes a Dagger constructor. On other objects, extra constructors are plain Java conveniences. You can then chain on the CLI and API: ```shell dagger api call dagger-organization members url ``` ### Interfaces The Java SDK does not currently support interfaces that accept arbitrary objects from other modules. Accept concrete core types or your own `@Object` types instead. ## Working with core Dagger types The vendored client exposes the entire Dagger API through the static `dag()` method of `io.dagger.client.Dagger`, usually imported with `import static io.dagger.client.Dagger.dag;`. You use it to build containers, mount directories and files, handle secrets, and run services. Core types live in the `io.dagger.client` package. ### Containers Each builder method returns a new, immutable `Container`. Nothing mutates in place, and Dagger content-addresses and caches every step. Builder methods are lazy. Methods that return data from the engine (`stdout`, `entries`, `contents`, `sync`, and so on) run the pipeline and declare checked exceptions. ```java package io.dagger.modules.mymodule; import static io.dagger.client.Dagger.dag; import io.dagger.client.Container; import io.dagger.client.Directory; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object public class MyModule { /** Build and return a container */ @Function public Container build(Directory source) { return dag() .container() .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withExec(List.of("npm", "install")) .withExec(List.of("npm", "run", "build")); } } ``` ### Directories and files `Directory` and `File` are first-class, "just-in-time" artifacts. You can accept them as arguments, return them, mount them into containers, and export them to the host. Methods with optional parameters have an overload that takes a generated `XxxArguments` builder. For example, `withDirectory` accepts a `Container.WithDirectoryArguments`: ```java /** * Copy a directory into a container, leaving some paths out. * * @param source Source directory * @param exclude Exclusion patterns */ @Function public Container copyDirectoryWithExclusions(Directory source, Optional> exclude) { return dag() .container() .from("alpine:latest") .withDirectory( "/src", source, new Container.WithDirectoryArguments().withExclude(exclude.orElse(List.of()))); } ``` The pattern is the same everywhere. Required parameters are positional Java arguments. Optional ones live in a generated `XxxArguments` inner class of the type that declares the method, and you set them with `withXxx` methods. ### Workspace inputs {#workspace-inputs} A module that needs to read the user's project, whether its source tree, config files, or lockfiles, takes a `Workspace` argument, almost always on the constructor. You don't pass it. Dagger auto-populates it from the current workspace and uploads nothing up front. Dagger pulls project content lazily, when a function actually reads a path, so a module can declare access to the whole workspace cheaply and pay only for what it touches. ```java title="MyModule.java" package io.dagger.modules.mymodule; import static io.dagger.client.Dagger.dag; import io.dagger.client.Container; import io.dagger.client.Directory; import io.dagger.client.Workspace; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object public class MyModule { private Directory source; public MyModule() {} /** @param ws The current workspace, auto-populated by Dagger. */ public MyModule(Workspace ws) { // Pull the workspace root as a Directory (lazy, no upload yet). this.source = ws.directory("/"); } /** Functions reuse the pulled Directory like any other. */ @Function public Container build() { return dag() .container() .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withExec(List.of("npm", "install")) .withExec(List.of("npm", "run", "build")); } } ``` The `Workspace` client type has these accessors for reading project content: | Accessor | Signature | Returns | |---|---|---| | `directory` | `ws.directory(String path)` / `ws.directory(String path, Workspace.DirectoryArguments opts)` | a `Directory` at `path` | | `file` | `ws.file(String path)` | a `File` at `path` | | `findUp` | `ws.findUp(String name)` / `ws.findUp(String name, Workspace.FindUpArguments opts)` | the workspace path of `name`, searching upward, or `null` | **Path resolution.** A relative path resolves from the workspace's current working directory. An absolute path (starting with `/`) resolves from the workspace root, also called the boundary. So `ws.directory("/")` is the whole project root, while `ws.directory(".")` is wherever the user invoked Dagger from. **Excluding files.** `directory` takes a `Workspace.DirectoryArguments` builder to filter what gets pulled. Tight filters matter for caching. The less you load, the fewer cache invalidations you get: ```java public MyModule(Workspace ws) { this.source = ws.directory( "/", new Workspace.DirectoryArguments() .withExclude(List.of("node_modules", ".git", "dist"))); // .withInclude(List.of("app/", "package.*")) // allowlist instead // .withGitignore(true) // apply .gitignore rules } ``` `findUp` walks up from a start path and returns the absolute workspace path of the first match, stopping at the workspace boundary. Relative start paths resolve from the workspace cwd; pass `new Workspace.FindUpArguments().withFrom("...")` to change that. Because it resolves against the engine, it declares checked exceptions. Use it to find a project root marker such as `pom.xml` or `package.json`. :::tip To use the current workspace, declare a `Workspace` parameter on the module constructor or a function. Dagger injects it and leaves it out of the CLI arguments. ::: ### Path-defaulted directories and files For a function that needs a specific file or directory rather than the whole workspace, annotate a `Directory`, `File`, `GitRepository`, or `GitRef` parameter with `@DefaultPath`. Dagger resolves the path when the caller omits the argument, and callers can still pass their own: ```java /** * Print the project's dependencies. * * @param pom The Maven project file */ @Function public String dependencies(@DefaultPath("pom.xml") File pom) throws Exception { return pom.contents(); } ``` A `Directory` parameter may also carry `@Ignore` to filter what is loaded; see [Ignore patterns](#ignore-patterns). ### Secrets Accept sensitive values as `Secret`, never as plain strings. Dagger scrubs secret plaintext from logs, caches, and crash reports: ```java package io.dagger.modules.mymodule; import static io.dagger.client.Dagger.dag; import io.dagger.client.Secret; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object public class MyModule { /** * Query the GitHub API * * @param token GitHub API token */ @Function public String githubApi(Secret token) throws Exception { return dag() .container() .from("alpine:3.17") .withSecretVariable("GITHUB_API_TOKEN", token) .withExec(List.of("apk", "add", "curl")) .withExec( List.of( "sh", "-c", "curl \"https://api.github.com/repos/dagger/dagger/issues\"" + " --header \"Authorization: Bearer $GITHUB_API_TOKEN\"")) .stdout(); } } ``` Callers supply secrets through providers on the CLI: ```shell dagger api call github-api --token=env:GITHUB_TOKEN # environment variable dagger api call github-api --token=file:./token.txt # file dagger api call github-api --token=cmd:"gh auth token" # command output dagger api call github-api --token=op://vault/item/field # 1Password ``` ### Services Return `Service` to expose a long-running service, and bind it into other containers with `withServiceBinding`. Services are content-addressed, so a given definition always gets the same hostname and port conflicts never come up: ```java package io.dagger.modules.mymodule; import static io.dagger.client.Dagger.dag; import io.dagger.client.Container; import io.dagger.client.Service; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import java.util.List; @Object public class MyModule { /** Start and return an HTTP service */ @Function public Service httpService() { return dag() .container() .from("python") .withWorkdir("/srv") .withNewFile("index.html", "Hello, world!") .withExposedPort(8080) .asService( new Container.AsServiceArguments() .withArgs(List.of("python", "-m", "http.server", "8080"))); } /** Send a request to an HTTP service and return the response */ @Function public String get() throws Exception { return dag() .container() .from("alpine") .withServiceBinding("www", httpService()) .withExec(List.of("wget", "-O-", "http://www:8080")) .stdout(); } } ``` ### A larger example Real modules combine these pieces. This lint module takes a source directory, returns a custom `LintRun` object, and exposes both a report file and an assertion on it. Note the custom type, the chaining, and `dag().currentModule().source()`, which reaches the module's own files: ```java title="Ruff.java" package io.dagger.modules.ruff; import io.dagger.client.Directory; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; /** Ruff is a fast Python linter implemented in Rust */ @Object public class Ruff { /** * Lint a Python codebase * * @param source The Python source directory to lint */ @Function public LintRun lint(Directory source) { return new LintRun(source); } } ``` ```java title="LintRun.java" package io.dagger.modules.ruff; import static io.dagger.client.Dagger.dag; import io.dagger.client.Container; import io.dagger.client.Directory; import io.dagger.client.File; import io.dagger.module.annotation.Function; import io.dagger.module.annotation.Object; import jakarta.json.Json; import jakarta.json.JsonArray; import jakarta.json.JsonObject; import java.io.StringReader; import java.util.List; import java.util.stream.Collectors; /** The result of running the Ruff lint tool */ @Object public class LintRun { private Directory source; public LintRun() {} public LintRun(Directory source) { this.source = source; } /** Return a JSON report file for this run */ @Function public File report() { List cmd = List.of("/ruff", "check", "--exit-zero", "--output-format", "json", "."); return dag() .currentModule() .source() .directory("build") .dockerBuild() .withMountedDirectory("/src", source) .withWorkdir("/src") .withExec(cmd, new Container.WithExecArguments().withRedirectStdout("ruff-report.json")) .file("ruff-report.json"); } /** Fail if the run reported any issues */ @Function public void assertClean() throws Exception { JsonArray issues = Json.createReader(new StringReader(report().contents())).readArray(); if (!issues.isEmpty()) { String lines = issues.stream() .map(v -> " - " + ((JsonObject) v).getString("message")) .collect(Collectors.joining("\n")); throw new RuntimeException("%d issues\n%s".formatted(issues.size(), lines)); } } } ``` The Jakarta JSON API used here is already a dependency of every Java module, because the SDK itself uses it. Add other libraries to the `` section of your `pom.xml` as you would in any Maven project. ## Module dependencies A module can depend on other Dagger modules and call them through `dag()`, for example `dag().golang()` once `golang` is a dependency. `dagger-module.toml` records dependencies under `dependencies`: ```toml title="dagger-module.toml" template name = "dev" engineVersion = "v{{ version }}" [runtime] source = "dagger.io/sdk/java/runtime" [[dependencies]] name = "golang" source = "dagger.io/go" [[dependencies]] name = "wolfi" source = "../wolfi" ``` A `source` may be a local path (`../wolfi`) or a remote reference of the form `[proto://]host/repo[/subpath][@version]`, e.g. `github.com/shykes/daggerverse/hello@v0.3.0`. Manage dependencies from the module directory with `dagger module client`: ```shell dagger module client add ../wolfi --sdk=java dagger module client list --sdk=java dagger module client update --sdk=java dagger module client rm --sdk=java ``` Use the exact `TARGET` from `client list` for removal. Client add and remove commands update targets in `dagger.toml`. Client update refreshes `dagger.lock`. All three regenerate the module. The SDK writes the corresponding `[[dependencies]]` entries shown above. Manual dependency edits in `dagger-module.toml` are replaced by generation. After changing dependencies, [regenerate](#regenerate-bindings-and-generated-files) so the new module's functions appear on `dag()`. A dependency's functions follow the same conventions as the core API. Required arguments are positional, and optional ones go in an `XxxArguments` builder: ```java @Function public Directory example(Directory buildSrc, List buildArgs) { return dag() .golang() .build(buildArgs, new Golang.BuildArguments().withSource(buildSrc)) .terminal(); } ``` ## Regenerate the SDK and entrypoint {#regenerate-bindings-and-generated-files} A Java module uses generated code alongside your handwritten sources: - `sdk/src/main/java/` holds the Java SDK library: the `io.dagger.client` runtime and the `io.dagger.module.annotation` annotations - `sdk/src/processor/java/` and `sdk/src/processor/resources/` hold the annotation processor that turns your annotated classes into the entrypoint - `sdk/src/generated/java/` holds the typed client bindings, including `dag()`, all core types (`Container`, `Directory`, and the rest), every dependency's functions, and the `XxxArguments` builders - `src/generated/java/io/dagger/gen/entrypoint/Entrypoint.java` is the entrypoint that registers your objects and functions with the engine and dispatches calls to them You do not edit these files by hand, but you do commit them. Dagger does not regenerate them when it loads a module, including a module installed from Git. If the entrypoint is missing, the module fails to load with an error telling you to run `dagger generate`. The generated `.gitattributes` marks them as `linguist-generated`: ```text title=".gitattributes" /sdk/** linguist-generated /src/generated/** linguist-generated ``` Regenerate them whenever you change your module's objects, functions, or annotations, bump the engine version, or add or remove a dependency. Use `dagger generate`, which returns a changeset: ```shell # Review the regenerated files, then apply dagger generate ``` `dagger generate` discovers and runs every generator in the workspace. For modules registered under the Java SDK, that includes regenerating the vendored SDK and entrypoint. Generation runs Maven in containers Dagger controls. It builds the SDK against the engine's current schema, vendors the result under `sdk/`, and compiles your module once with the annotation processor enabled to produce `Entrypoint.java`. It never touches your local Maven installation. :::note Editing a function's body needs no regeneration. Changing the *shape* of your module does, and not only when dependencies change: a new `@Function`, a renamed argument, a new `@Object`. If you forget, the module still builds but the new function is not registered. `dagger check --generate` catches this drift. ::: Apply and commit the resulting changeset. This keeps the generated client in sync with the functions and dependencies available to your module. :::note A `.dagger-java-sdk-skip-generate` marker in a module or one of its ancestors skips Java generation for that module. ::: :::tip The `.gitignore` does not cover generated files; commit them with your module. It ignores only `target/` (Maven build output) and local-only files such as `.env`. ::: ### Committing a prebuilt SDK jar By default the runtime compiles the vendored SDK sources on every fresh build. To shorten module builds, the SDK can also commit a compiled SDK jar under `sdk/repo/`. The `pom.xml` detects it and compiles only your own code against it, while the sources stay checked in for IDE navigation. This is opt-in because it puts a binary in version control, and that is a trade-off worth making on purpose rather than by default. Enable it as a setting on the SDK module in `dagger.toml`, then regenerate: ```toml title="dagger.toml" [modules.java-sdk.settings] vendorSdkJar = true ``` ## Engine version Each module declares the Dagger engine version it requires in the `engineVersion` field of `dagger-module.toml`. Set it to a concrete version. Bumping the engine version usually means the generated client bindings should change too, so follow with `dagger generate`. ## Checks, generators, services, directives, and ignore patterns A module worth reusing provides at least one of the three first-class function types, a check, a generator, or a service, so that the platform verbs (`dagger check`, `dagger generate`, `dagger up`) have something to run. These work the same in Java as in any SDK; see the [SDKs overview](./index.mdx) for the full treatment. In Java, you mark each one with an annotation next to `@Function`. Both annotations are required: | Annotation | Return type | Run by | Purpose | |---|---|---|---| | `@Check` | `void` (or `Container`) | `dagger check` | validate the project (test/lint/scan) | | `@Generate` | `Changeset` | `dagger generate` | produce a diff to apply to the workspace | | `@Up` | `Service` | `dagger up` | start a long-running service | The parts specific to Java follow. ### Annotations Java modules use annotations from `io.dagger.module.annotation` to add Dagger metadata that Java's type system can't express: | Annotation | Placement | Meaning | |---|---|---| | `@Module` | the package (`package-info.java`) | marks the package as the module; optional `description` | | `@Object` | a class | expose the class as a Dagger object; optional `value` (name) and `description` | | `@Function` | a public method, or a field | expose the method as a function, or the field as a readable value | | `@Enum` | an enum | expose the enum as a Dagger enum | | `@Default("json")` | a parameter | optional argument with a default value | | `@DefaultPath("path")` | a `Directory`, `File`, `GitRepository`, or `GitRef` parameter | load from this path when the caller omits the argument | | `@Ignore({...})` | a `Directory` parameter | patterns to leave out when loading the directory | | `@Check` | a `@Function` method | mark the function as a [check](#checks) | | `@Generate` | a `@Function` method | mark the function as a [generator](#generators) | | `@Up` | a `@Function` method | mark the function as a [service](#up-services) | ### Ignore patterns {#ignore-patterns} A module reads the user's project through a `Workspace` argument (see [Workspace inputs](#workspace-inputs)) and filters what gets pulled with the exclude option when reading a workspace directory. Tight filters matter for caching. The less you load, the fewer cache invalidations you get: ```java public MyModule(Workspace ws) { this.source = ws.directory( "/", new Workspace.DirectoryArguments() .withExclude(List.of("node_modules", ".git", "dist"))); } ``` For a `Directory` parameter loaded with `@DefaultPath`, use `@Ignore` instead. Patterns are gitignore-style; a leading `!` re-includes a path, so `{"**", "!**/*.java"}` keeps only Java sources: ```java /** * @param source The Java sources to compile */ @Function public Container compile( @DefaultPath(".") @Ignore({"**", "!src/**/*.java", "!pom.xml"}) Directory source) { return dag().container().from("maven:3.9-eclipse-temurin-21").withDirectory("/src", source); } ``` ### Checks {#checks} Annotate a function with `@Check` (in addition to `@Function`) to make it a check, a validation function such as a test, lint, or scan that takes no caller arguments. `dagger check` discovers and runs every check a module exposes. A check fails when it throws, or when it returns a `Container` whose execution exits non-zero. ```java /** Lint the project. */ @Function @Check public void lint() throws Exception { dag() .container() .from("golangci/golangci-lint:latest") .withMountedDirectory("/src", source) .withWorkdir("/src") .withExec(List.of("golangci-lint", "run")) .sync(); } /** A check can also return a container; a non-zero exit fails the check. */ @Function @Check public Container build() { return dag().container().from("alpine:3").withExec(List.of("true")); } ``` A `void` check must actually execute something, such as `sync()`, `stdout()`, or another resolving call, because builder methods alone are lazy. You can also declare checks on custom object types to group them, for example a `Test` object with `lint` and `unit` checks reached through a `test()` function on the main object. ### Generators {#generators} Annotate a function with `@Generate` (in addition to `@Function`) to make it a generator. It runs a tool, captures the resulting directory, diffs that against the source, and returns the result as a `Changeset`. `dagger generate` discovers and runs every generator and presents the combined [changeset](../../using/generating.mdx) for you to review and apply. ```java /** Format the source. */ @Function @Generate public Changeset format() { Directory formatted = dag() .container() .from("maven:3.9-eclipse-temurin-21") .withMountedDirectory("/src", source) .withWorkdir("/src") .withExec(List.of("mvn", "-q", "com.spotify.fmt:fmt-maven-plugin:format")) .directory("/src"); return formatted.changes(source); } ``` :::note A `@Generate` function you write is distinct from the SDK's own generator, which vendors the SDK and entrypoint. `dagger generate` runs both kinds. ::: See [Generating code](../../using/generating.mdx). ### Services {#up-services} Annotate a function with `@Up` (in addition to `@Function`) to make it a service. It returns a `Service`. `dagger up` discovers and starts every service the module exposes and opens their ports on the host. ```java /** Run the web server. */ @Function @Up public Service web() { return dag().container().from("nginx:alpine").withExposedPort(80).asService(); } ``` Like checks, `@Up` services can live on custom object types so you can group related services, for example an `Infra` object with a `database` service, which `dagger up -l` lists as `infra:database`. This is the same `Service` type described under [Services](#services) above. The `@Up` annotation is what makes a service function runnable directly with `dagger up`. ## Testing Java modules Because a Java module is an ordinary Maven project, you can test it two ways, and they complement each other. ### Idiomatic Java tests Pure Java logic, such as parsing reports, formatting summaries, or computing counts, can be unit-tested with JUnit with no engine involved. Add JUnit to the `` in your `pom.xml` (the Surefire plugin is already configured): ```xml title="pom.xml" org.junit.jupiter junit-jupiter 5.11.4 test ``` ```java title="src/test/java/io/dagger/modules/mymodule/IssueTest.java" package io.dagger.modules.mymodule; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class IssueTest { @Test void summary() { Issue issue = new Issue("/src/app/main.py", "undefined name", 12); assertEquals("app/main.py:12 error: undefined name", issue.summary()); } } ``` Run them like any Maven test. They don't need the engine, because the vendored SDK and committed entrypoint compile without Dagger: ```shell mvn test ``` The Dagger runtime packages the module with `-DskipTests`, so unit tests never run as part of loading the module. They run when you, or a check, invoke Maven. ### Functional tests via checks For behavior that exercises containers and the Dagger API, write functions in your module and invoke them, or model them as checks so they run under `dagger check`. A check that builds, lints, or tests your project doubles as both a CI gate and a smoke test: ```shell # Smoke test: does it build? dagger api call build # Run all checks dagger check # Run generators and confirm there's no drift dagger check --generate ``` ### In CI Run `dagger check` in CI to run every check the module exposes. The heavy lifting happens in content-addressed containers, so the same command behaves the same on a laptop and on a CI runner, with full caching: ```yaml title=".github/workflows/ci.yml" jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dagger/dagger-for-github@v6 with: verb: check ``` ## IDE and Maven setup Open the module's `pom.xml` as a Maven project in your IDE. The `build-helper-maven-plugin` configuration registers the vendored SDK (`sdk/src/main/java`, `sdk/src/processor/java`, `sdk/src/generated/java`) and the committed entrypoint (`src/generated/java`) as source roots. Code completion and go-to-definition work for `dag()`, every core type, and every dependency with no extra setup, because all of it is ordinary source in your checkout. A few things to keep in mind: - **Java version.** The module compiles with `17`; the Dagger runtime builds and runs it on a Temurin 21 JDK/JRE. Use JDK 17 or newer locally. - **Do not edit generated files.** The next `dagger generate` overwrites anything under `sdk/` or `src/generated/`. - **Keep the Dagger plugin configuration.** Dagger needs the `maven-compiler-plugin` two-pass setup and the `maven-shade-plugin` execution that sets `io.dagger.gen.entrypoint.Entrypoint` as the main class to build and run the module. Add your own plugins and dependencies around them. - **`target/` is build output only.** It is git-ignored, and the runtime and generator both exclude it, so an IDE build never affects what Dagger packages. - **Formatting.** The SDK sources follow [google-java-format](https://github.com/google/google-java-format); the samples in this guide use the same style. ## Packaging and release You distribute a Java SDK module as a Git repository. There is no build artifact to publish. Consumers fetch the source by reference, and the committed `sdk/` and `src/generated/` directories are what make that work. A consumer's engine builds the module from source and never has to run code generation. Recommended release checklist: 1. **Pin the engine version.** Set `engineVersion` in `dagger-module.toml` to the oldest engine version your module supports. 2. **Commit the generated files.** Run `dagger generate`, review the changes, and commit `sdk/` and `src/generated/` with your module. 3. **Version with Git tags.** Tag a release (for example, `v1.2.0`) and push it. Consumers can pin that version with `@v1.2.0`. Before publishing, run `dagger check --generate` to confirm that the committed files are up to date. Consumers can install your module into a workspace with: ```shell dagger module install github.com/you/your-module@v1.2.0 ``` To add it as a dependency of another module, add a `[[dependencies]]` entry to that module's `dagger-module.toml`, then run `dagger generate`. A module reference follows `[proto://]host/repo[/subpath][@version]`. The version may be a tag, branch, or commit. Dagger resolves it over HTTPS or SSH depending on the authentication available. ## Troubleshooting **`dagger init` / `dagger develop` not found.** Install the Java SDK with `dagger module install dagger.io/sdk/java`, scaffold with `dagger module init java --name `, and regenerate with `dagger generate`. **Nothing was written after `dagger module init`.** The command returns a changeset. Review and accept it, or rerun with `-y` to apply without prompting. **"is missing its generated file `src/generated/java/io/dagger/gen/entrypoint/Entrypoint.java`".** The runtime refuses to build a module without a committed entrypoint. Run `dagger generate`, apply the changeset, and commit the result. That applies when the module is consumed from Git too. **A new function or object doesn't show up.** Run `dagger generate`. The entrypoint under `src/generated/java` registers your functions with the engine and must be regenerated whenever annotated code changes, not only when dependencies change. **`dagger generate` does not regenerate the module.** Look for a `.dagger-java-sdk-skip-generate` marker in the module or one of its ancestors. **"The class … must be public if annotated with @Object" / "must have a public no-argument constructor".** Every `@Object` class must be public and instantiable without arguments. Keep the no-argument constructor even when you add a parameterized one. **"The class … must have a single non-empty constructor".** The main object may have only one parameterized constructor, since that one becomes the module constructor. Move the alternatives to static factory methods. **Compile errors mentioning `io.dagger.client` or `dag()`.** The vendored SDK is stale or missing. Regenerate as above. If you changed `engineVersion`, regenerate so the bindings match the schema. **Maven build failures at module load.** The runtime prints Maven's output in the trace, and `mvn package` locally reproduces the same build. The processor reports annotation misuse during `dagger generate`, for example `@DefaultPath` on a type other than `Directory`, `File`, `GitRepository`, or `GitRef`, `@Ignore` on a non-`Directory`, or `@Default` combined with `@DefaultPath`. **Engine version mismatch.** Align the module's `engineVersion` in `dagger-module.toml`, then regenerate. ## Next steps - [SDKs overview](./index.mdx) for the platform concepts that apply to every SDK - [Types](../api/index.mdx) - [Generating code](../../using/generating.mdx) - [Quickstart](../../getting-started/quickstart.mdx) --- # PHP SDK URL: https://docs.dagger.io/reference/sdks/php # PHP SDK :::note The PHP SDK module still uses the previous beta SDK interface. It needs an update before the current `dagger module init` and module client commands can use it. The module runtime is unchanged. ::: The PHP SDK lets you write Dagger modules in PHP. You define plain PHP classes and methods, mark them with attributes, and the SDK turns them into Dagger objects and functions that anyone can call from the CLI, from another module, or over the API. In return, your module gets a generated, fully typed PHP client, `dag()`, for the whole Dagger API. That covers containers, directories, files, secrets, services, and every module you depend on. This page is a standalone guide to the PHP SDK. It assumes you already understand the platform concepts covered in the [SDKs overview](./index.mdx): - [Types](../api/index.mdx) explains how SDK types map to the Dagger API - [Generating code](../../using/generating.mdx) explains how generators and tooling return diffs for you to apply A useful, reusable module provides at least one of the three first-class function types: a check, a generator, or a service. The PHP SDK currently supports checks. See [Checks, directives, and ignore patterns](#checks-directives-and-ignore-patterns) for the PHP syntax and what is still missing. The PHP SDK is itself a Dagger module, `dagger.io/sdk/php`. Install it into your workspace once, then use Dagger's module commands to scaffold, generate, and maintain PHP modules: ```shell # Install the PHP SDK into your workspace (once) dagger module install dagger.io/sdk/php # Create a PHP module dagger module init php --name my-module ``` ## Create a module :::note Run these commands from inside a Git repository. That's where the new module goes. ::: Install the PHP SDK into your workspace, then create a new module. The SDK argument is required. This example also sets the optional module name: ```shell dagger module install dagger.io/sdk/php dagger module init php --name my-module ``` `dagger module init` returns a [changeset](../../using/generating.mdx), a structured diff of the files it wants to create. Dagger shows you that diff to review before it writes anything to disk. :::tip If the repository does not have a `dagger.toml` yet, use `dagger module install --here dagger.io/sdk/php` to create it in the current directory. ::: ### Where the module is created By default, `dagger module init` places the new module beside the `dagger.toml` it is editing: ``` /.dagger/modules/ ``` That is the workspace root unless the config lives in a subdirectory, as it does when several projects share one repository. Pass `--path` to choose a different location. The path is relative to your current directory, like any other path you type, and a leading `/` means the workspace root. ```shell dagger module init php --name my-module --path ci # ./ci dagger module init php --name my-module --path /ci # /ci ``` Dagger registers a module at a custom path as authored by the SDK but does not install it as a callable workspace module. Run `dagger module install ./ci` to install it too. The PHP SDK ships one starter template, `minimal`, and uses it by default. Pass `--template` to pick a template by name: ```shell dagger module init php --name my-module --template minimal ``` List the PHP SDK's module initialization options with: ```shell dagger module init php --help ``` ### Resulting file layout Once initialized and generated, a PHP module looks like this: ```text my-module/ ├── dagger-module.toml ├── composer.json ├── composer.lock # generated: written by the first composer install ├── entrypoint.php # generated: called by the engine, do not edit ├── src/ │ └── MyModule.php # your code ├── sdk/ # generated: the typed Dagger client (gitignored) ├── vendor/ # composer dependencies (gitignored) ├── .gitattributes # marks generated files for linguist └── .gitignore # ignores sdk/, vendor/, and .env ``` :::note `dagger module init` writes `dagger-module.toml`, `composer.json`, `entrypoint.php`, and `src/MyModule.php`, updates the workspace config, and runs the PHP SDK's generator for the new module. That is why `composer.lock`, `.gitattributes`, and `.gitignore` land in the same changeset. ::: Unlike some other SDKs, you do not commit the generated PHP client in `sdk/`. The runtime regenerates it from the engine's schema every time it loads the module, so `.gitignore` excludes it along with `vendor/`. A fresh clone never carries a stale client, which is a nice property. The cost is that your editor is blind until you generate the client locally. See [Regenerate bindings](#regenerate-bindings-and-generated-files). The module config records the runtime separately from the SDK that authors it: ```toml title="dagger-module.toml" template name = "my-module" engineVersion = "v{{ version }}" [runtime] source = "php" ``` `runtime.source = "php"` tells the engine to use the PHP runtime. The workspace's `dagger.toml` records the SDK under `sdks.php` and records the module path as one of its scopes. That scope is how the PHP SDK's generator finds workspace modules. `composer.json` declares the module as a Composer package in the `DaggerModule` namespace and depends on the generated client through a path repository: ```json title="composer.json" { "name": "daggermodule/my-module", "repositories": [ { "type": "path", "url": "./sdk" } ], "require": { "php": "^8.1", "dagger/dagger": "*@dev" }, "autoload": { "psr-4": { "DaggerModule\\": "src/" } } } ``` ## Define objects and functions A PHP module is a set of classes in the `DaggerModule` namespace under `src/`. The main object is a class whose name matches your module, PascalCased. A module named `my-module` has a `MyModule` class in `src/MyModule.php`. Mark classes with `#[DaggerObject]`, and every public method marked with `#[DaggerFunction]` becomes a callable Dagger Function. ```php title="src/MyModule.php" `: ```shell dagger api call hello --name=World --greeting=Hello # Hello, World! dagger api call loud-hello --name=World --greeting=Hello # HELLO, WORLD! ``` The CLI converts PHP method and argument names to kebab-case (`loudHello` becomes `loud-hello`, `$name` becomes `--name`). ### The constructor Mark `__construct` on the main class with `#[DaggerFunction]` to make it the module's constructor. Its arguments become arguments of the main object, and the initialized instance is the main object. Use it for module-wide configuration and shared state. A common pattern is to accept a `Workspace` so the module can read the project it runs against (see [Workspace inputs](#workspace-inputs)). Dagger fills it in from the current workspace and pulls content lazily, so you store the project directory once and reuse it: ```php title="src/MyModule.php" source = $ws->directory('/'); } #[DaggerFunction] #[ReturnsListOfType('string')] public function foo(): array { return dag() ->container() ->from('alpine:latest') ->withMountedDirectory('/app', $this->source) ->directory('/app') ->entries(); } } ``` Every property on the object, public or private, is part of its state, and Dagger serializes it between functions in a chain. Only public properties marked with `#[DaggerFunction]` show up in the API as readable fields. Everything else stays private to Dagger: ```php #[DaggerObject] class LintRun { #[DaggerFunction] #[Doc('The directory that was linted')] public Directory $source; // Present in PHP, hidden from the API. private string $report = ''; } ``` Constructor promotion works too, so `public function __construct(private readonly string $greeting = 'Hello')` both declares the argument and stores it. A class that declares fields but no constructor gets an implicit argument-less constructor. ## Arguments and return values Dagger derives a function's argument and return types from the PHP signature. The mapping is: | PHP type | Dagger type | |---|---| | `string` | `String` | | `int` | `Int` | | `float` | `Float` | | `bool` | `Boolean` | | `void` or `null` | `Void` | | `array` with `#[ListOfType('T')]` / `#[ReturnsListOfType('T')]` | `[T]` (list) | | `Dagger\Directory` | `Directory` | | `Dagger\File` | `File` | | `Dagger\Container` | `Container` | | `Dagger\Secret` | `Secret` | | `Dagger\Service` | `Service` | | a `#[DaggerObject]` class `T` | object `T` | ### Lists PHP's `array` type carries no element type, so a list argument or return value must declare its element type with an attribute. Use `#[ListOfType]` on parameters and fields, and `#[ReturnsListOfType]` on methods. The SDK does not read docblock annotations such as `@param string[]`. ```php use Dagger\Attribute\ListOfType; use Dagger\Attribute\ReturnsListOfType; use Dagger\Directory; #[DaggerFunction] #[ReturnsListOfType('string')] public function capitalizeStrings( #[ListOfType('string')] array $values, ): array { return array_map(fn(string $v) => ucwords($v), $values); } #[DaggerFunction] #[ReturnsListOfType(Directory::class)] public function split( #[ListOfType(Directory::class)] array $dirs, ): array { return $dirs; } ``` The element type may be a scalar name (`'string'`, `'int'`, `'float'`, `'bool'`), a class name, or a nested `ListOfType` for lists of lists. ### Documentation The `#[Doc]` attribute becomes API documentation, shown by `dagger api functions` and `dagger api call --help`. Place it on a method to document the function, on a parameter to document that argument, and on the main class to document the whole module. The SDK ignores PHP docblocks. ```php #[DaggerFunction] #[Doc('Return a greeting')] public function hello( #[Doc('Who to greet')] string $name, ): string { return "Hello, {$name}!"; } ``` ### Optional and default arguments Dagger arguments are required by default. Make one optional with a PHP default value or a nullable type: ```php title="default value" #[DaggerFunction] public function hello(string $name = 'world'): string { return "Hello, {$name}"; } ``` ```php title="optional" #[DaggerFunction] public function hello(?string $name): string { if ($name !== null) { return "Hello, {$name}"; } return 'Hello, world'; } ``` - A PHP default value (`string $name = 'world'`) makes the argument optional and supplies the default when the caller omits it. - A nullable type with no default (`?string $name`) makes the argument optional and passes `null` when omitted, so you can detect "not passed." - A `Directory` or `File` parameter with `#[DefaultPath]` is also optional; see [Default paths](#default-paths). ### Nullability Use a nullable type (`?Dagger\Secret`, `?Dagger\Container`) when an argument may be absent. `null` means the caller passed null or nothing at all. Non-nullable scalars are always present. A common constructor pattern falls back to a computed default: ```php #[DaggerFunction] public function __construct(?Container $ctr = null) { $this->ctr = $ctr ?? dag()->container()->from('alpine:3'); } ``` ### Enums The PHP SDK does not currently register custom enums defined in your module. To accept a closed set of values, take a `string` argument and validate it yourself. Throw an exception that lists the allowed choices: ```php #[DaggerFunction] public function scan(string $ref, string $severity = 'HIGH'): string { $allowed = ['UNKNOWN', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL']; if (!in_array($severity, $allowed, true)) { throw new \InvalidArgumentException( 'severity should be one of ' . implode(', ', $allowed), ); } return dag() ->container() ->from('aquasec/trivy:0.50.4') ->withExec(['trivy', 'image', "--severity={$severity}", $ref]) ->stdout(); } ``` The generated client includes enums that already exist in the Dagger API (for example `Dagger\ImageLayerCompression`) as PHP enums, and you can pass them to core API calls as usual. ### Custom object types Return an instance of another `#[DaggerObject]` class to expose a custom object. Public properties marked `#[DaggerFunction]` become readable fields, and `#[DaggerFunction]` methods on the class become chainable functions. Dagger prefixes custom type names with the module name in the API schema (for example `MyModuleOrganization`) to avoid collisions: ```php title="src/MyModule.php" username; } } ``` Each class can live in its own file under `src/`; the SDK discovers every `#[DaggerObject]` in the directory. You can then chain calls on the CLI and API: ```shell dagger api call dagger-organization members url ``` ### Interfaces The PHP SDK does not currently support Dagger interfaces as argument or return types. Accept a concrete object from a dependency instead, or take the values you need (a `Container`, a `Directory`, a string) directly. ## Working with core Dagger types The generated client exposes the whole Dagger API through the `dag()` function, imported with `use function Dagger\dag;`. You use it to build containers, mount directories and files, handle secrets, and run services. Core types live in the `Dagger` namespace (`Dagger\Container`, `Dagger\Directory`, and so on). ### Containers Each builder method returns a new, immutable `Container`. Nothing mutates in place, and Dagger content-addresses and caches every step for you. ```php use Dagger\Container; use Dagger\Directory; #[DaggerFunction] #[Doc('Build and return a container')] public function build(Directory $source): Container { return dag() ->container() ->from('node:20') ->withDirectory('/app', $source) ->withWorkdir('/app') ->withExec(['npm', 'install']) ->withExec(['npm', 'run', 'build']); } ``` ### Directories and files `Directory` and `File` are first-class, "just-in-time" artifacts. You can accept them as arguments, return them, mount them into containers, and export them to the host. Less common parameters are optional PHP arguments, so pass them by name. For example, `withDirectory` accepts `exclude`: ```php #[DaggerFunction] public function copyDirectoryWithExclusions( #[Doc('Source directory')] Directory $source, #[Doc('Exclusion patterns')] #[ListOfType('string')] array $exclude = [], ): Container { return dag() ->container() ->from('alpine:latest') ->withDirectory('/src', $source, exclude: $exclude); } ``` The same pattern holds across the whole client. Required parameters are positional, and optional ones are named arguments with defaults. ### Workspace inputs {#workspace-inputs} When a module needs to read the user's project, whether the source tree, config files, or lockfiles, it takes a `Dagger\Workspace` argument, almost always on the constructor. You don't pass it. Dagger fills it in from the current workspace and uploads nothing up front. Dagger pulls project content only when a function actually reads a path, so a module can declare access to the whole workspace cheaply and pay only for what it touches. ```php title="src/MyModule.php" source = $ws->directory('/'); } // Functions reuse the pulled Directory like any other. #[DaggerFunction] public function build(): Container { return dag() ->container() ->from('node:20') ->withDirectory('/app', $this->source) ->withWorkdir('/app') ->withExec(['npm', 'install']) ->withExec(['npm', 'run', 'build']); } } ``` The `Workspace` client type exposes accessors for reading project content: | Accessor | Signature | Returns | |---|---|---| | `directory` | `$ws->directory(string $path, ?array $exclude = [], ?array $include = [], ?bool $gitignore = false): Directory` | a `Directory` at `$path` | | `file` | `$ws->file(string $path): File` | a `File` at `$path` | | `findUp` | `$ws->findUp(string $name, ?string $from = '.'): string` | the workspace path of `$name`, searching upward | **Path resolution.** A relative path resolves from the workspace's current working directory. An absolute path (starting with `/`) resolves from the workspace root, also called the boundary. So `$ws->directory('/')` is the whole project root, while `$ws->directory('.')` is wherever the user invoked Dagger from. **Excluding files.** `directory` takes named `exclude`, `include`, and `gitignore` arguments to filter what gets pulled. Tight filters matter for caching. Every file you load is a file whose change can invalidate the cache, so load only what the build needs: ```php #[DaggerFunction] public function __construct(Workspace $ws) { $this->source = $ws->directory( '/', exclude: ['vendor', '.git', 'dist'], // include: ['app/', 'composer.*'], // allowlist instead // gitignore: true, // apply .gitignore rules ); } ``` `findUp` walks up from a start path and returns the absolute workspace path of the first match, stopping at the workspace boundary. Relative start paths resolve from the workspace cwd; pass `from:` to change it. Use it to find a project root marker such as `composer.json`. :::tip To use the current workspace, declare a `Dagger\Workspace` argument on the module constructor or function. Dagger injects it automatically and omits it from the CLI arguments. ::: ### Default paths {#default-paths} When someone calls a function from a module directory rather than a workspace, a `Directory` or `File` argument can default to a path in the caller's project with the `#[DefaultPath]` attribute. The argument becomes optional. When the caller omits it, Dagger loads the path from the caller's context (the Git repository root for absolute paths, the module directory for relative ones): ```php use Dagger\Attribute\DefaultPath; use Dagger\Attribute\Ignore; #[DaggerFunction] #[ReturnsListOfType('string')] public function readDir( #[DefaultPath('.')] #[Ignore('vendor/', 'tests/')] Directory $source, ): array { return $source->entries(); } ``` `#[Ignore]` filters what gets loaded for a `Directory` argument, using `.gitignore` syntax. Prefer the `Workspace` pattern above for new modules. Default paths are still useful for functions that must work when no workspace is present. ### Secrets Accept sensitive values as `Dagger\Secret`, never as plain strings. Dagger scrubs secret plaintext from logs, caches, and crash reports: ```php use Dagger\Secret; #[DaggerFunction] #[Doc('Query the GitHub API')] public function githubApi( #[Doc('GitHub API token')] Secret $token, ): string { return dag() ->container() ->from('alpine:3.17') ->withSecretVariable('GITHUB_API_TOKEN', $token) ->withExec(['apk', 'add', 'curl']) ->withExec(['sh', '-c', 'curl "https://api.github.com/repos/dagger/dagger/issues" --header "Authorization: Bearer $GITHUB_API_TOKEN"']) ->stdout(); } ``` Callers supply secrets through providers on the CLI: ```shell dagger api call github-api --token=env:GITHUB_TOKEN # environment variable dagger api call github-api --token=file:./token.txt # file dagger api call github-api --token=cmd:"gh auth token" # command output dagger api call github-api --token=op://vault/item/field # 1Password ``` ### Services Return `Dagger\Service` to expose a long-running service, and bind it into other containers with `withServiceBinding`. Services are content-addressed, so a given definition always gets the same hostname and port conflicts never come up: ```php use Dagger\Service; #[DaggerFunction] #[Doc('Start and return an HTTP service')] public function httpService(): Service { return dag() ->container() ->from('python') ->withWorkdir('/srv') ->withNewFile('index.html', 'Hello, world!') ->withExposedPort(8080) ->asService(args: ['python', '-m', 'http.server', '8080']); } #[DaggerFunction] #[Doc('Send a request to an HTTP service and return the response')] public function get(): string { return dag() ->container() ->from('alpine') ->withServiceBinding('www', $this->httpService()) ->withExec(['wget', '-O-', 'http://www:8080']) ->stdout(); } ``` ### A larger example Real modules combine these pieces. This module reads the workspace once in its constructor, caches Composer downloads in a cache volume, exposes a build function, and turns a test run into a check: ```php title="src/MyModule.php" source = $ws->directory('/', exclude: ['vendor', '.git']); } #[DaggerFunction] #[Doc('Return a container with the application and its dependencies installed')] public function build(): Container { return dag() ->container() ->from('composer:2') ->withMountedCache('/tmp/cache', dag()->cacheVolume('composer')) ->withDirectory('/app', $this->source) ->withWorkdir('/app') ->withExec(['composer', 'install', '--no-interaction']); } #[DaggerFunction, Check] #[Doc('Run the test suite')] public function test(): Container { return $this->build()->withExec(['vendor/bin/phpunit']); } #[DaggerFunction] #[Doc('Return the number of failing tests, parsed from the report')] public function failures(): int { $report = $this->build() ->withExec(['vendor/bin/phpunit', '--log-junit', 'report.xml'], expect: \Dagger\ReturnType::ANY) ->file('report.xml') ->contents(); $xml = new \SimpleXMLElement($report); return (int) $xml->testsuite['failures']; } } ``` ## Module dependencies A module can depend on other Dagger modules and call them through `dag()`. Once `wolfi` is a dependency, for example, `dag()->wolfi()` works. `dagger-module.toml` records dependencies under `dependencies`: ```toml title="dagger-module.toml" template name = "dev" engineVersion = "v{{ version }}" [runtime] source = "php" [[dependencies]] name = "go" source = "../../modules/go" [[dependencies]] name = "wolfi" source = "../wolfi" ``` A `source` may be a local path (`../wolfi`) or a remote reference of the form `[proto://]host/repo[/subpath][@version]`, such as `github.com/shykes/daggerverse/hello@v0.3.0`. Manage dependencies by adding, updating, or removing `[[dependencies]]` entries in `dagger-module.toml`. After changing dependencies, [regenerate bindings](#regenerate-bindings-and-generated-files) so the new module's functions appear on `dag()`. :::note Use Composer for ordinary PHP packages (`composer require`), and Dagger for Dagger modules. Installing a Dagger module through Composer does not register it with Dagger, so you cannot call it through `dag()`. ::: ## Regenerate bindings and generated files {#regenerate-bindings-and-generated-files} A PHP module uses generated code alongside your handwritten `src/`: - `sdk/` is the typed Dagger client, including `dag()`, all core types (`Container`, `Directory`, and the rest), and every dependency's functions - `entrypoint.php` is the script the engine runs to register and call your functions - `composer.lock` and `vendor/` are the resolved Composer dependencies, including the generated client Don't edit `sdk/` or `entrypoint.php` by hand. The PHP runtime regenerates `sdk/` from the engine's schema and runs `composer install` every time it loads the module, so the client is never stale at runtime and you don't need to commit it. The generated `.gitignore` and `.gitattributes` match: ```text title=".gitignore" /sdk /vendor /.env ``` ```text title=".gitattributes" /sdk/** linguist-generated /entrypoint.php linguist-generated ``` Regenerate the local copy whenever you add or remove a dependency, bump the engine version, or want up-to-date completions in your IDE. Use `dagger generate`, which returns a changeset: ```shell # Review the regenerated files, then apply dagger generate ``` `dagger generate` discovers and runs every generator in the workspace. For modules registered under the PHP SDK, that includes binding regeneration. :::note A `.dagger-php-sdk-skip-generate` marker in a module or one of its ancestors skips PHP binding regeneration for that module. ::: ## Engine version Each module declares the Dagger engine version it requires in the `engineVersion` field of `dagger-module.toml`. Set it to a concrete version. Bumping the engine version usually means the generated bindings change too, so follow up with `dagger generate`. ## Checks, directives, and ignore patterns {#checks-directives-and-ignore-patterns} A useful, reusable module provides at least one of the three first-class function types: a check, a generator, or a service. That gives the platform verbs (`dagger check`, `dagger generate`, `dagger up`) something to run. See the [SDKs overview](./index.mdx) for the full treatment. The PHP SDK currently implements checks only: | Attribute | Return type | Run by | Purpose | |---|---|---|---| | `#[Check]` | `void` or `Container` | `dagger check` | validate the project (test/lint/scan) | There is no PHP attribute for generators (`dagger generate`) or `up` services (`dagger up`) yet. A PHP function can still return a `Changeset` or a `Service`, and you can call it explicitly with `dagger api call`, but those verbs will not discover it. If a module's main job is to generate code or start services, write it with the [Go](./go.mdx), [Python](./python.mdx), [TypeScript](./typescript.mdx), or [Dang](./dang.mdx) SDK instead. ### Attributes PHP modules use attributes from the `Dagger\Attribute` namespace to add Dagger metadata that PHP's type system can't express: | Attribute | Placement | Meaning | |---|---|---| | `#[DaggerObject]` | class | expose the class as a Dagger object | | `#[DaggerFunction]` | public method or property | expose the method as a function, or the property as a field | | `#[Doc('...')]` | class, method, or parameter | API documentation | | `#[Check]` | method (with `#[DaggerFunction]`) | mark the function as a [check](#checks) | | `#[ListOfType('T')]` | `array` parameter or property | element type of a list | | `#[ReturnsListOfType('T')]` | method returning `array` | element type of the returned list | | `#[DefaultPath('...')]` | `Directory` or `File` parameter | default the argument to a path in the caller's project | | `#[Ignore('...', ...)]` | `Directory` parameter | exclude paths when loading the argument | Attributes can be combined on one line: `#[DaggerFunction, Check]`. ### Ignore patterns A module reads the user's project through a `Workspace` argument (see [Workspace inputs](#workspace-inputs)), not a path-defaulted `Directory`. To filter what gets pulled, use the `exclude` argument when reading a workspace directory. Tight filters matter for caching, since loading less means fewer cache invalidations: ```php #[DaggerFunction] public function __construct(Workspace $ws) { $this->source = $ws->directory('/', exclude: ['vendor', '.git', 'dist']); } ``` For `Directory` arguments that callers pass explicitly (or that use `#[DefaultPath]`), the `#[Ignore]` attribute applies the same filtering with `.gitignore` syntax; see [Default paths](#default-paths). ### Checks {#checks} Add the `#[Check]` attribute next to `#[DaggerFunction]` to make a function a check, a validation function (test, lint, scan) that takes no required arguments. `dagger check` discovers and runs every check a module exposes. A check must return `void` or `Container`. It fails when it throws an exception or when the returned `Container` exits non-zero. ```php use Dagger\Attribute\Check; #[DaggerFunction, Check] #[Doc('Lint the project')] public function lint(): void { $output = dag() ->container() ->from('php:8.4-cli-alpine') ->withMountedDirectory('/src', $this->source) ->withWorkdir('/src') ->withExec(['sh', '-c', 'find . -name "*.php" -not -path "./vendor/*" -exec php -l {} \;']) ->stdout(); if (str_contains($output, 'Parse error')) { throw new \RuntimeException($output); } } // A check can also return a container; a non-zero exit fails the check. #[DaggerFunction, Check] #[Doc('Run the test suite')] public function test(): Container { return $this->build()->withExec(['vendor/bin/phpunit']); } ``` You can also declare checks on custom object types to group them, for example a `Test` object with `lint` and `unit` checks. List a module's checks with `dagger check -l`, and run a subset by name pattern with `dagger check 'test*'`. ## Testing PHP modules Because a PHP module is an ordinary Composer package, you can test it two ways. Unit tests are fast and need no engine. Checks need an engine, but they test the thing you actually ship. ### Idiomatic PHP tests You can unit-test functions that contain pure PHP logic, such as parsing a report or formatting a summary, with [PHPUnit](https://phpunit.de/) and no engine involved. Add it as a development dependency and put tests in a `tests/` directory: ```shell composer require --dev phpunit/phpunit ``` ```php title="tests/IssueTest.php" assertSame('app/main.php:12 error: undefined variable', $issue->summary()); } } ``` Run them like any PHPUnit suite. They don't need the engine, but they do need the generated client installed locally (see [IDE and Composer setup](#ide-and-composer-setup)): ```shell vendor/bin/phpunit tests ``` ### Functional tests via checks For behavior that exercises containers and the Dagger API, write functions in your module and call them, or make them checks so they run under `dagger check`. A check that builds, lints, or tests your project is both a CI gate and a smoke test: ```shell # Smoke test: does it build? dagger api call build # Run all checks dagger check # Run generators and confirm there's no drift dagger check --generate ``` ### In CI Run `dagger check` in CI to run every check the module exposes. The heavy lifting happens in content-addressed containers, so the same command behaves the same on a laptop and on a CI runner, with full caching: ```yaml title=".github/workflows/ci.yml" jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dagger/dagger-for-github@v6 with: verb: check ``` ## IDE and Composer setup {#ide-and-composer-setup} The module's `composer.json` resolves the `dagger/dagger` client from the local `./sdk` path repository. Because `sdk/` and `vendor/` are gitignored, a fresh checkout has neither, and your editor cannot resolve `Dagger\Container` or `dag()` until they exist. To get autocompletion and go-to-definition: ```shell # Materialize the generated client into ./sdk dagger generate # Install it, and any other dependencies, into ./vendor composer install ``` Repeat `dagger generate` after adding a dependency or bumping the engine version so the local client matches what the runtime will generate. Local development needs PHP 8.2 or newer and [Composer](https://getcomposer.org/). The runtime container uses PHP 8.4, so anything that runs locally on a recent PHP also runs in the engine. Third-party packages are ordinary Composer dependencies. Add them with `composer require`; the runtime runs `composer install` when it loads the module, so they are available inside your functions. Keep `composer.json` and `composer.lock` in version control. :::tip Do not publish Dagger modules to Packagist. Consumers install modules through Dagger (`dagger module install`), not Composer. Dagger does not register a module pulled in as a Composer package, so you cannot call its functions. ::: ## Packaging and release You distribute a PHP SDK module as a Git repository. There is no build artifact to publish. Consumers fetch the source by reference, and the runtime generates the client and installs Composer dependencies when it loads the module. Recommended release checklist: 1. **Pin the engine version.** Set `engineVersion` in `dagger-module.toml` to the oldest engine version your module supports. 2. **Commit the lockfile.** Run `dagger generate`, review the changes, and commit `composer.json` and `composer.lock` with your module. `sdk/` and `vendor/` stay gitignored. 3. **Version with Git tags.** Tag a release (for example, `v1.2.0`) and push it. Consumers can pin that version with `@v1.2.0`. Before publishing, run `dagger check --generate` to confirm that the committed files are up to date. Consumers can install your module into a workspace with: ```shell dagger module install github.com/you/your-module@v1.2.0 ``` To add it as a dependency of another module, add a `[[dependencies]]` entry to that module's `dagger-module.toml`, then run `dagger generate`. A module reference follows `[proto://]host/repo[/subpath][@version]`. The version may be a tag, branch, or commit, and Dagger resolves it over HTTPS or SSH depending on the authentication available. ## Troubleshooting **`dagger init` / `dagger develop` not found.** Install the PHP SDK with `dagger module install dagger.io/sdk/php`, scaffold with `dagger module init php --name `, and regenerate with `dagger generate`. **Nothing was written after `dagger module init`.** The command returns a changeset. Review and accept it, or rerun with `-y` to apply without prompting. **A function doesn't show up.** It must be `public`, carry the `#[DaggerFunction]` attribute, and live on a class marked `#[DaggerObject]` under `src/`. Every parameter and the return value need a type hint. **"Argument ... cannot be supported without a typehint" or "cannot be supported without a return type".** Add the missing type declaration; the SDK builds the API schema from PHP types, not docblocks. **A missing `ListOfType` / `ReturnsListOfType` attribute error.** `array` parameters, fields, and return values must declare their element type with the matching attribute. **A new dependency doesn't show up on `dag()`.** Run `dagger generate`, then `composer install` locally. The functions available on `dag()` come from the generated `sdk/` client, which must match `dagger-module.toml`. **`dagger generate` does not regenerate bindings.** Look for a `.dagger-php-sdk-skip-generate` marker in the module or one of its ancestors. **Editor cannot resolve `Dagger\...` classes or `dag()`.** The generated client is missing locally. Run `dagger generate` and `composer install`; both `sdk/` and `vendor/` are gitignored, so this is expected on a fresh clone. **A check is rejected at registration.** Checks take no required arguments (give every argument a default or make it nullable) and must return `void` or `Container`. **Engine version mismatch.** Align the module's `engineVersion` in `dagger-module.toml`, then regenerate. ## Next steps - [SDKs overview](./index.mdx) covers platform concepts that apply to every SDK - [Types](../api/index.mdx) - [Generating code](../../using/generating.mdx) - [Quickstart](../../getting-started/quickstart.mdx) --- # Python SDK URL: https://docs.dagger.io/reference/sdks/python # Python SDK The Python SDK lets you write Dagger modules in Python. You declare objects with `@object_type` and expose functions with `@function`; the SDK turns your type hints into a Dagger API schema, and generates a typed client (`dagger.dag`) for calling the Dagger API and any module dependencies. This page is the standalone reference for developing modules with Python. For platform concepts that apply to every SDK, start with the [SDKs overview](./index.mdx) and the [Types](../api/index.mdx) reference. ## The Python SDK is a module The **Python SDK module**, `dagger.io/sdk/python`, supplies the generation code. Install it once, then use the CLI module commands: ```shell # Install the Python SDK into your workspace (once) dagger module install dagger.io/sdk/python # Create a module dagger module init python --name my-module ``` ```shell dagger module init python --help ``` The commands you use most often: | Command | What it does | |---|---| | `dagger module init python` | Create a module and generate its bindings. | | `dagger module client add`, `rm`, `update`, `list` | Manage the module's clients from its directory. | | `dagger generate` | Regenerate registered modules and clients. | | `dagger sdk scope list --sdk=python --is-module` | List Python modules. | The examples below are run from a project working directory (the directory that contains, or will contain, your `.dagger` folder). ## Create a module :::note Run these commands from inside a Git repository. That's where the new module is created. ::: Install the Python SDK into your workspace, then create a new module: ```shell dagger module install dagger.io/sdk/python dagger module init python --name=my-module ``` `dagger module init` returns a [changeset](../../using/generating.mdx), a diff of the files to write. Dagger shows it to you to review before anything is written to disk. `init` arguments: | Argument | Required | Description | |---|---|---| | `--name` | no | Module name. Without `--name` or `--path`, inferred as `-dev` and installed as the workspace entrypoint. | | `--path` | no | Where to create the module. Default: `/.dagger/modules/`. A custom path is registered for generation but is not installed. | | `--template` | no | Select `default`, `empty`, or `legacy`. Defaults to `default`. | | `--python-version` | no | Pin the Python version written into `pyproject.toml`. | | `--use-uv` | no | Enable [uv](https://docs.astral.sh/uv/) for the generated project (default on). | | `--base-image` | no | Override the runtime base image in `pyproject.toml`. | By default the module is created at `/.dagger/modules/`. Commit the generated SDK files with the module. ### What `init` produces A minimal Python module looks like: ``` my-module/ ├── dagger-module.toml # module metadata ├── pyproject.toml # Python project + Dagger build config ├── src/ │ └── my_module/ │ └── __init__.py # @object_type / @function code └── sdk/ # generated dagger-io client (checked in by default) ``` :::note `dagger module init` writes the source and generates the `sdk/` client in the same changeset. Commit the generated files with the module. ::: `dagger-module.toml` declares the SDK source: ```toml title="dagger-module.toml" template name = "my-module" engineVersion = "v{{ version }}" [runtime] source = "python" ``` A module can use a single `@object_type` whose constructor reads the current workspace into a `source` field (see [Workspace inputs](#workspace-inputs)): ```python title="src/my_module/__init__.py" import dagger from dagger import object_type @object_type class MyModule: source: dagger.Directory def __init__(self, ws: dagger.Workspace): self.source = ws.directory("/") ``` This example exposes no functions yet. Add `@function` methods to make it callable. The [next section](#define-objects-and-functions) shows how. Function and argument names are converted from `snake_case` (Python) to `kebab-case` on the CLI (and `camelCase` in the GraphQL API) automatically: a method `container_echo` becomes `container-echo`, and a parameter `string_arg` becomes `--string-arg`. ## Define objects and functions A module is a Python class decorated with `@object_type`. The class named after the module (PascalCase of the module name) is the **main object**. Its functions are the module's entry points. Functions are instance methods decorated with `@function`. ```python import dagger from dagger import dag, function, object_type @object_type class MyModule: @function def build(self, source: dagger.Directory) -> dagger.Container: return ( dag.container() .from_("node:20") .with_directory("/app", source) .with_workdir("/app") .with_exec(["npm", "install"]) .with_exec(["npm", "run", "build"]) ) ``` Functions may be synchronous or `async`. Use `async def` whenever you `await` a Dagger API call that returns a leaf value (`stdout()`, `entries()`, `sync()`, `publish()`, …). Lazy chains that return Dagger objects (`Container`, `Directory`, …) do not need to be awaited and can be returned directly. You can run multiple Dagger calls concurrently with `anyio` task groups or `asyncio.gather`, since each call is an independent coroutine. ### Fields, state, and constructors Class attributes with type annotations become **fields**. By default fields are private state. Use `field()` to expose an attribute through the Dagger API (so callers can read it), and to give it a default: ```python from typing import Annotated from dagger import Doc, field, function, object_type @object_type class MyModule: """Functions for greeting the world""" greeting: Annotated[str, Doc("The greeting to use")] = field(default="Hello") name: Annotated[str, Doc("Who to greet")] = "World" @function def message(self) -> str: """Return the greeting message""" return f"{self.greeting}, {self.name}!" ``` The set of fields that have a value at construction time forms the module's **constructor**. Callers pass them as top-level flags: ```shell dagger api call my-module --greeting=Hi --name=Dagger message ``` `@object_type` builds on dataclasses, so you can define an explicit `__init__` to derive state from the constructor arguments, and `field(init=False)` for computed fields: ```python from typing import Annotated import dagger from dagger import Doc, dag, field, object_type @object_type class MyModule: source: dagger.Directory = field(init=False) container: Annotated[ dagger.Container, Doc("The container for the workspace"), ] = field(init=False) def __init__( self, ws: dagger.Workspace, token: Annotated[dagger.Secret | None, Doc("GitHub API token")] = None, ): self.source = ws.directory("/") self.token = token self.container = ( dag.container() .from_("python:3.11") .with_workdir("/app") .with_directory("/app", self.source) .with_mounted_cache("/root/.cache/pip", dag.cache_volume("python-pip")) .with_exec(["pip", "install", "-r", "requirements.txt"]) ) ``` The `ws: dagger.Workspace` argument is special. Dagger **auto-populates** it from the current workspace. The caller passes nothing for it, and no project files are uploaded up front. The module pulls only the paths it asks for. See [Workspace inputs](#workspace-inputs) below. ### Custom object types Define additional `@object_type` classes to model your domain and return structured results. Use `field()` to expose their attributes, and add `@function` methods for computed values: ```python import dagger from dagger import dag, field, function, object_type @object_type class Account: username: str = field() email: str = field() @function def url(self) -> str: return f"https://github.com/{self.username}" @object_type class Organization: url: str = field() repositories: list[dagger.GitRepository] = field() members: list[Account] = field() @object_type class MyModule: @function def dagger_organization(self) -> Organization: url = "https://github.com/dagger" return Organization( url=url, repositories=[dag.git(f"{url}/dagger")], members=[ Account(username="jane", email="jane@example.com"), Account(username="john", email="john@example.com"), ], ) ``` Custom type names are namespaced in the schema by the module's main object (e.g. `MyModuleAccount`) to avoid conflicts when multiple modules are loaded together. ## Arguments and return values Function parameters become typed arguments. Return type hints become the function's return type. Python types map to Dagger types as follows: | Python | Dagger / GraphQL | Notes | |---|---|---| | `str` | `String` | | | `int` | `Int` | | | `float` | `Float` | | | `bool` | `Boolean` | | | `list[T]` | list of `T` | e.g. `list[str]`, `list[dagger.File]` | | `dagger.Container` | `Container` | core type | | `dagger.Directory` | `Directory` | core type | | `dagger.File` | `File` | core type | | `dagger.Secret` | `Secret` | core type | | `dagger.Service` | `Service` | core type | | your `@object_type` | object | custom type | | your `@enum_type` | enum | custom enum | | `None` / `-> None` | `Void` | | ### Documentation Docstrings document the module, objects, and functions. Use `typing.Annotated` with `Doc(...)` to document arguments and fields: ```python """A simple example module to say hello. Further documentation for the module here. """ from typing import Annotated from dagger import Doc, function, object_type @object_type class MyModule: """Simple hello functions.""" @function def hello( self, name: Annotated[str, Doc("Who to greet")], greeting: Annotated[str, Doc("The greeting to display")], ) -> str: """Return a greeting.""" return f"{greeting}, {name}!" ``` These descriptions appear in `dagger api functions` and `dagger api call --help`. ### Defaults and nullability A parameter with a Python default value is **optional**: ```python @function def build(self, node_version: str = "20") -> dagger.Container: ... ``` ```shell dagger api call my-module build # uses "20" dagger api call my-module build --node-version=18 ``` A parameter without a default is **required**. To make an argument nullable (accepts no value, defaulting to `None`), use `T | None`: ```python from typing import Annotated from dagger import Doc @function def deploy( self, token: Annotated[dagger.Secret | None, Doc("Optional token")] = None, ) -> str: ... ``` ### Renaming and deprecating arguments When a Python name collides with a builtin or you want a different external name, annotate with `Name`. Mark arguments as deprecated with `Deprecated`: ```python from typing import Annotated from dagger import Doc, Name, Deprecated @function def fetch( self, from_: Annotated[str, Name("from"), Doc("Source URL")], legacy: Annotated[str | None, Deprecated("use 'from' instead")] = None, ) -> str: ... ``` ### Enums Define a validated set of string values with `@enum_type` on an `enum.Enum` subclass. Member docstrings document each value: ```python import enum from dagger import dag, enum_type, function, object_type @enum_type class Severity(enum.Enum): """Vulnerability severity levels""" LOW = "LOW" """Minimal risk; routine fix""" HIGH = "HIGH" """Serious risk; quick fix needed.""" CRITICAL = "CRITICAL" """Severe risk; immediate action.""" @object_type class MyModule: @function def scan(self, ref: str, severity: Severity) -> str: return ( dag.container() .from_("aquasec/trivy:0.50.4") .with_exec(["trivy", "image", "--severity=" + severity.value, ref]) .stdout() ) ``` Invalid values are rejected with an error listing the allowed choices. ### Interfaces Interfaces let your module accept arbitrary objects from other modules without depending on their concrete types. Declare one with `@interface` on a `typing.Protocol` subclass, listing the `@function` methods you need (signatures with `...` bodies). Any object that provides matching functions can be passed in: ```python import typing import dagger from dagger import dag, function, interface, object_type @interface class Duck(typing.Protocol): @function async def quack(self) -> str: ... @object_type class MyModule: # Accept any object that satisfies the Duck interface. @function async def quack_it(self, duck: Duck) -> str: return await duck.quack() # A function can also return an interface value. @function def get_duck(self) -> Duck: return dag.mallard() ``` The `typing.Protocol` base is what marks this as a Dagger interface rather than a plain Python protocol. Any object whose functions match the protocol, including objects returned by other modules, can be passed where the interface is expected. ## Core Dagger types The generated client `dag` exposes the full Dagger API. The most common core types are `Container`, `Directory`, `File`, `Secret`, `Service`, and `CacheVolume`. They are immutable and lazy: each `with_*` method returns a new value, and nothing executes until you `await` a leaf operation. ### Workspace inputs A module reads the user's project through a **workspace**. Declare a `dagger.Workspace` argument on the module's constructor and Dagger **auto-populates** it from the current workspace. The caller passes nothing for it, and nothing is uploaded up front. The module pulls project content **lazily, on demand**, so only the paths you actually read enter the runtime. Read content from the workspace with these accessors: | Accessor | Returns | Notes | |---|---|---| | `ws.directory(path)` | `dagger.Directory` | Pull a directory. Accepts `exclude` / `include` patterns and `gitignore`. | | `ws.file(path)` | `dagger.File` | Pull a single file. | | `ws.find_up(name)` | `str \| None` (await) | Walk up looking for `name`; returns the path or `None`. | Paths resolve **relative to the workspace cwd**; paths that start with `/` resolve from the **workspace root**. So `ws.directory("/")` pulls the whole project root, while `ws.directory("src")` pulls `src` relative to where the workspace was invoked. The idiomatic pattern is to store `ws.directory("/")` as the module's source directory in the constructor, then have functions build on it: ```python import dagger from dagger import dag, field, function, object_type @object_type class MyModule: source: dagger.Directory = field(init=False) def __init__(self, ws: dagger.Workspace): # Pull only what you need; exclude prunes inputs to keep cache keys tight. self.source = ws.directory("/", exclude=["node_modules", ".git", "dist"]) @function def build(self) -> dagger.Container: return ( dag.container() .from_("node:20") .with_directory("/app", self.source) .with_workdir("/app") .with_exec(["npm", "ci"]) .with_exec(["npm", "run", "build"]) ) ``` `ws.directory(...)` and `ws.file(...)` are lazy and return Dagger objects directly (no `await`). `ws.find_up(...)` returns a leaf value and must be awaited from an `async` function: ```python @function async def config_path(self, ws: dagger.Workspace) -> str | None: return await ws.find_up("pyproject.toml") ``` **Push file access to the leaves and use `exclude`.** Only pull the paths you actually need, and prune with the `exclude` (or `include`) option. The less you load, the fewer cache invalidations. :::note `directory()` and `file()` are synchronous (they return Dagger objects); `find_up()` is `async`. The exact `exclude`/`include`/`gitignore` option names come from your generated `sdk/` client and may vary with your engine version. ::: ### Secrets Accept credentials as `dagger.Secret`, never as `str`. Secrets are scrubbed from logs, caches, and crash reports: ```python from typing import Annotated import dagger from dagger import Doc, dag, function, object_type @object_type class MyModule: @function async def github_api( self, token: Annotated[dagger.Secret, Doc("GitHub API token")], ) -> str: """Query the GitHub API""" return await ( dag.container() .from_("alpine:3.17") .with_secret_variable("GITHUB_API_TOKEN", token) .with_exec(["apk", "add", "curl"]) .with_exec( [ "sh", "-c", 'curl -H "Authorization: Bearer $GITHUB_API_TOKEN" ' "https://api.github.com/repos/dagger/dagger/issues", ] ) .stdout() ) ``` Callers supply secrets via providers (`env:`, `file:`, `cmd:`, `op://`, `vault://`, …): ```shell dagger api call my-module github-api --token=env:GITHUB_TOKEN ``` ### Services Return `dagger.Service` and bind services into other containers with `with_service_binding`: ```python import dagger from dagger import dag, function, object_type @object_type class MyModule: @function def http_service(self) -> dagger.Service: """Start and return an HTTP service.""" return ( dag.container() .from_("python") .with_workdir("/srv") .with_new_file("index.html", "Hello, world!") .with_exposed_port(8080) .as_service(args=["python", "-m", "http.server", "8080"]) ) @function async def get(self) -> str: """Send a request to an HTTP service and return the response.""" return await ( dag.container() .from_("alpine") .with_service_binding("www", self.http_service()) .with_exec(["wget", "-O-", "http://www:8080"]) .stdout() ) ``` Services are content-addressed: the same definition always resolves to the same hostname, so there are no port conflicts. ## Module dependencies Modules can depend on other modules. Manage their clients from the module directory: ```shell cd .dagger/modules/my-module dagger module client add github.com/shykes/daggerverse/hello@v0.3.0 --sdk=python dagger module client list --sdk=python dagger module client update --sdk=python dagger module client rm github.com/shykes/daggerverse/hello@v0.3.0 --sdk=python ``` Client add and remove commands update targets in `dagger.toml`. Client update refreshes `dagger.lock`. All three regenerate the module. Review and apply the changeset. The SDK writes the runtime dependencies into `dagger-module.toml`. A module reference follows `[proto://]host/repo[/subpath][@version]`; `@version` may be a tag, branch, or commit. Local dependencies use a relative path source (e.g. `./path/to/module`). Adding a client also regenerates bindings, so its module appears on `dag` as a typed function: ```python @object_type class MyModule: @function async def greeting(self) -> str: # 'hello' dependency is available on dag after regeneration return await dag.hello().hello() ``` ## Regenerate bindings The generated client (the `sdk/` directory and `dagger`/`dag` types) reflects the engine API plus your dependencies. Regenerate it after changing dependencies or the required engine version. `dagger generate` returns a changeset: ```shell dagger generate ``` `dagger generate` runs registered SDK scopes and author-defined generators in the workspace, with one changeset for review. Commit the generated SDK files. Run `dagger generate` and commit the result when you change the module API or engine version. ## Engine version `dagger-module.toml` records the Dagger engine version your module requires (`engineVersion`). Edit that field to change the requirement. Then run `dagger generate` to refresh the bindings. ## Checks, generators, services, directives, and ignore ### Checks, generators, and services Dagger has three first-class function types, each marked by stacking a decorator with `@function` and each run by its own verb. A useful reusable module provides at least one of them: | Decorator | Return type | Run by | Purpose | |---|---|---|---| | `@check` | `str` (or any value; non-zero exit fails) | `dagger check` | Validate something (lint, test). | | `@generate` | `dagger.Changeset` | `dagger generate` | Produce a changeset of edits to the source. | | `@up` | `dagger.Service` | `dagger up` | Start a long-running service. | All three decorators are exported from `dagger.mod` and re-exported from `dagger`, so you can import them directly: ```python from dagger import check, generate, up ``` A **check** validates something and fails the run on a non-zero exit. Combine `@function` with `@check`: ```python import dagger from dagger import check, dag, field, function, object_type @object_type class MyModule: source: dagger.Directory = field(init=False) def __init__(self, ws: dagger.Workspace): self.source = ws.directory("/") @function @check async def lint(self) -> str: """Lint the code""" return await ( dag.container() .from_("python:3.12") .with_directory("/app", self.source) .with_workdir("/app") .with_exec(["pip", "install", "ruff"]) .with_exec(["ruff", "check", "."]) .stdout() ) @function @check async def test(self) -> str: """Run unit tests""" return await ( dag.container() .from_("python:3.12") .with_directory("/app", self.source) .with_workdir("/app") .with_exec(["pip", "install", "pytest", "-e", "."]) .with_exec(["pytest"]) .stdout() ) ``` Apply both decorators with `@function` on top, then `@check` directly above the method. `dagger check` discovers and runs every check. A **generator** returns a `dagger.Changeset` describing changes to apply to the source. Combine `@function` with `@generate`: ```python import dagger from dagger import dag, field, function, generate, object_type @object_type class MyModule: source: dagger.Directory = field(init=False) def __init__(self, ws: dagger.Workspace): self.source = ws.directory("/") @function @generate def codegen(self) -> dagger.Changeset: """Generate API client code""" generated = ( dag.container() .from_("python:3.12") .with_directory("/app", self.source) .with_workdir("/app") .with_exec(["python", "scripts/codegen.py"]) .directory("/app") ) return generated.changes(self.source) ``` `dagger generate` runs all author-written `@generate` functions across the project and presents the unified changeset for review and apply. :::note A `@generate` function is your own generator. `dagger generate` runs these functions and the registered SDK scopes that regenerate the Python client in `sdk/`. See [Regenerate bindings](#regenerate-bindings). ::: A **service** returns a `dagger.Service` and is started by `dagger up`. Combine `@function` with `@up`: ```python import dagger from dagger import dag, function, object_type, up @object_type class MyModule: @function @up def database(self) -> dagger.Service: """Returns a postgres database service""" return ( dag.container() .from_("postgres:16") .with_exposed_port(5432) .as_service() ) ``` `dagger up` starts every `@up` service and exposes its ports. A plain `@function` returning `dagger.Service` can still be bound into other containers with `with_service_binding`, but only `@up` functions are started by `dagger up`. See [Services](#services). See [Generating code](../../using/generating.mdx). ### Directives The `Name`, `Deprecated`, and `Doc` annotations used above are the Python expression of Dagger's directives. They are applied via `typing.Annotated` (except `Doc`, which can be a plain annotation). The `@check`, `@generate`, and `@up` decorators mark function behavior. ### Ignoring inputs When you pull project files from a workspace, prune what enters the runtime with the `exclude` (or `include`) option on `ws.directory(...)`: ```python def __init__(self, ws: dagger.Workspace): self.source = ws.directory( "/src", exclude=["**/__pycache__", "**/*.pyc", ".venv", ".git"], ) ``` `exclude` patterns reduce what is uploaded into the runtime container, which keeps cache keys stable. Pull the narrowest path you need, `ws.directory("/src")` instead of `ws.directory("/")`, and exclude the rest. See [Workspace inputs](#workspace-inputs). ## Testing Python modules The most direct test is to call your functions and assert on the output: ```shell # Smoke test: does it build? dagger api call my-module build # Run all checks dagger check # Confirm generated code is up to date dagger check --generate ``` You can also write Python tests that run inside a container built by your module, executed as a check so they run with `dagger check` and in CI: ```python import dagger from dagger import dag, field, function, object_type @object_type class MyModule: source: dagger.Directory = field(init=False) def __init__(self, ws: dagger.Workspace): self.source = ws.directory("/") def _app(self) -> dagger.Container: return ( dag.container() .from_("python:3.12") .with_directory("/app", self.source) .with_workdir("/app") .with_exec(["pip", "install", "-e", ".[test]"]) ) @function @check async def unit_test(self) -> str: """Run pytest""" return await self._app().with_exec(["pytest", "-q"]).stdout() @function @check async def integration_test(self) -> str: """Run tests against a Postgres service""" db = ( dag.container() .from_("postgres:16") .with_env_variable("POSTGRES_PASSWORD", "test") .with_exposed_port(5432) .as_service() ) return await ( self._app() .with_service_binding("db", db) .with_env_variable("DATABASE_URL", "postgres://postgres:test@db:5432/postgres") .with_exec(["pytest", "-q", "-m", "integration"]) .stdout() ) ``` ### CI In CI, run `dagger check` to execute every check, and `dagger check --generate` to confirm generated code is up to date: ```yaml title=".github/workflows/ci.yml" template jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dagger/dagger-for-github@v6 with: version: "v{{ version }}" verb: check ``` Because all checks run in containers, local runs and CI runs are identical. ## IDE and package-manager setup For autocompletion and type checking, all dependencies must be installed in an activated [virtual environment](https://packaging.python.org/en/latest/tutorials/installing-packages/#creating-virtual-environments) (typically `.venv`, next to `pyproject.toml`). That includes the generated `dagger-io` client in `./sdk`. Make sure the `sdk/` directory has been generated (see [Regenerate bindings](#regenerate-bindings)) before installing. To open an editor with working completions: ```shell uv run code . # VS Code; replace 'code' with 'vim' for terminal editors ``` ### Project environment When using a `uv.lock`: ```shell uv sync ``` For an older module without `uv.lock`: ```shell uv add --editable ./sdk rm requirements.lock ``` :::note With `uv.lock`, the SDK library (`dagger-io`, generated in `./sdk`) must be a **production** dependency, unlike the other methods where it is a development dependency. ::: When using a `requirements.lock`, uv's [pip interface](https://docs.astral.sh/uv/pip/) manages everything: ```shell uv venv uv pip install -r requirements.lock -e ./sdk -e . ``` To pin a Python version (e.g. 3.12) both locally and in Dagger: ```shell echo 3.12 > .python-version ``` Match the Python version Dagger uses (3.13 by default, unless overridden in `pyproject.toml` or `.python-version`): ```shell python -m venv .venv source .venv/bin/activate python -m pip install -r requirements.lock -e ./sdk -e . ``` :::tip If `.venv` lives inside the module, add it to `.gitignore` **and** to `"include": ["!.venv"]` in `dagger-module.toml` so it isn't uploaded to the runtime container. ::: ### Python build configuration (`pyproject.toml`) `dagger-module.toml` declares `runtime.source: "python"`; the Python-specific build settings (Python version, base image, whether to use uv) live in `pyproject.toml` and can be read or edited with `mod config`: ```shell # Read the current configuration dagger api call python-sdk mod --path .dagger/modules/my-module config get # Set values at once (returns a changeset) dagger api call python-sdk \ mod --path .dagger/modules/my-module config set --python-version=3.12 --use-uv ``` `mod config set` accepts `--python-version`, `--base-image`, and `--use-uv`. You can also pass `--python-version`, `--use-uv`, and `--base-image` at `init` time. ## Packaging and release Modules are distributed as Git repositories. There is no package registry step. Tag a release in your repo, and consumers reference the module by `host/repo[/subpath]@version`. Set `engineVersion` in `dagger-module.toml` and commit generated bindings before tagging: ```shell template dagger generate git add -A && git commit -m "release" && git tag v0.1.0 && git push --tags ``` Consumers install your module into their workspace: ```shell dagger module install github.com/you/your-module@v0.1.0 ``` or call it ad hoc: ```shell dagger -m github.com/you/your-module@v0.1.0 api call ... ``` ## Troubleshooting - **`dagger init` / `dagger develop` not found.** Install the Python SDK with `dagger module install dagger.io/sdk/python`, then use `dagger module init python` and `dagger generate`. - **`init` "didn't write any files."** `init` returns a changeset; review and apply it to write the files into your workspace. - **IDE has no autocompletion / `import dagger` unresolved.** The `sdk/` client must be generated and installed into an active `.venv`. Run `dagger generate` and `uv sync` / `uv pip install -e ./sdk -e .`. - **A dependency isn't showing up on `dag`.** Add it with `dagger module client add --sdk=python` from the module directory, then apply the changeset. - **Engine/API version mismatch errors after upgrading.** Edit `engineVersion` in `dagger-module.toml`, then run `dagger generate` and commit. - **Wrong Python version in the runtime.** Set it with `mod config set --python-version=…` (or `.python-version`) and confirm with `mod config get`. - **Secrets leaking or rejected.** Type credential arguments as `dagger.Secret`, not `str`, and pass them via a provider (`env:`, `file:`, …). Dagger scrubs `Secret` values from output. ## Next steps - [SDKs overview](./index.mdx) - [Types](../api/index.mdx) - [Generating code](../../using/generating.mdx) --- # TypeScript SDK URL: https://docs.dagger.io/reference/sdks/typescript # TypeScript SDK :::note The TypeScript SDK module still uses the previous beta SDK interface. It needs an update before the current `dagger module init` and module client commands can use it. The module runtime is unchanged. ::: The TypeScript SDK lets you write Dagger modules in TypeScript. You define an `@object()` class with `@func()` methods, and Dagger exposes those methods as API functions that anyone can call from the CLI, from another module, or over the API. Inside your functions you orchestrate containers, files, services, and secrets through a generated, fully-typed client. This page assumes you already understand the Dagger module model. If you don't, read these first. This page stays light on platform concepts and links back where relevant: - [SDKs overview](./index.mdx) covers what a module is and how it runs - [Types](../api/index.mdx) covers how language types map to the Dagger API - [Generating code](../../using/generating.mdx) covers how generated files are reviewed and applied :::important **The TypeScript SDK is itself a Dagger module**, `dagger.io/sdk/typescript`. The CLI module commands use an installed SDK for source and client generation. ::: ## Create a module :::note Run these commands from inside a Git repository. That's where the new module is created. ::: Install the TypeScript SDK into your workspace, then use the module initialization command: ```shell dagger module install dagger.io/sdk/typescript dagger module init typescript --name=my-module ``` `dagger module init` returns a [changeset](../../using/generating.mdx) describing the files to create, which Dagger shows you to review before anything is written to disk. `dagger module init` arguments (SDK-specific options require a compatible SDK): | Argument | Required | Description | |---|---|---| | `--name` | no | Module name. Without `--name` or `--path`, inferred as `-dev` and installed as the workspace entrypoint. | | `--path` | no | Where to create the module. Defaults to `/.dagger/modules/`. | By default the module is created beside the active `dagger.toml`: ``` /.dagger/modules/my-module ``` Pass `--path` to choose a different location. A custom path is registered for generation but is not installed; use `dagger module install ` to install it. ### File layout Once created and generated, a Node module looks like this: ``` my-module/ ├── dagger-module.toml # module config ├── package.json # npm/yarn/pnpm package definition ├── tsconfig.json # TypeScript config with the @dagger.io/dagger path alias ├── sdk/ # generated TypeScript bindings (the typed client) └── src/ └── index.ts # your module code ``` :::note `dagger module init` runs generation as part of initialization. Source files and generated bindings are part of the same changeset. ::: `dagger-module.toml` declares the SDK source: ```toml title="dagger-module.toml" template name = "my-module" engineVersion = "v{{ version }}" [runtime] source = "typescript" ``` `tsconfig.json` wires the `@dagger.io/dagger` import to the local generated SDK so editors resolve types without a separate install: ```json title="tsconfig.json" { "compilerOptions": { "target": "ES2022", "moduleResolution": "Node", "experimentalDecorators": true, "strict": true, "skipLibCheck": true, "paths": { "@dagger.io/dagger": ["./sdk/index.ts"], "@dagger.io/dagger/telemetry": ["./sdk/telemetry.ts"] } } } ``` `package.json` declares the package as an ES module: ```json title="package.json" { "type": "module" } ``` ## Define objects and functions A module is a class decorated with `@object()`. Methods decorated with `@func()` become Dagger API functions. The first `@object()` class is the module's main object; its name in the API is derived from the module name. ```typescript title="src/index.ts" import { dag, Container, Directory, object, func } from "@dagger.io/dagger" @object() class MyModule { /** * Build and publish a container image, returning the published ref */ @func() async build(src: Directory): Promise { const builder = dag .container() .from("golang:latest") .withDirectory("/src", src) .withWorkdir("/src") .withEnvVariable("CGO_ENABLED", "0") .withExec(["go", "build", "-o", "myapp"]) const prodImage = dag .container() .from("alpine") .withFile("/bin/myapp", builder.file("/src/myapp")) .withEntrypoint(["/bin/myapp"]) return await prodImage.publish("ttl.sh/myapp:latest") } } ``` Call it like any other module: ```shell dagger -m ./.dagger/modules/my-module api call build --src=. ``` Key points: - `dag` is the generated Dagger client. Every core type (`Container`, `Directory`, `File`, `Service`, `Secret`, …) is imported from `@dagger.io/dagger`. - The Dagger API is lazy and immutable. Each builder method returns a new value and nothing executes until you `await` a leaf operation (`publish`, `stdout`, `sync`, `entries`, …). You can return an unresolved `Container`/`Directory` and the caller decides when to evaluate it. - A `@func()` may be `async` and return a `Promise`, or return a lazy Dagger type directly. ### The constructor A class constructor becomes the module's constructor. Its parameters become arguments that callers (or workspace config) can set when they reference the module. Store them as fields so other functions can use them. ```typescript import { dag, Directory, object, func } from "@dagger.io/dagger" @object() class MyModule { source: Directory nodeVersion: string constructor(source: Directory, nodeVersion = "20") { this.source = source this.nodeVersion = nodeVersion } @func() build(): Container { return dag .container() .from(`node:${this.nodeVersion}`) .withDirectory("/app", this.source) .withWorkdir("/app") .withExec(["npm", "ci"]) .withExec(["npm", "run", "build"]) } } ``` A field decorated with `@func()` is exposed as a readable API field; an undecorated field is private internal state. ### Workspace inputs To read the user's project files, give your `@object()` constructor a `Workspace` parameter. Dagger **auto-populates** it from the current workspace. The caller passes nothing, and nothing is uploaded up front. The module then reads project content **lazily, on demand**, so only the paths you actually touch are loaded (and become part of the cache key). ```typescript import { dag, Container, Directory, Workspace, object, func } from "@dagger.io/dagger" @object() class MyModule { source: Directory // ws is auto-populated from the current workspace; the caller passes nothing. constructor(ws: Workspace) { // Read the workspace root as the module's source directory. this.source = ws.directory("/") } @func() build(): Container { return dag .container() .from("node:20") .withDirectory("/app", this.source) .withWorkdir("/app") .withExec(["npm", "ci"]) .withExec(["npm", "run", "build"]) } } ``` :::note The `@object()` constructor declares the `Workspace` as a plain typed parameter with no decorator. Dagger fills it from the current workspace; the caller passes nothing. The `Workspace` type is imported from `@dagger.io/dagger`. ::: A `Workspace` exposes lazy accessors for project content: - `ws.directory(path, opts?)` returns a `Directory` for `path`. Takes an options object with `exclude`, `include`, and `gitignore`. Use `exclude` aggressively to keep the cache key tight: ```typescript this.source = ws.directory("/", { exclude: ["node_modules", ".git", "dist"] }) ``` - `ws.file(path)` returns a `File` for `path`. ```typescript const pkg = ws.file("package.json") ``` - `ws.findUp(name, opts?)` walks up from a start path (`opts.from`) within the workspace looking for `name`. It returns the absolute workspace path, or null if not found. The search stops at the workspace root. ```typescript const modRoot = await ws.findUp("package.json") ``` **Path resolution.** Relative paths (e.g. `"src"`, `"package.json"`) resolve from the workspace **cwd**; absolute paths (e.g. `"/src"`, `"/go.mod"`) resolve from the workspace **root**. Because these accessors are lazy, reading a `Directory` or `File` from the workspace does not upload anything until a downstream operation actually evaluates it. Pull only the paths you need, with tight `exclude` patterns, to minimize cache invalidation. ## Arguments and return values Function parameters become Dagger function arguments. TypeScript types map onto the Dagger type system: | TypeScript | Dagger API | Notes | |---|---|---| | `string` | `String` | | | `number` | `Int` / `Float` | | | `boolean` | `Boolean` | | | `string[]`, `T[]` | list | | | `Directory`, `File`, `Container`, `Service`, `Secret`, `Port`, … | core types | imported from `@dagger.io/dagger` | | `void` / `Promise` | `Void` | common for checks and side-effecting functions | | a custom `@object()` class | object | for returning multiple related values | | a TypeScript `enum` | enum | restricts to a set of values | ### Documenting arguments and functions Use JSDoc comments. The comment above a method is the function description; the comment above a parameter is the argument description. Both appear in `dagger api functions` and `dagger api call --help`. ```typescript @object() class MyModule { /** * Query the GitHub API */ @func() async githubApi( /** * GitHub API token */ token: Secret, ): Promise { return await dag .container() .from("alpine:3.17") .withSecretVariable("GITHUB_API_TOKEN", token) .withExec(["apk", "add", "curl"]) .withExec([ "sh", "-c", `curl "https://api.github.com/repos/dagger/dagger/issues" --header "Authorization: Bearer $GITHUB_API_TOKEN"`, ]) .stdout() } } ``` ### Defaults and optional arguments A TypeScript default value makes the argument optional: ```typescript @func() greet(name = "world"): string { return `Hello, ${name}!` } ``` ```shell dagger api call greet # Hello, world! dagger api call greet --name=Sam # Hello, Sam! ``` ### Nullability An optional parameter (`name?: string`) maps to a nullable argument that may be omitted. A required parameter without a default must always be provided. ```typescript @func() maybeGreet(name?: string): string { return name ? `Hello, ${name}!` : "Hello!" } ``` ### Enums A TypeScript `enum` restricts an argument to a fixed set of values. Document members with JSDoc. ```typescript import { func, object } from "@dagger.io/dagger" /** * Severity levels for a scan */ export enum Severity { Low = "LOW", Medium = "MEDIUM", High = "HIGH", Critical = "CRITICAL", } @object() export class Security { @func() describe(severity: Severity): string { return `scanning at severity ${severity}` } } ``` Passing an invalid value produces an error listing the valid choices. ### Custom object types To return multiple related values, define another `@object()` class and expose its fields with `@func()`: ```typescript import { dag, File, object, func } from "@dagger.io/dagger" @object() class BuildResult { @func() binary: File @func() version: string constructor(binary: File, version: string) { this.binary = binary this.version = version } } @object() class MyModule { @func() build(): BuildResult { const bin = dag .container() .from("golang:1.22") .withExec(["sh", "-c", "echo built > /out/app"]) .file("/out/app") return new BuildResult(bin, "1.0.0") } } ``` Dagger prefixes custom type names in the schema (e.g. `MyModuleBuildResult`) to avoid collisions when multiple modules are loaded together. ### Interfaces Interfaces let your module accept arbitrary objects from other modules without depending on their concrete types. Declare a plain TypeScript `interface` whose members are the functions you need. Write them as fields with function types that return `Promise`s (every call is remote). Any object that provides matching functions can be passed in: ```typescript import { dag, object, func } from "@dagger.io/dagger" export interface Duck { quack: () => Promise } @object() export class MyModule { // Accept any object that satisfies the Duck interface. @func() async quackIt(duck: Duck): Promise { return await duck.quack() } // A function can also return an interface value. @func() getDuck(): Duck { return dag.mallard() } } ``` Methods that take arguments are typed the same way, e.g. `withName: (name: string) => Promise`. Any object whose functions match the interface, including objects returned by other modules, can be passed where the interface is expected. ## Core Dagger types The generated client exposes the full Dagger API. The patterns below are the ones you'll reach for most. ### Containers and files ```typescript @func() test(source: Directory): Container { return dag .container() .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withMountedCache("/app/node_modules", dag.cacheVolume("node-modules")) .withExec(["npm", "ci"]) .withExec(["npm", "test"]) } ``` `withMountedCache` + `dag.cacheVolume("name")` give you a persistent, content-addressed cache keyed by name. Every builder operation is layer-cached automatically by its inputs. ### Secrets Accept secrets as the `Secret` type, never as a plain string. Dagger scrubs secret values from logs, caches, and crash reports. ```typescript @func() async deploy(token: Secret): Promise { return await dag .container() .from("alpine") .withSecretVariable("DEPLOY_TOKEN", token) .withExec(["sh", "-c", "deploy --token=$DEPLOY_TOKEN"]) .stdout() } ``` Callers supply secrets through providers: ```shell dagger api call deploy --token=env:DEPLOY_TOKEN dagger api call deploy --token=file:./token.txt dagger api call deploy --token=cmd:"gh auth token" dagger api call deploy --token=op://vault/item/field ``` ### Services ```typescript @func() integrationTest(source: Directory): Container { const db = dag .container() .from("postgres:16") .withEnvVariable("POSTGRES_PASSWORD", "test") .withExposedPort(5432) .asService() return dag .container() .from("node:20") .withDirectory("/app", source) .withWorkdir("/app") .withServiceBinding("db", db) .withEnvVariable("DATABASE_URL", "postgres://postgres:test@db:5432/test") .withExec(["npm", "run", "test:integration"]) } ``` Services are content-addressed. The same definition always resolves to the same hostname, so there are no port conflicts. ### Concurrency Because the API is lazy, you can start independent pipelines and `await` them together with `Promise.all`: ```typescript @func() async ci(source: Directory): Promise { await Promise.all([ this.lint(source).sync(), this.test(source).sync(), ]) } ``` ## Module dependencies Manage module clients from the module directory: ```shell cd .dagger/modules/my-module dagger module client list --sdk=typescript dagger module client add dagger.io/go --sdk=typescript dagger module client update --sdk=typescript dagger module client rm dagger.io/go --sdk=typescript ``` These commands require the updated TypeScript SDK interface. Client add and remove commands update targets in `dagger.toml`. Client update refreshes `dagger.lock`. All three run generation. Installed module names are not valid client targets. A module reference is `[proto://]host/repo[/subpath][@version]`, e.g. `github.com/shykes/daggerverse/hello@v0.3.0`. Local paths (`./path/to/module`) are also supported. After adding a dependency, `dagger-module.toml` gains a `dependencies` entry: ```toml title="dagger-module.toml" template name = "my-module" engineVersion = "v{{ version }}" [runtime] source = "typescript" [[dependencies]] name = "go" source = "dagger.io/go" ``` Once a dependency is added and bindings are regenerated, call it through `dag`. The dependency's functions are accessible by its name, and arguments are passed as an options object: ```typescript import { dag, Directory, object, func } from "@dagger.io/dagger" @object() class MyModule { @func() example(buildSrc: Directory, buildArgs: string[]): Directory { return dag.go().build({ source: buildSrc, args: buildArgs }).terminal() } } ``` ## Regenerate bindings The `sdk/` directory holds the generated typed client. Regenerate it whenever you change the module API or engine version. Use `dagger generate`, which returns a changeset: ```shell dagger generate ``` `dagger generate` runs registered SDK scopes and author-defined generators. SDK binding generation requires a compatible TypeScript SDK module. ### Commit vs. generate Commit generated SDK files so consumers and CI can build the module. Run `dagger generate`, inspect the changeset, and commit the result with the change that required it. ## Engine version `dagger-module.toml`'s `engineVersion` records the engine your module targets. Edit that field to change the requirement, then regenerate bindings with a compatible SDK. ## Checks, generators, directives, and ignore A useful, reusable module provides at least one first-class function type: a **check** (run by `dagger check`), a **generator** (run by `dagger generate`), or a **service** (started by `dagger up`). Each is a regular `@func()` annotated with an additional decorator. ### Checks {#checks} A check is a function that validates something and passes or fails by exit code. Mark it with `@check()`. `dagger check` discovers and runs every check in the module. ```typescript import { dag, Directory, Workspace, object, func, check } from "@dagger.io/dagger" @object() class MyModule { source: Directory constructor(ws: Workspace) { this.source = ws.directory("/") } /** * Lint the code */ @func() @check() async lint(): Promise { await dag .container() .from("node:20") .withDirectory("/app", this.source) .withWorkdir("/app") .withExec(["npm", "ci"]) .withExec(["npm", "run", "lint"]) .sync() } /** * Run unit tests */ @func() @check() async test(): Promise { await dag .container() .from("node:20") .withDirectory("/app", this.source) .withWorkdir("/app") .withExec(["npm", "ci"]) .withExec(["npm", "test"]) .sync() } } ``` ```shell dagger check ``` A check passes if it returns without throwing; it fails if any `withExec` returns a non-zero exit code (which surfaces as a thrown error when you `sync`). ### Generators {#generators} A generator produces a [changeset](../../using/generating.mdx), a diff between the current source and generated output. Mark it with `@generate()` and return a `Changeset`. `dagger generate` runs every generator and presents the combined changeset for review. ```typescript import { dag, Directory, Workspace, Changeset, object, func, generate } from "@dagger.io/dagger" @object() class MyModule { source: Directory constructor(ws: Workspace) { this.source = ws.directory("/") } /** * Generate API client code */ @func() @generate() generateApi(): Changeset { return dag .container() .from("node:20") .withDirectory("/app", this.source) .withWorkdir("/app") .withExec(["npm", "ci"]) .withExec(["npm", "run", "generate:api"]) .directory("/app") .changes(this.source) } } ``` `.changes(this.source)` computes the diff against the original source. This author-written `@generate()` function differs from SDK binding generation (covered in [Regenerate bindings](#regenerate-bindings)). Your functions produce project-specific output (API clients, config, scaffolding) and are run by `dagger generate`. ### Services {#up-services} A service is a long-running container, such as a database, web server, or other dependency. Mark a `@func()` with `@up()` and return a `Service`. `dagger up` starts every service in the module. See [Services](../../using/services.mdx). ```typescript import { Service, dag, object, func, up } from "@dagger.io/dagger" @object() class MyModule { /** * A web server service */ @func() @up() web(): Service { return dag.container().from("nginx:alpine").withExposedPort(80).asService() } /** * A redis service */ @func() @up() redis(): Service { return dag.container().from("redis:alpine").withExposedPort(6379).asService() } } ``` ```shell dagger up ``` The `@up()` decorator goes below `@func()`, and the function returns a `Service` built from a container with `.asService()`. ### Cache directive Control function caching with the `@func({ cache })` option: ```typescript // Persistent caching for 10 minutes (useful for external data) @func({ cache: "10m" }) async latestRelease(): Promise { /* ... */ } // Cache only for the current session @func({ cache: "session" }) async sessionId(): Promise { /* ... */ } // Never cache, re-execute every call @func({ cache: "never" }) async currentTime(): Promise { /* ... */ } ``` A cache miss still benefits from layer caching for operations inside the function. `"never"` forces the function to re-run but does not disable layer caching for its operations. ### Ignore When you read a directory from the workspace, filter it with the `exclude` option on `ws.directory(path, { exclude })` (see [Workspace inputs](#workspace-inputs)). Push file access to the leaves: read only the paths you need, with tight `exclude` patterns, to minimize cache invalidation. ```typescript this.source = ws.directory("/", { exclude: ["node_modules", ".git", "dist"] }) ``` ## Testing TypeScript modules The primary way to test a module is to call its functions and run its checks: ```shell # Smoke test. Does it build? dagger -m ./.dagger/modules/my-module api call build --src=. # Run all checks dagger check # Run generators and confirm there's no drift dagger check --generate ``` For richer behavioral tests, write a separate test module (in TypeScript or any SDK) that adds your module as a dependency and asserts on its outputs. Because services and containers are content-addressed and reproducible, integration tests against ephemeral databases or HTTP services are deterministic. ### In CI Run `dagger check` from CI to execute every check the module defines: ```yaml title=".github/workflows/ci.yml" template name: ci on: [push, pull_request] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dagger/dagger-for-github@v6 with: version: "v{{ version }}" verb: check ``` If your module keeps generated `sdk/` files in git, also gate on drift by running `dagger check --generate`. ## IDE and package-manager setup ### Runtimes and package managers TypeScript modules can use Node, Bun, or Deno. For Node, the SDK works with npm, yarn, and pnpm. The package manager is detected from your lockfile (`package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml`). Commit the lockfile so builds are reproducible. Bun and Deno manage dependencies through their own native tooling; the module code and decorators are identical across runtimes. Only dependency installation and lockfiles differ. ### Editor type resolution Because `tsconfig.json` maps `@dagger.io/dagger` to the local `./sdk/index.ts`, most editors resolve Dagger types and offer completion without a separate install: ```json "experimentalDecorators": true, "paths": { "@dagger.io/dagger": ["./sdk/index.ts"], "@dagger.io/dagger/telemetry": ["./sdk/telemetry.ts"] } ``` `experimentalDecorators` is required for `@object()`, `@func()`, `@check()`, `@generate()`, and `@argument()` to work. ### Source maps When working across inter-dependent modules, the SDK attaches source maps to generated code (line comments of the form `./path/to/filename:line`), so you can click through to a function's declaration in another module. Most editors support this natively or via a plugin such as the [Open file plugin](https://marketplace.visualstudio.com/items?itemName=Fr43nk.seito-openfile) for VS Code. ## Packaging and release A TypeScript module is published simply by pushing it to a git repository. There is no separate publish step. Consumers reference it by its git address and version (a tag, branch, or commit): ```shell # In a consumer module's workspace dagger module install github.com/you/my-module@v1.0.0 ``` This installs the module in the consumer's `dagger.toml`. Use `dagger module client add --sdk=typescript` to call it from module code. Tag releases (e.g. `v1.0.0`) so consumers can pin a stable version. Keep generated `sdk/` files committed so consumers don't have to regenerate before using your module. Recommended release checklist: 1. `dagger check` passes. 2. `dagger check --generate` produces no drift. 3. `engineVersion` in `dagger-module.toml` matches the engine you support. 4. Tag and push: `git tag vX.Y.Z && git push --tags`. ## Troubleshooting **Decorators not recognized / runtime errors about metadata.** Ensure `experimentalDecorators` is `true` in `tsconfig.json` and that your `@object()` class is exported/defined as shown. The first `@object()` class is the module's main object. **Calling a dependency fails with "unknown function" or missing types.** Add the client with `dagger module client add --sdk=typescript` from the module directory and apply the changeset. The generated `sdk/` client must include the dependency before `dag.()` resolves. **`dagger check` finds no checks.** Confirm the functions are decorated with both `@func()` and `@check()` and that the class is the module's `@object()`. **CI build fails on missing `sdk/`.** Either commit the generated `sdk/` directory, or run `dagger generate` with a compatible SDK as a build step before calling the module. **Engine version mismatch warnings.** Align `dagger-module.toml`'s `engineVersion` with your engine, then regenerate bindings with a compatible SDK. --- # Self-hosting URL: https://docs.dagger.io/self-hosting/index # Self-hosting TODO --- # Calling functions URL: https://docs.dagger.io/using/calling-functions # Calling functions `dagger check`, `dagger generate`, and `dagger up` are conveniences built on top of the underlying primitive: calling a module's functions. When you need something those verbs don't cover, call functions directly. ```shell dagger api call jest test ``` ## Discover functions List the functions available in your workspace: ```shell dagger api functions ``` List the functions on a specific module: ```shell dagger api functions jest ``` Every function and its arguments are also visible through `--help`: ```shell dagger api call jest --help ``` ## Pass arguments Arguments are flags on the function that accepts them: ```shell dagger api call jest --package-manager=yarn test ``` Secrets are passed the same way, via provider URIs: ```shell dagger api call deploy --token=env:DEPLOY_TOKEN ``` ## Chain functions Functions interconnect into a pipeline. Each function returns an object, and the next function is called on that result: ```shell dagger -m core api call container \ from --address=alpine \ with-exec --args=echo,hello \ stdout ``` ## Output By default the result is printed to your terminal. Format it as JSON: ```shell dagger api call jest test --json ``` Or save a returned file or directory to the host: ```shell dagger api call build --output=./dist ``` --- # Checking your code URL: https://docs.dagger.io/using/checking # Checking your code ```shell dagger check ``` Runs all checks in your workspace in parallel. Exits non-zero if any check fails. Results are identical on your laptop, in CI, and in the cloud. ## List checks ```shell dagger check -l ``` ## Filter checks ```shell dagger check eslint:* # all checks from a module dagger check vitest:test # a single check dagger check *:lint # pattern across modules ``` ## Stop on first failure By default every check runs so you see all failures at once. To cancel the rest as soon as one fails: ```shell dagger check --failfast ``` ## Checks and generators A generator can be run as a check to confirm its output is up to date. To control which kind runs: ```shell dagger check --no-generate # only annotated check functions dagger check --generate # only generators-run-as-checks ``` ## In CI ```yaml # GitHub Actions - run: dagger check ``` --- # Generating code URL: https://docs.dagger.io/using/generating # Generating code ```shell dagger generate ``` Runs every generator in your workspace. A generator doesn't write to your files directly — it returns a **changeset**: a diff of the proposed changes. Dagger shows you the changed paths and line counts, and nothing is written until you approve. Functions can also return a changeset without being generators — a formatter's `fix`, called with `dagger api call`, is the common case. Checks never do; they only validate. ## List generators ```shell dagger generate -l ``` ## Filter generators ```shell dagger generate protobuf:* # all generators from a module dagger generate changelog:generate # a single generator ``` ## Apply without prompting Pass `-y` / `--auto-apply` to skip the review step — useful in scripts and non-interactive sessions: ```shell dagger generate -y ``` ## Coding agents When Dagger detects that a coding agent is running `dagger generate`, it requires an explicit choice up front. Pass `-y` to apply the result, or `--no-apply` to run the generators and show the changes without writing them: ```shell dagger generate --no-apply ``` `--no-apply` exits successfully even when there are pending changes, just like choosing **Discard** at the interactive prompt. Generators still run and may perform other work; only the changeset is withheld. ## Verify in CI In CI you usually want to check that committed files are up to date rather than rewrite them. `dagger check` runs each generator as a read-only check and fails, without applying anything, if its output differs from what's committed: ```yaml # GitHub Actions - run: dagger check --generate ``` A failing generator check means the committed output is stale — run `dagger generate` locally, apply the changeset, and commit. --- # Running services URL: https://docs.dagger.io/using/services # Running services Start all services defined in your workspace: ```shell dagger up ``` Services run in ephemeral containers. Dagger tunnels their ports to your local machine. Stop with Ctrl+C. ## Listing and filtering services ```shell dagger up -l # list available services dagger up web # start only the 'web' service dagger up web api redis # start multiple services ``` ## Use cases - Running a database for local development or testing - Running end-to-end integration tests against a service - Running sidecars (proxies, caches, queues) ## How services work Service containers have three key properties: - **Content-addressed hostnames.** Each service gets a canonical hostname derived from its definition. Same definition, same hostname, so no port conflicts. - **Just-in-time lifecycle.** Services start when first needed. If several clients request the same service, they share one instance. Services stop when nothing references them anymore. - **Health checks.** Dagger health-checks a service before any client can connect, so there is no race between service startup and test execution. ## Wiring a service into another module Generic modules can compose through workspace settings. If a module's constructor accepts an optional `Service`, you can wire another module's service into it with a plain module reference. No glue module required. For example, a `docusaurus` module knows how to serve a documentation site, and a `playwright` module knows how to run browser tests against any web app it's given. Connect them in `dagger.toml`: ```toml [modules.docusaurus] source = "github.com/example/docusaurus@v1.0" [modules.playwright] source = "github.com/example/playwright@v1.0" [modules.playwright.settings] app = "docusaurus:serve" ``` The setting is a `:` string. The leading segment is a module's install name, the `[modules.X]` key in the same `dagger.toml`. The rest names a zero-arg function on it. Any function returning a matching type works, and Dagger injects the value it returns. `dagger up -l` is a convenient place to find and copy a service's path, but the path is valid because it resolves to a `Service`, not because the function is listed there. When Dagger constructs `playwright`, it resolves the reference and passes the service as the `app` argument. This also works for `Container` constructor arguments. Point the setting at any module function that returns a `Container`, for example to share a common base image across modules. Because a module reference is an ordinary address string, the same value works as a CLI flag with no `dagger.toml` entry: ```shell dagger call playwright --app=docusaurus:serve test ``` ## Container-to-host networking A service defined in a module can be exposed to your local machine: ```shell # Start an HTTP service and access it locally dagger up web curl http://localhost:80 ``` ## Host-to-container networking Containers running in Dagger can also connect to services on your host machine. This is useful for testing against a locally running database or API. A function exposes a host service by accepting it as an argument, using the `tcp://` or `udp://` provider to point at a host port: ```shell # Make a database running on the host reachable from the function dagger api call integration-test --db=tcp://localhost:5432 ``` Inside the function the argument is an ordinary `Service`, so the same code works whether the upstream runs on your host or in another container.