Skip to main content

Basics

Welcome to Dagger, a general-purpose composition engine for containerized workflows.

Dagger is a modular, composable platform designed to replace complex systems glued together with artisanal scripts - for example, complex integration testing environments, data processing pipelines, and AI agent workflows. It is open source and works with any compute platform or technology stack, automatically optimizing for speed and cost.

Create containers

Dagger works by expressing workflows as combinations of functions from the Dagger API, which exposes a range of tools for working with containers, files, directories, network services, secrets. You call the Dagger API from the shell (Dagger Shell) or from code (custom Dagger Functions written in a programming language).

Dagger Shell is an interactive client for the Dagger API, giving you typed objects, built-in documentation, and access to a cross-language ecosystem of reusable modules. You launch it by typing dagger. Here's an example of using it to build and return an Alpine container:

container | from alpine

You can also run commands in the container and return the output. Here's an example of returning the output of the uname command:

container | from alpine | with-exec uname | stdout

Here's an example of installing the curl package in the container and using it to retrieve a webpage:

container | from alpine | with-exec apk add curl | with-exec curl https://dagger.io | stdout

Add files and directories

Once you've got a container, you continue using the Dagger API to modify it, by adding files or directories to it. You can use directories from the Dagger host's filesystem or remote Git repositories. Here's an example of adding Dagger's own open source GitHub repository to the container:

container | from alpine | with-directory /src https://github.com/dagger/dagger

Here's another example, this time creating a new file in the container:

container | from alpine | with-new-file /hi.txt "Hello from Dagger!"
GETTING HELP

The Dagger API is extensively documented but if you're unsure how to proceed at any point, simply append .help to your in-progress workflow for context-sensitive assistance on what you can do next. For example:

container | from alpine | .help
container | from alpine | .help with-directory
container | from alpine | .help with-exec

Use the interactive terminal

Did it work? To look inside, request an interactive terminal session with the running container:

container | from alpine | with-new-file /hi.txt "Hello from Dagger!" | terminal

This drops you into an interactive terminal running the bash shell. You now directly execute commands in the running container, as shown below:

Terminal

INTERACTIVE CONTAINER DEBUGGING

The terminal function is very useful for debugging and experimenting, since it allows you to interact directly with containers and inspect their state, at any stage of your Dagger Function execution.

Chain functions in the shell

What you just did (and have been doing since the start) is chaining one Dagger API function call to another with the pipe (|) operator. This is one of Dagger's most powerful features, as it allows you create dynamic workflows in a single command - no context switching between Dockerfile creation, build commands, and registry pushes.

Dagger's documentation has numerous examples of function chaining in action but here's one more: creating an Alpine container, dropping in a text file with a custom message, setting it to display that message when run, and publishing it to a temporary registry - all in a single command!

container | from alpine | with-new-file /hi.txt "Hello from Dagger!" |
with-entrypoint cat /hi.txt | publish ttl.sh/hello

Publish with Dagger Shell

Write custom functions

As your workflows become more complex, you'll start wishing you could make them more reusable, repeatable and shareable. To do this, encapsulate your workflows into custom Dagger Functions. These are just regular code consisting of a series of method/function calls, such as "pull a container image", "copy a file", "forward a TCP port", and so on, which can be chained together. They are written in a programming language using a type-safe Dagger SDK and packaged into modules.

Here's the previous example, rewritten as a Dagger Function:

func (m *Basics) Publish(ctx context.Context) (string, error) {
return dag.Container().
From("alpine:latest").
WithNewFile("/hi.txt", "Hello from Dagger!").
WithEntrypoint([]string{"cat", "/hi.txt"}).
Publish(ctx, "ttl.sh/hello")
}

When you create a custom Dagger Function and tell Dagger about it (by installing the corresponding module), the Dagger API is dynamically extended to include that new function. You then call this function from Dagger Shell, or from the command-line using dagger call, in exactly the same way as you would call functions from the original Dagger API.

Publish with a Dagger Function

Chain functions in code

Function chaining works the same way, whether you're writing Dagger Function code using a Dagger SDK or using Dagger Shell. The following are equivalent:

// Returns a base container
func (m *Basics) Base() *dagger.Container {
return dag.Container().From("cgr.dev/chainguard/wolfi-base")
}

// Builds on top of base container and returns a new container
func (m *Basics) Build() *dagger.Container {
return m.Base().WithExec([]string{"apk", "add", "bash", "git"})
}

// Builds and publishes a container
func (m *Basics) BuildAndPublish(ctx context.Context) (string, error) {
return m.Build().Publish(ctx, "ttl.sh/bar")
}
FUNCTION NAMES

When calling Dagger Functions, all names (functions, arguments, fields, etc.) are converted into a shell-friendly "kebab-case" style. This is why a Dagger Function named FooBar in Go, foo_bar in Python and fooBar in TypeScript/PHP/Java is called as foo-bar in Dagger Shell or on the command-line.

Use arguments and return values

Dagger Functions are just like regular functions: they accept arguments and return values. In addition to common types (string, boolean, integer, arrays...), the Dagger API also defines powerful core types which Dagger Functions can use, such as Directory, Container, Service, Secret, and many more.

Here's a revision of the previous example which splits it into two smaller functions: one to build the container, and one to publish it. The builder function accepts the container image string as an argument and returns a Container type. The publisher function accepts a Container type as argument and returns the image identifier as a string.

func (m *Basics) Build(
// +default "alpine:latest"
image string,
) *dagger.Container {
return dag.Container().
From(image).
WithNewFile("/hi.txt", "Hello from Dagger!")
}

func (m *Basics) Publish(
ctx context.Context,
// +default "alpine:latest"
image string,
) (string, error) {
return m.Build(image).
WithEntrypoint([]string{"cat", "/hi.txt"}).
Publish(ctx, "ttl.sh/hello")
}
SANDBOXING

Dagger Functions are fully "sandboxed" and do not have direct access to the host system. Therefore, host resources such as directories, files, environment variables, network services and so on must be explicitly passed to Dagger Functions as arguments. This "sandboxing" of Dagger Functions improves security, ensures reproducibility, and assists caching.

Install other modules

You can group Dagger Functions into modules and share them with others - your team, your company, or the broader Dagger community. And just as others can use your modules, you too can use modules created and shared by others, to speed up your development and take advantage of best practices. The Daggerverse is a free service run by Dagger, which indexes all publicly available Dagger modules, and lets you easily search and consume them.

Daggerverse

Here's an example of installing and using two modules from the Daggerverse: a Wolfi container builder and a Trivy container scanner:

Modules

Here's what it looks like in code:

func (m *Basics) Check(ctx context.Context) (string, error) {
ctr := dag.Wolfi().Container()
return dag.Trivy().
ScanContainer(ctx, ctr);
}
CROSS-LANGUAGE COLLABORATION

Dagger Functions can call other Dagger Functions, across languages. For example, a Python function can call a Go function, which can call a TypeScript function, and so on. This means that you no longer need to care which language your workflow is written in; you can use the one that you're most comfortable with or that best suits your requirements.

Speed things up

One of Dagger's most powerful features is its ability to cache data across workflow runs. Dagger caches two types of data:

  • Layers: Build instructions and the results of some API calls.
  • Volumes: Contents of Dagger filesystem volumes.

Taken together, these two types of caching significantly reduce execution times. Here's an example: a Dagger Function that creates a cache volume to store the packages installed by apt:

func (m *Basics) Env(ctx context.Context) *dagger.Container {
aptCache := dag.CacheVolume("apt-cache")
return dag.Container().
From("debian:latest").
WithMountedCache("/var/cache/apt/archives", aptCache).
WithExec([]string{"apt-get", "update"}).
WithExec([]string{"apt-get", "install", "--yes", "maven", "mariadb-server"})
}

Notice that when you call this Dagger Function multiple times, the second and subsequent runs are drastically faster than the first, since Dagger automatically reuses cached instructions and files from the cache.

Caching

Trace everything

Building and running workflows is only part of the problem - you also need a way to inspect and monitor them. Dagger provides two powerful real-time visualization tools: the Dagger terminal UI (TUI), which you've already seen above, and Dagger Cloud, a browser-based interface focused on tracing and debugging Dagger workflows.

Once configured, every time you execute a Dagger workflow, its operational telemetry is automatically sent to Dagger Cloud as a Trace and the workflow output includes a link to visualize the workflow run on Dagger Cloud. Here's an example:

Dagger Trace

DAGGER CLOUD

Dagger Cloud sign-up is optional, and free of charge for a single user.

Next steps

Now that you know the basics of Dagger, continue your journey with the resources below: