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
- Go
- Python
- TypeScript
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
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"})
}
import dagger
from dagger import dag, function, object_type
@object_type
class HelloDagger:
@function
def build_env(self, source: dagger.Directory) -> dagger.Container:
"""Build a ready-to-use development environment"""
# create a Dagger cache volume for dependencies
node_cache = dag.cache_volume("node")
return (
dag.container()
# start from a base Node.js container
.from_("node:21-slim")
# add the source code at /src
.with_directory("/src", source)
# mount the cache volume at /root/.npm
.with_mounted_cache("/root/.npm", node_cache)
# change the working directory to /src
.with_workdir("/src")
# run npm install to install dependencies
.with_exec(["npm", "install"])
)
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class HelloDagger {
/**
* Build a ready-to-use development environment
*/
@func()
buildEnv(source: Directory): Container {
// create a Dagger cache volume for dependencies
const 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(["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 "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:
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:
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.