Quickstart
Daggerize an example application
The best way to understand how Dagger works is by creating a delivery pipeline using Dagger Functions - a process we call "Daggerizing".
- Choose a Dagger SDK and bootstrap a new Dagger module for your application's pipeline with
dagger init
. - Construct the pipeline by creating and combining one or more Dagger Functions to produce the desired results. Your Dagger Functions can use the core Dagger API and/or call Dagger Functions from third-party Daggerverse modules.
- Use
dagger call
to run and test your pipeline locally. Once you're satisfied, transfer your Dagger module and yourdagger call
commands to your CI configuration.
Get the example application
The example application is a skeleton Vue framework application that returns a "Hello from Dagger!" welcome page. Clone its repository and set it as the current working directory:
git clone https://github.com/dagger/hello-dagger
cd hello-dagger
Visualize in Dagger Cloud (optional)
This step is optional and will create a Dagger Cloud individual plan account. The individual plan is free of charge for a single user. You will require a GitHub account for account setup and identity verification. If you prefer not to sign up for Dagger Cloud, you can skip this section.
Dagger Cloud is an online visualization tool for Dagger pipelines. It provides a web interface to visualize each step of your pipeline, drill down to detailed logs, understand how long operations took to run, and whether operations were cached.
Create a new Dagger Cloud account by running dagger login
:
dagger login
The Dagger CLI will invite you to authenticate your device by displaying a link containing a unique key. Click the link in your browser, and verify that you see the same key in the Dagger Cloud Web interface.
$ dagger login
Browser opened to: https://auth.dagger.cloud/activate?user_code=FCNP-SRLM
Confirmation code: FCNP-SRLM
Once you confirm your authentication code, your Dagger CLI will be authenticated and you will get redirected to your newly created Dagger Cloud organization.
After successfully creating your organization, all future dagger call
pipelines
can be inspected in Dagger Cloud.
Initialize a Dagger module
Bootstrap a new Dagger module in Go, Python or TypeScript by running dagger init
in the application's root directory, using the --source
flag to specify a directory for the module's source code.
- Go
- Python
- TypeScript
dagger init --sdk=go --source=./dagger
This will generate a dagger.json
module metadata file, an initial dagger/main.go
source code template, as well as a dagger/dagger.gen.go
file and dagger/internal/
directory.
dagger init --sdk=python --source=./dagger
This will generate a dagger.json
module metadata file, initial dagger/src/hello_dagger/__init__.py
and dagger/src/hello_dagger/main.py
source code template, dagger/pyproject.toml
and dagger/uv.lock
files, as well as a generated dagger/sdk
folder for local development.
dagger init --sdk=typescript --source=./dagger
This will generate a dagger.json
module metadata file, initial dagger/src/index.ts
source code template, dagger/package.json
and dagger/tsconfig.json
files, as well as a generated dagger/sdk
folder for local development.
Construct a pipeline using Dagger Functions
Dagger Functions are regular code, written in Go, Python or TypeScript using the corresponding Dagger SDK. They consist 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.
Don't worry about how the Dagger Functions shown below work for the moment - it's explained in detail in the next sections!
- Go
- Python
- TypeScript
Replace the generated dagger/main.go
file with the following code, which adds four Dagger Functions to your Dagger module:
package main
import (
"context"
"fmt"
"math"
"math/rand"
"dagger/hello-dagger/internal/dagger"
)
type HelloDagger struct{}
// Publish the application container after building and testing it on-the-fly
func (m *HelloDagger) Publish(ctx context.Context, source *dagger.Directory) (string, error) {
_, err := m.Test(ctx, source)
if err != nil {
return "", err
}
return m.Build(source).
Publish(ctx, fmt.Sprintf("ttl.sh/hello-dagger-%.0f", math.Floor(rand.Float64()*10000000))) //#nosec
}
// Build the application container
func (m *HelloDagger) Build(source *dagger.Directory) *dagger.Container {
build := m.BuildEnv(source).
WithExec([]string{"npm", "run", "build"}).
Directory("./dist")
return dag.Container().From("nginx:1.25-alpine").
WithDirectory("/usr/share/nginx/html", build).
WithExposedPort(80)
}
// Return the result of running unit tests
func (m *HelloDagger) Test(ctx context.Context, source *dagger.Directory) (string, error) {
return m.BuildEnv(source).
WithExec([]string{"npm", "run", "test:unit", "run"}).
Stdout(ctx)
}
// Build a ready-to-use development environment
func (m *HelloDagger) BuildEnv(source *dagger.Directory) *dagger.Container {
nodeCache := dag.CacheVolume("node")
return dag.Container().
From("node:21-slim").
WithDirectory("/src", source).
WithMountedCache("/root/.npm", nodeCache).
WithWorkdir("/src").
WithExec([]string{"npm", "install"})
}
In this Dagger module, each Dagger Function performs a different operation:
- The
Publish()
Dagger Function tests, builds and publishes a container image of the application to a registry. - The
Test()
Dagger Function runs the application's unit tests and returns the results. - The
Build()
Dagger Function performs a multi-stage build and returns a final container image with the production-ready application and an NGINX Web server to host and serve it. - The
BuildEnv()
Dagger Function creates a container with the build environment for the application.
Replace the generated dagger/src/hello_dagger/main.py
file with the following code, which adds four Dagger Functions to your Dagger module:
import random
import dagger
from dagger import dag, function, object_type
@object_type
class HelloDagger:
@function
async def publish(self, source: dagger.Directory) -> str:
"""Publish the application container after building and testing it on-the-fly"""
await self.test(source)
return await self.build(source).publish(
f"ttl.sh/myapp-{random.randrange(10 ** 8)}"
)
@function
def build(self, source: dagger.Directory) -> dagger.Container:
"""Build the application container"""
build = (
self.build_env(source)
.with_exec(["npm", "run", "build"])
.directory("./dist")
)
return (
dag.container()
.from_("nginx:1.25-alpine")
.with_directory("/usr/share/nginx/html", build)
.with_exposed_port(80)
)
@function
async def test(self, source: dagger.Directory) -> str:
"""Return the result of running unit tests"""
return await (
self.build_env(source)
.with_exec(["npm", "run", "test:unit", "run"])
.stdout()
)
@function
def build_env(self, source: dagger.Directory) -> dagger.Container:
"""Build a ready-to-use development environment"""
node_cache = dag.cache_volume("node")
return (
dag.container()
.from_("node:21-slim")
.with_directory("/src", source)
.with_mounted_cache("/root/.npm", node_cache)
.with_workdir("/src")
.with_exec(["npm", "install"])
)
In this Dagger module, each Dagger Function performs a different operation:
- The
publish()
Dagger Function tests, builds and publishes a container image of the application to a registry. - The
test()
Dagger Function runs the application's unit tests and returns the results. - The
build()
Dagger Function performs a multi-stage build and returns a final container image with the production-ready application and an NGINX Web server to host and serve it. - The
build_env()
Dagger Function creates a container with the build environment for the application.
Replace the generated dagger/src/index.ts
file with the following code, which adds four Dagger Functions to your Dagger module:
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class HelloDagger {
/**
* Publish the application container after building and testing it on-the-fly
*/
@func()
async publish(source: Directory): Promise<string> {
await this.test(source)
return await this.build(source).publish(
"ttl.sh/myapp-" + Math.floor(Math.random() * 10000000),
)
}
/**
* Build the application container
*/
@func()
build(source: Directory): Container {
const build = this.buildEnv(source)
.withExec(["npm", "run", "build"])
.directory("./dist")
return dag
.container()
.from("nginx:1.25-alpine")
.withDirectory("/usr/share/nginx/html", build)
.withExposedPort(80)
}
/**
* Return the result of running unit tests
*/
@func()
async test(source: Directory): Promise<string> {
return this.buildEnv(source)
.withExec(["npm", "run", "test:unit", "run"])
.stdout()
}
/**
* Build a ready-to-use development environment
*/
@func()
buildEnv(source: Directory): Container {
const nodeCache = dag.cacheVolume("node")
return dag
.container()
.from("node:21-slim")
.withDirectory("/src", source)
.withMountedCache("/root/.npm", nodeCache)
.withWorkdir("/src")
.withExec(["npm", "install"])
}
}
In this Dagger module, each Dagger Function performs a different operation:
- The
publish()
Dagger Function tests, builds and publishes a container image of the application to a registry. - The
test()
Dagger Function runs the application's unit tests and returns the results. - The
build()
Dagger Function performs a multi-stage build and returns a final container image with the production-ready application and an NGINX Web server to host and serve it. - The
buildEnv()
Dagger Function creates a container with the build environment for the application.
If you're writing the code above in an IDE, you can easily configure your IDE to recognize your Dagger module. This can significantly speed things up, by giving you automatic type-checking, intelligent code completion, and other IDE features when writing Dagger module code.
The Dagger module's class name is automatically generated based on the name of the directory where dagger init
was executed. If you cloned the example application into a directory other than the default (hello-dagger
), or if you used a different TypeScript application altogether, the auto-generated class name will be different and the code samples above will not work until you update them to use the correct name. Alternatively, you can override Dagger's default class name by specifying a name via the --name
argument to dagger init
.
Run the pipeline
Call a Dagger Function to run the pipeline:
dagger call publish --source=.
This single command runs the application's tests, then builds and publishes it as a container image to the ttl.sh container registry. Here's what you should see:
If you signed up for Dagger Cloud, the output of the previous command would have also included a link to visualize the pipeline run on Dagger Cloud. Click the link in your browser to see a complete breakdown of the steps performed by the pipeline. Here's what you should see:
This is called a "Trace". It represents a single run of a Daggerized pipeline, and shows a detailed log and output of each step in the pipeline. If there are any errors in the run, Dagger Cloud automatically brings you to the first error in the list.
- Even though you just tested, built and published a Node.js application, you didn't need to install any dependencies like
node
ornpm
on your local machine. You only needed the Dagger CLI and the ability to run containers. This is a very powerful feature that eliminates all the variability and dependencies related to the host environment and/or configuration. - Subsequent calls to
dagger call publish...
are significantly faster than the first run (try it!). Dagger caches every operation by default and automatically generates a Directed Acyclic Graph (DAG) to run your pipeline steps concurrently to maximize pipeline speed and accuracy.