Skip to main content


Create the build environment

You're now ready to dive into Dagger and see how Dagger Functions work!

In the example pipeline you just ran, the publish stage depends on the test and build stages, which in turn depend on the build-env stage. Dagger represents these stages and dependencies in a Directed Acyclic Graph (DAG), which it then runs concurrently to maximize pipeline speed and accuracy.

Let's start with the build-env stage and look at its Dagger Function first.

Inspect the Dagger Function

package main

import "dagger/hello-dagger/internal/dagger"

type HelloDagger struct{}

// Build a ready-to-use development environment
func (m *HelloDagger) BuildEnv(source *dagger.Directory) *dagger.Container {
// create a Dagger cache volume for dependencies
nodeCache := dag.CacheVolume("node")
return dag.Container().
// start from a base Node.js container
// add the source code at /src
WithDirectory("/src", source).
// mount the cache volume at /src/node_modules
WithMountedCache("/src/node_modules", nodeCache).
// change the working directory to /src
// run npm install to install dependencies
WithExec([]string{"npm", "install"})

This Dagger Function expects a source argument of type Directory. This argument tells the Dagger Function where to find the application's source code, and may refer to either a local directory or a remote Git repository.

The Dagger Function begins by calling the dag client, which is pre-initialized in every Dagger Function. This client contains all the core types (like Container, Directory, etc.), as well as bindings to any dependencies your module has declared.

It uses the dag client to initialize a base container from the node:21-slim image as a Container object. This Container object comes with useful methods of its own, which are then called in a chain to add source code to the container, configure a cache volume and run npm install to install dependencies (refer to the code comments for details).


If you've worked with Dockerfiles, this Dagger Function should look very familiar to you. Similar to the instructions in a Dockerfile, it starts with a base container image, then calls various functions to revise the base image. The base image is represented as a Container object, which is one of Dagger's core types. The Dagger API lets you manipulate the container by calling the object's functions, which can then return another object, and so on. This is called "function chaining", and is a core feature of Dagger.

Call the Dagger Function

Now that you know how the Dagger Function works, call it as below:

dagger call build-env --source=.

Here's what you should see:

Build env

This output means that the build succeeded, and a Container type representing the built container image was returned. This is a "just-in-time" container - a transient artifact produced as the result of a Dagger Function.


When using dagger call, all names (functions, arguments, fields, etc.) are converted into a shell-friendly "kebab-case" style. This is why Dagger Functions named FooBar in Go, foo_bar in Python and fooBar in TypeScript are called with dagger call foo-bar ....

Interact with a just-in-time container

Just-in-time containers come with useful functions of their own, which you can call by chaining them to the artifact in the dagger call command. One of the most interesting functions is terminal, which can be used to open an interactive terminal session with the running container.

To see this in action, chain an additional function call to terminal on the returned Container:

dagger call \
build-env --source=. \
terminal --cmd=bash

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

Build env with terminal


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.