Skip to main content

Dagger for CI: Quickstart

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(
// +defaultPath="/"
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
From("node:21-slim").
// add the source code at /src
WithDirectory("/src", source).
// mount the cache volume at /root/.npm
WithMountedCache("/root/.npm", nodeCache).
// change the working directory to /src
WithWorkdir("/src").
// run npm install to install dependencies
WithExec([]string{"npm", "install"})
}

This Dagger Function requires 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.

It's possible to specify a default path for this argument. This Dagger Function (and all the others in this quickstart) set / as their default path, which means that if no source argument is provided on the command line, Dagger will default to the root of the Git repository as the location of the application's source code.

DEFAULT PATHS

Default paths are only available for Directory and File arguments. Dagger's default path resolution differs depending on whether the paths are (a) absolute or relative; and (b) in Git or non-Git contexts.

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

FUNCTION CHAINING

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 "chaining", and is a core feature of Dagger.

Call the Dagger Function

Now that you know how the Dagger Function works, call it from Dagger Shell:

dagger -c build-env

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.

FUNCTION NAMES

When calling Dagger Functions, 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/PHP/Java are called with foo-bar ... (in interactive mode) or dagger shell -c foo-bar ... (in non-interactive mode).

Interact with a just-in-time container

Just-in-time containers come with useful built-in functions, which you can chain together with the pipe (|) operator. This feature makes it easy to build powerful pipelines on the fly. One of the most interesting built-in 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 -c 'build-env | 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

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.