Skip to main content

Functions

Dagger Functions are regular code, written in a supported programming language, and running in containers. Dagger Functions let you encapsulate common operations or workflows into discrete units with clear inputs and outputs.

Here's an example of a simple Dagger Function:

import { object, func } from "@dagger.io/dagger"

@object()
class MyModule {
@func()
hello(): string {
return "Hello, world"
}
}

Here is an example call for this Dagger Function:

dagger call hello

The result will be:

Hello, world

Here's an example of a more complex Dagger Function, which calls a remote API method:

import { dag, object, func } from "@dagger.io/dagger"

@object()
class MyModule {
@func()
async getUser(): Promise<string> {
return await dag
.container()
.from("alpine:latest")
.withExec(["apk", "add", "curl"])
.withExec(["apk", "add", "jq"])
.withExec([
"sh",
"-c",
"curl https://randomuser.me/api/ | jq .results[0].name",
])
.stdout()
}
}

Here is an example call for this Dagger Function:

dagger call get-user

The result will look something like this:

{
"title": "Mrs",
"first": "Beatrice",
"last": "Lavigne"
}

Arguments

Dagger Functions, just like regular functions, can accept arguments. In addition to basic types (string, boolean, integer, arrays...), Dagger also defines powerful core types which functions can use for their arguments, such as Directory, Container, Service, Secret, and many more.

Here's an example of modifying the previous Dagger Function to accept a string argument:

import { dag, object, func } from "@dagger.io/dagger"

@object()
class MyModule {
@func()
async getUser(gender: string): Promise<string> {
return await dag
.container()
.from("alpine:latest")
.withExec(["apk", "add", "curl"])
.withExec(["apk", "add", "jq"])
.withExec([
"sh",
"-c",
`curl https://randomuser.me/api/?gender=${gender} | jq .results[0].name`,
])
.stdout()
}
}

Here is an example call for this Dagger Function:

dagger call get-user --gender=male

The result will look something like this:

{
"title": "Mr",
"first": "Hans-Werner",
"last": "Thielen"
}

Here's an example of a Dagger Function that accepts a Directory as argument:

import { dag, Directory, object, func } from "@dagger.io/dagger"

@object()
class MyModule {
@func()
async tree(dir: Directory, depth: string): Promise<string> {
return await dag
.container()
.from("alpine:latest")
.withMountedDirectory("/mnt", dir)
.withWorkdir("/mnt")
.withExec(["apk", "add", "tree"])
.withExec(["tree", "-L", depth])
.stdout()
}
}

Here is an example call for this Dagger Function:

dagger call tree --dir=. --depth=1

The result will look like this:

.
├── LICENSE
├── dagger
└── dagger.json

2 directories, 2 files

You can also pass a remote Git reference, and the Dagger CLI will convert it to a Directory referencing the contents of that repository. Here is an example call that lists the source code for the Dagger CLI, from the main branch of the Dagger GitHub repository:

dagger call tree --dir=https://github.com/dagger/dagger#main:cmd/dagger --depth=1

The result will be the same file listing as this GitHub page:

.
├── call.go
├── cloud.go
├── debug.go
├── engine.go
├── exec_nonunix.go
├── exec_unix.go
├── flags.go
├── functions.go
├── gen.go
├── licenses.go
├── listen.go
├── log.go
├── main.go
├── module.go
├── module_test.go
├── query.go
├── run.go
├── session.go
├── shell.go
└── version.go

1 directory, 20 files
note

When calling a Dagger Function from the CLI, its arguments are exposed as command-line flags. How the flag is interpreted depends on the argument type.

Optional arguments

Function arguments can be marked as optional. In this case, the Dagger CLI will not display an error if the argument is omitted in the function call.

Here's an example of a Dagger Function with an optional argument:

import { object, func } from "@dagger.io/dagger"

@object()
class MyModule {
@func()
hello(name?: string): string {
if (name) {
return `Hello, ${name}`
}
return "Hello, world"
}
}

Here is an example call for this Dagger Function, with the optional argument:

dagger call hello --name=John

The result will look like this:

Hello, John

Here is an example call for this Dagger Function, without the optional argument:

dagger call hello

The result will look like this:

Hello, world

Default arguments

Function arguments can be configured to use default values if no value is supplied for them.

Here's an example of a Dagger Function with a default value for an argument:

import { object, func } from "@dagger.io/dagger"

@object()
class MyModule {
@func()
hello(name = "world"): string {
return `Hello, ${name}`
}
}

Here is an example call for this Dagger Function, without the required argument:

dagger call hello

The result will look like this:

Hello, world

Return values

Not only can Dagger Functions use Dagger's core types in their arguments; they can use them in their return value as well.

This opens powerful applications to Dagger Functions. For example, a Dagger Function that builds binaries could take a directory as argument (the source code) and return another directory (containing binaries) or a container image (with the binaries included).

Here's an example of a Dagger Function that accepts a Directory containing a Go application's source code as input, compiles it into a binary, and returns a Container with the binary:

import { dag, Container, Directory, object, func } from "@dagger.io/dagger"

@object()
class MyModule {
@func()
build(source: Directory, architecture: string, os: string): Container {
const dir = dag
.container()
.from("golang:1.21")
.withMountedDirectory("/src", source)
.withWorkdir("/src")
.withEnvVariable("GOARCH", architecture)
.withEnvVariable("GOOS", os)
.withEnvVariable("CGO_ENABLED", "0")
.withExec(["go", "build", "-o", "build/"])
.directory("/src/build")

return dag
.container()
.from("alpine:latest")
.withDirectory("/usr/local/bin", dir)
}
}

Here is an example call for this Dagger Function:

dagger call build --source=https://github.com/golang/example/#master:hello --os=linux --architecture=amd64 terminal

This example chains two functions calls:

  • a call to Build(), which builds a Go application from a remote GitHub repository and returns a Container with the compiled binary;
  • a call to Terminal(), which opens an interactive terminal session with the Container returned by the previous function.

The result will be an interactive terminal session with the built container, which you can use to validate the compiled Go binary:

/ # cd /usr/local/bin
/usr/local/bin # ls
hello
/usr/local/bin # ./hello
Hello, world!
/usr/local/bin #