Skip to main content

Directory

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.

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

package main

import (
"context"
"dagger/my-module/internal/dagger"
)

type MyModule struct{}

func (m *MyModule) Foo(
ctx context.Context,
// +ignore=["*", "!**/*.go", "!go.mod", "!go.sum"]
source *dagger.Directory,
) (*dagger.Container, error) {
return dag.
Container().
From("alpine:latest").
WithDirectory("/src", source).
Sync(ctx)
}

Here are a few examples of useful patterns:

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

// +ignore=[
// "**_test.go",
// "**/testdata/**"
// ]

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:

package main

import (
"context"
"dagger/my-module/internal/dagger"
)

type MyModule struct{}

func (m *MyModule) Foo(
ctx context.Context,
source *dagger.Directory,
) *dagger.Container {
builder := dag.
Container().
From("golang:latest").
WithDirectory("/src", source, dagger.ContainerWithDirectoryOpts{Exclude: []string{"*.git", "internal"}}).
WithWorkdir("/src/hello").
WithExec([]string{"go", "build", "-o", "hello.bin", "."})
return dag.
Container().
From("alpine:latest").
WithDirectory("/app", builder.Directory("/src/hello"), dagger.ContainerWithDirectoryOpts{Include: []string{"hello.bin"}}).
WithEntrypoint([]string{"/app/hello.bin"})
}

Here are a few examples of useful patterns:

// 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",
}

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

Dagger Cloud Trace

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:

func (m *MyModule) Debug(
ctx context.Context,
// +ignore=["*", "!analytics"]
source *dagger.Directory,
) *dagger.Directory {
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

A directory.

Implements Exportable, Node, Syncer

digest
Return the directory's digest. The format of the digest is not guaranteed to be stable between releases of Dagger. It is guaranteed to be stable between invocations of the same Dagger engine.
entries
Returns a list of files and directories at the given path.
exists
check if a file or directory exists
export
Writes the contents of the directory to a path on the host.
findUp
Search up the directory tree for a file or directory, and return its path. If no match, return null
glob
Returns a list of files and directories that matche the given pattern.
id
A unique identifier for this Directory.
name
Returns the name of the directory.
asGit
Converts this directory to a local git repository
asModule
Load the directory as a Dagger module source
asModuleSource
Load the directory as a Dagger module source
asWorkspace
Creates a synthetic workspace from this directory.
changes
Return the difference between this directory and another directory, typically an older snapshot.
chown
Change the owner of the directory contents recursively.
diff
Return the difference between this directory and an another directory. The difference is encoded as a directory.
directory
Retrieves a directory at the given path.
dockerBuild
Use Dockerfile compatibility to build a container from this directory. Only use this function for Dockerfile compatibility. Otherwise use the native Container type directly, it is feature-complete and supports all Dockerfile features.
file
Retrieve a file at the given path.
filter
Return a snapshot with some paths included or excluded
search
Searches for content matching the given regular expression or literal string.
stat
Return file status
sync
Force evaluation in the engine.
terminal
Opens an interactive terminal in new container with this directory mounted inside.
withChanges
Return a directory with changes from another directory applied to it.
withDirectory
Return a snapshot with a directory added
withError
Raise an error.
withFile
Retrieves this directory plus the contents of the given file copied to the given path.
withFiles
Retrieves this directory plus the contents of the given files copied to the given path.
withNewDirectory
Retrieves this directory plus a new directory created at the given path.
withNewFile
Return a snapshot with a new file added
withoutDirectory
Return a snapshot with a subdirectory removed
withoutFile
Return a snapshot with a file removed
withoutFiles
Return a snapshot with files removed
withPatch
Retrieves this directory with the given Git-compatible patch applied.
withPatchFile
Retrieves this directory with the given Git-compatible patch file applied.
withSymlink
Return a snapshot with a symlink
withTimestamps
Retrieves this directory with all file/dir timestamps set to the given time.

digest: String!

Return the directory's digest. The format of the digest is not guaranteed to be stable between releases of Dagger. It is guaranteed to be stable between invocations of the same Dagger engine.

entries(path: String): [String!]!

Returns a list of files and directories at the given path.

path: String

Location of the directory to look at (e.g., "/src").

exists(path: String!,expectedType: ExistsType,doNotFollowSymlinks: Boolean = false): Boolean!

check if a file or directory exists

path: String!

Path to check (e.g., "/file.txt").

expectedType: ExistsType

If specified, also validate the type of file (e.g. "REGULAR_TYPE", "DIRECTORY_TYPE", or "SYMLINK_TYPE").

doNotFollowSymlinks: Boolean = false

If specified, do not follow symlinks.

export(path: String!, wipe: Boolean = false): String!

Writes the contents of the directory to a path on the host.

path: String!

Location of the copied directory (e.g., "logs/").

wipe: Boolean = false

If true, then the host directory will be wiped clean before exporting so that it exactly matches the directory being exported; this means it will delete any files on the host that aren't in the exported dir. If false (the default), the contents of the directory will be merged with any existing contents of the host directory, leaving any existing files on the host that aren't in the exported directory alone.

findUp(name: String!, start: String!): String

Search up the directory tree for a file or directory, and return its path. If no match, return null

name: String!

The name of the file or directory to search for

start: String!

The path to start the search from

glob(pattern: String!): [String!]!

Returns a list of files and directories that matche the given pattern.

pattern: String!

Pattern to match (e.g., "*.md").

id: ID!

A unique identifier for this Directory.

name: String!

Returns the name of the directory.

asGit: GitRepository!

Converts this directory to a local git repository

asModule(sourceRootPath: String = "."): Module!

Load the directory as a Dagger module source

sourceRootPath: String = "."

An optional subpath of the directory which contains the module's configuration file.

If not set, the module source code is loaded from the root of the directory.

asModuleSource(sourceRootPath: String = "."): ModuleSource!

Load the directory as a Dagger module source

sourceRootPath: String = "."

An optional subpath of the directory which contains the module's configuration file.

If not set, the module source code is loaded from the root of the directory.

asWorkspace(cwd: String = "/"): Workspace!

Creates a synthetic workspace from this directory.

cwd: String = "/"

Current working directory inside the workspace root. Defaults to the workspace root.

changes(from: Directory!): Changeset!

Return the difference between this directory and another directory, typically an older snapshot.

The difference is encoded as a changeset, which also tracks removed files, and can be applied to other directories.

from: Directory!

The base directory snapshot to compare against

chown(path: String!, owner: String!): Directory!

Change the owner of the directory contents recursively.

path: String!

Path of the directory to change ownership of (e.g., "/").

owner: String!

A user:group to set for the mounted directory and its contents.

The user and group can either be an ID (1000:1000) or a name (foo:bar).

If the group is omitted, it defaults to the same as the user.

diff(other: Directory!): Directory!

Return the difference between this directory and an another directory. The difference is encoded as a directory.

other: Directory!

The directory to compare against

directory(path: String!): Directory!

Retrieves a directory at the given path.

path: String!

Location of the directory to retrieve. Example: "/src"

dockerBuild(dockerfile: String = "Dockerfile",platform: Platform,buildArgs: [BuildArg!] = [],target: String = "",secrets: [Secret!] = [],noInit: Boolean = false,ssh: Socket): Container!

Use Dockerfile compatibility to build a container from this directory. Only use this function for Dockerfile compatibility. Otherwise use the native Container type directly, it is feature-complete and supports all Dockerfile features.

dockerfile: String = "Dockerfile"

Path to the Dockerfile to use (e.g., "frontend.Dockerfile").

platform: Platform

The platform to build.

buildArgs: [BuildArg!] = []

Build arguments to use in the build.

target: String = ""

Target build stage to build.

secrets: [Secret!] = []

Secrets to pass to the build.

They will be mounted at /run/secrets/[secret-name].

noInit: Boolean = false

If set, skip the automatic init process injected into containers created by RUN statements.

This should only be used if the user requires that their exec processes be the pid 1 process in the container. Otherwise it may result in unexpected behavior.

ssh: Socket

A socket to use for SSH authentication during the build

(e.g., for Dockerfile RUN --mount=type=ssh instructions).

Typically obtained via host.unixSocket() pointing to the SSH_AUTH_SOCK.

file(path: String!): File!

Retrieve a file at the given path.

path: String!

Location of the file to retrieve (e.g., "README.md").

filter(exclude: [String!] = [],include: [String!] = [],gitignore: Boolean = false): Directory!

Return a snapshot with some paths included or excluded

exclude: [String!] = []

If set, paths matching one of these glob patterns is excluded from the new snapshot. Example: ["node_modules/", ".git*", ".env"]

include: [String!] = []

If set, only paths matching one of these glob patterns is included in the new snapshot. Example: (e.g., ["app/", "package.*"]).

gitignore: Boolean = false

If set, apply .gitignore rules when filtering the directory.

Searches for content matching the given regular expression or literal string.

Uses Rust regex syntax; escape literal ., [, ], {, }, | with backslashes.

paths: [String!] = []

Directory or file paths to search

globs: [String!] = []

Glob patterns to match (e.g., "*.md")

pattern: String!

The text to match.

literal: Boolean = false

Interpret the pattern as a literal string instead of a regular expression.

multiline: Boolean = false

Enable searching across multiple lines.

dotall: Boolean = false

Allow the . pattern to match newlines in multiline mode.

insensitive: Boolean = false

Enable case-insensitive matching.

skipIgnored: Boolean = false

Honor .gitignore, .ignore, and .rgignore files.

skipHidden: Boolean = false

Skip hidden files (files starting with .).

filesOnly: Boolean = false

Only return matching files, not lines and content

limit: Int

Limit the number of results to return

stat(path: String!, doNotFollowSymlinks: Boolean = false): Stat

Return file status

path: String!

Path to stat (e.g., "/file.txt").

doNotFollowSymlinks: Boolean = false

If specified, do not follow symlinks.

sync: Directory!

Force evaluation in the engine.

terminal(container: Container,cmd: [String!] = [],experimentalPrivilegedNesting: Boolean = false,insecureRootCapabilities: Boolean = false): Directory!

Opens an interactive terminal in new container with this directory mounted inside.

container: Container

If set, override the default container used for the terminal.

cmd: [String!] = []

If set, override the container's default terminal command and invoke these command arguments instead.

experimentalPrivilegedNesting: Boolean = false

Provides Dagger access to the executed command.

insecureRootCapabilities: Boolean = false

Execute the command with all root capabilities. This is similar to running a command with "sudo" or executing "docker run" with the "--privileged" flag. Containerization does not provide any security guarantees when using this option. It should only be used when absolutely necessary and only with trusted commands.

withChanges(changes: Changeset!): Directory!

Return a directory with changes from another directory applied to it.

changes: Changeset!

Changes to apply to the directory

withDirectory(path: String!,source: Directory!,exclude: [String!] = [],include: [String!] = [],gitignore: Boolean = false,owner: String = "",permissions: Int): Directory!

Return a snapshot with a directory added

path: String!

Location of the written directory (e.g., "/src/").

source: Directory!

Identifier of the directory to copy.

exclude: [String!] = []

Exclude artifacts that match the given pattern (e.g., ["node_modules/", ".git*"]).

include: [String!] = []

Include only artifacts that match the given pattern (e.g., ["app/", "package.*"]).

gitignore: Boolean = false

Apply .gitignore filter rules inside the directory

owner: String = ""

A user:group to set for the copied directory and its contents.

The user and group can either be an ID (1000:1000) or a name (foo:bar).

If the group is omitted, it defaults to the same as the user.

permissions: Int

Permission given to the copied directory and contents (e.g., 0755).

withError(err: String!): Directory!

Raise an error.

err: String!

Message of the error to raise. If empty, the error will be ignored.

withFile(path: String!,source: File!,permissions: Int,owner: String = ""): Directory!

Retrieves this directory plus the contents of the given file copied to the given path.

path: String!

Location of the copied file (e.g., "/file.txt").

source: File!

Identifier of the file to copy.

permissions: Int

Permission given to the copied file (e.g., 0600).

owner: String = ""

A user:group to set for the copied directory and its contents.

The user and group can either be an ID (1000:1000) or a name (foo:bar).

If the group is omitted, it defaults to the same as the user.

withFiles(path: String!,sources: [File!]!,permissions: Int): Directory!

Retrieves this directory plus the contents of the given files copied to the given path.

path: String!

Location where copied files should be placed (e.g., "/src").

sources: [File!]!

Identifiers of the files to copy.

permissions: Int

Permission given to the copied files (e.g., 0600).

withNewDirectory(path: String!, permissions: Int = 420): Directory!

Retrieves this directory plus a new directory created at the given path.

path: String!

Location of the directory created (e.g., "/logs").

permissions: Int = 420

Permission granted to the created directory (e.g., 0777).

withNewFile(path: String!,contents: String!,permissions: Int = 420): Directory!

Return a snapshot with a new file added

path: String!

Path of the new file. Example: "foo/bar.txt"

contents: String!

Contents of the new file. Example: "Hello world!"

permissions: Int = 420

Permissions of the new file. Example: 0600

withoutDirectory(path: String!): Directory!

Return a snapshot with a subdirectory removed

path: String!

Path of the subdirectory to remove. Example: ".github/workflows"

withoutFile(path: String!): Directory!

Return a snapshot with a file removed

path: String!

Path of the file to remove (e.g., "/file.txt").

withoutFiles(paths: [String!]!): Directory!

Return a snapshot with files removed

paths: [String!]!

Paths of the files to remove (e.g., ["/file.txt"]).

withPatch(patch: String!): Directory!Experimental

Retrieves this directory with the given Git-compatible patch applied.

Experimental: This API is highly experimental and may be removed or replaced entirely.

patch: String!

Patch to apply (e.g., "diff --git a/file.txt b/file.txt\nindex 1234567..abcdef8 100644\n--- a/file.txt\n+++ b/file.txt\n@@ -1,1 +1,1 @@\n-Hello\n+World\n").

withPatchFile(patch: File!): Directory!Experimental

Retrieves this directory with the given Git-compatible patch file applied.

Experimental: This API is highly experimental and may be removed or replaced entirely.

patch: File!

File containing the patch to apply

Return a snapshot with a symlink

target: String!

Location of the file or directory to link to (e.g., "/existing/file").

linkName: String!

Location where the symbolic link will be created (e.g., "/new-file-link").

withTimestamps(timestamp: Int!): Directory!

Retrieves this directory with all file/dir timestamps set to the given time.

timestamp: Int!

Timestamp to set dir/files in.

Formatted in seconds following Unix epoch (e.g., 1672531199).

References

Returned by

Accepted as an argument by