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 Dagger Functions can use for their arguments, such as Directory
, Container
, Service
, Secret
, and many more.
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.
Dagger Functions execute in containers and thus do not have default access to your host environment (host files, directories, environment variables, etc.). Access to these host resources can only be granted by explicitly passing them as argument values to the Dagger Function.
- Files and directories: Dagger Functions can accept arguments of type
File
orDirectory
. Pass files and directories on your host by specifying their path as the value of the argument when usingdagger call
. - Environment variables: Pass environment variable values as argument values when invoking a function by just using the standard shell convention of using
$ENV_VAR_NAME
when usingdagger call
. - Local network services: Dagger Functions that accept an argument of type
Service
can be passed local network services in the formtcp://<host>:<port>
.
String arguments
To pass a string argument to a Dagger Function, add the corresponding flag to the dagger call
command, followed by the string value.
Here is an example of a Dagger Function that accepts a string argument:
- Go
- Python
- TypeScript
package main
import (
"context"
"fmt"
)
type MyModule struct{}
func (m *MyModule) GetUser(ctx context.Context, gender string) (string, error) {
return dag.Container().
From("alpine:latest").
WithExec([]string{"apk", "add", "curl"}).
WithExec([]string{"apk", "add", "jq"}).
WithExec([]string{"sh", "-c", fmt.Sprintf("curl https://randomuser.me/api/?gender=%s | jq .results[0].name", gender)}).
Stdout(ctx)
}
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def get_user(self, gender: str) -> str:
return await (
dag.container()
.from_("alpine:latest")
.with_exec(["apk", "add", "curl"])
.with_exec(["apk", "add", "jq"])
.with_exec(
[
"sh",
"-c",
(
f"curl https://randomuser.me/api/?gender={gender}"
" | jq .results[0].name"
),
]
)
.stdout()
)
Even though the Python runtime doesn't enforce type annotations at runtime, it's important to define them with Dagger Functions. The Python SDK needs the typing information at runtime to correctly report to the API. It can't rely on type inference, which is only possible for external static type checkers.
If a function doesn't have a return type annotation, it'll be declared as None
,
which translates to the dagger.Void type in the API:
@function
def hello(self):
return "Hello world!"
# Error: cannot convert string to Void
It's fine however, when no value actually needs to be returned:
@function
def hello(self):
...
# no return
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"
}
To pass host environment variables as arguments when invoking a Dagger Function, use the standard shell convention of $ENV_VAR_NAME
when using dagger call
.
Here is an example of passing a host environment variable containing a string value to a Dagger Function:
export GREETING=bonjour
dagger -m github.com/shykes/daggerverse/hello@v0.3.0 call hello --greeting=$GREETING
Boolean arguments
To pass a Boolean argument to a Dagger Function, simply add the corresponding flag, like so:
- To set the argument to true:
--foo=true
, or simply--foo
- To set the argument to false:
--foo=false
Here is an example of a Dagger Function that accepts a Boolean argument:
- Go
- Python
- TypeScript
package main
import (
"strings"
)
type MyModule struct{}
func (m *MyModule) Hello(shout bool) string {
message := "Hello, world"
if shout {
return strings.ToUpper(message)
}
return message
}
from dagger import function, object_type
@object_type
class MyModule:
@function
def hello(self, shout: bool) -> str:
message = "Hello, world"
if shout:
return message.upper()
return message
import { object, func } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
hello(shout: boolean): string {
const message = "Hello, world"
if (shout) {
return message.toUpperCase()
}
return message
}
}
Here is an example call for this Dagger Function:
dagger call hello --shout=true
The result will look like this:
HELLO, WORLD
Directory arguments
You can also pass a directory argument from the command-line. To do so, add the corresponding flag, followed by a local filesystem path or a remote Git reference. In both cases, the CLI will convert it to an object referencing the contents of that filesystem path or Git repository location, and pass the resulting Directory
object as argument to the Dagger Function.
Local directories
Dagger Functions do not have access to the filesystem of the host you invoke the Dagger Function from (i.e. the host you execute a CLI command like dagger call
from). Instead, host directories need to be explicitly passed as arguments to Dagger Functions.
Here's an example of a Dagger Function that accepts a Directory
as argument. The Dagger Function returns a tree representation of the files and directories at that path.
- Go
- Python
- TypeScript
package main
import (
"context"
"main/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) Tree(ctx context.Context, src *dagger.Directory, depth string) (string, error) {
return dag.Container().
From("alpine:latest").
WithMountedDirectory("/mnt", src).
WithWorkdir("/mnt").
WithExec([]string{"apk", "add", "tree"}).
WithExec([]string{"tree", "-L", depth}).
Stdout(ctx)
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def tree(self, src: dagger.Directory, depth: str) -> str:
return await (
dag.container()
.from_("alpine:latest")
.with_mounted_directory("/mnt", src)
.with_workdir("/mnt")
.with_exec(["apk", "add", "tree"])
.with_exec(["tree", "-L", depth])
.stdout()
)
import { dag, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
async tree(src: Directory, depth: string): Promise<string> {
return await dag
.container()
.from("alpine:latest")
.withMountedDirectory("/mnt", src)
.withWorkdir("/mnt")
.withExec(["apk", "add", "tree"])
.withExec(["tree", "-L", depth])
.stdout()
}
}
Here is an example of passing a local directory to this Dagger Function as argument:
mkdir -p mydir/mysubdir
touch mydir/a mydir/b mydir/c mydir/mysubdir/y mydir/mysubdir/z
dagger call tree --src=mydir --depth=2
The result will look like this:
.
├── a
├── b
├── c
└── mysubdir
├── y
└── z
2 directories, 5 files
Remote repositories
Dagger supports the use of HTTP and SSH protocols for accessing remote repositories, compatible with all major Git hosting platforms such as GitHub, GitLab, BitBucket, Azure DevOps, Codeberg, and Sourcehut. Dagger supports authentication via both HTTPS (using Git credential managers) and SSH (using a unified authentication approach).
Dagger supports the following reference schemes for directory arguments:
Protocol | Scheme | Authentication | Example |
---|---|---|---|
HTTP(S) | Git HTTP | Git credential manager | https://github.com/username/repo.git[#version[:subdir]] |
SSH | Explicit | SSH keys | ssh://git@github.com/username/repo.git[#version[:subdir]] |
SSH | SCP-like | SSH keys | git@github.com:username/repo.git[#version[:subdir]] |
Dagger provides additional flexibility in referencing directory arguments through the following options:
- Version specification: Add
#version
to target a particular version of the repository. This can be a tag, branch name, or full commit hash. If omitted, the default branch is used. - Monorepo support: Append
:subpath
after the version specification to access a specific subdirectory within the repository. Note that specifying a version is mandatory when including a subpath.
When referencing a specific subdirectory (subpath) within a repository, you must always include a version specification. The format is always #version:subpath
.
Here is an example of passing a remote repository (Dagger's open-source repository) over HTTPS as a Directory
argument:
dagger core container \
from --address=alpine:latest \
with-directory --path=/src --directory=https://github.com/dagger/dagger \
with-exec --args="ls","/src" \
stdout
The same repository can also be accessed using SSH. Note that this requires SSH authentication to be properly configured on your Dagger host. Here is the same example, this time using SSH:
dagger core container \
from --address=alpine:latest \
with-directory --path=/src --directory=ssh://git@github.com/dagger/dagger \
with-exec --args="ls","/src" \
stdout
For more information about authentication methods, best practices, and current limitations, refer to the documentation on using modules from remote repositories.
Container arguments
Just like directories, you can pass a container to a Dagger Function from the command-line. To do so, add the corresponding flag, followed by the address of an OCI image. The CLI will dynamically pull the image, and pass the resulting Container
object as argument to the Dagger Function.
Here is an example of a Dagger Function that accepts a container image reference as an argument. The Dagger Function returns operating system information for the container.
- Go
- Python
- TypeScript
package main
import (
"context"
"main/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) OsInfo(ctx context.Context, ctr *dagger.Container) (string, error) {
return ctr.
WithExec([]string{"uname", "-a"}).
Stdout(ctx)
}
import dagger
from dagger import function, object_type
@object_type
class MyModule:
@function
async def os_info(self, ctr: dagger.Container) -> str:
return await ctr.with_exec(["uname", "-a"]).stdout()
import { Container, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
async osInfo(ctr: Container): Promise<string> {
return ctr.withExec(["uname", "-a"]).stdout()
}
}
Here is an example of passing a container image reference to this Dagger Function as an argument.
dagger call os-info --ctr=ubuntu:latest
The result will look like this:
Linux buildkitsandbox 6.1.0-22-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.94-1 (2024-06-21) x86_64 x86_64 x86_64 GNU/Linux
Here is another example of passing a container image reference to a Dagger Function as an argument. The Dagger Function scans the container using Trivy and reports any vulnerabilities found.
dagger -m github.com/jpadams/daggerverse/trivy@v0.3.0 call scan-container --ctr=index.docker.io/alpine:latest
Secret arguments
Dagger allows you to utilize confidential information, such as passwords, API keys, SSH keys and so on, in your Dagger modules and Dagger Functions, without exposing those secrets in plaintext logs, writing them into the filesystem of containers you're building, or inserting them into the cache.
Secrets can be passed to Dagger Functions as arguments using the Secret
core type. Here is an example of a Dagger Function which accepts a GitHub personal access token as a secret, and uses the token to authorize a request to the GitHub API:
- Go
- Python
- TypeScript
package main
import (
"context"
"main/internal/dagger"
)
type MyModule struct{}
// Query the GitHub API
func (m *MyModule) GithubApi(
ctx context.Context,
// GitHub API token
token *dagger.Secret,
) (string, error) {
return dag.Container().
From("alpine:3.17").
WithSecretVariable("GITHUB_API_TOKEN", token).
WithExec([]string{"apk", "add", "curl"}).
WithExec([]string{"sh", "-c", `curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`}).
Stdout(ctx)
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def github_api(
self,
token: Annotated[dagger.Secret, Doc("GitHub API token")],
) -> str:
"""Query the GitHub API"""
return await (
dag.container(platform=dagger.Platform("linux/amd64"))
.from_("alpine:3.17")
.with_secret_variable("GITHUB_API_TOKEN", token)
.with_exec(["apk", "add", "curl"])
.with_exec(
[
"sh",
"-c",
(
'curl "https://api.github.com/repos/dagger/dagger/issues"'
' --header "Authorization: Bearer $GITHUB_API_TOKEN"'
' --header "Accept: application/vnd.github+json"'
),
]
)
.stdout()
)
import { dag, object, func, Secret } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Query the GitHub API
*/
@func()
async githubApi(
/**
* GitHub API token
*/
token: Secret,
): Promise<string> {
return await dag
.container()
.from("alpine:3.17")
.withSecretVariable("GITHUB_API_TOKEN", token)
.withExec(["apk", "add", "curl"])
.withExec([
"sh",
"-c",
`curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`,
])
.stdout()
}
}
The result will be a JSON-formatted list of issues from Dagger's repository.
When invoking the Dagger Function using the Dagger CLI, secrets can be sourced from host environment variables (env:
), the host filesystem (file:
) or the result of host command execution (cmd:
).
Here is an example call for this Dagger Function, with the secret sourced from a host environment variable named GITHUB_API_TOKEN
:
dagger call github-api --token=env:GITHUB_API_TOKEN
Secrets can also be passed from a host file using the file
source:
dagger call github-api --token=file:./github.txt
...or as the result of executing a command on the host using the cmd
source:
dagger call github-api --token=cmd:"gh auth token"
Service arguments
Host network services or sockets can be passed to Dagger Functions as arguments. To do so, add the corresponding flag, followed by a service or socket reference.
TCP and UDP services
To pass host TCP or UDP network services as arguments when invoking a Dagger Function, specify them in the form tcp://<host>:<port>
or udp://<host>:<port>
.
Assume that you have a PostgresQL database running locally on port 5432, as with:
docker run -d -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres
Here is an example of passing this host service as argument to a PostgreSQL client Dagger Function, which drops you to a prompt from where you can execute SQL queries:
dagger -m github.com/kpenfound/dagger-modules/postgres@v0.1.0 call client --db=postgres --user=postgres --password=postgres --server=tcp://localhost:5432
Unix sockets
Similar to host TCP/UDP services, Dagger Functions can also be granted access to host Unix sockets when the client is running on Linux or MacOS.
To pass host Unix sockets as arguments when invoking a Dagger Function, specify them by their path on the host.
For example, assuming you have Docker on your host with the Docker daemon listening on a Unix socket at /var/run/docker.sock
, you can pass this socket to a Docker client Dagger Function as follows:
dagger -m github.com/sipsma/daggerverse/docker-client@v0.0.1 call --sock=/var/run/docker.sock version
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:
- Go
- Python
- TypeScript
package main
import (
"context"
"fmt"
)
type MyModule struct{}
func (m *MyModule) Hello(
ctx context.Context,
// +optional
name string,
) (string, error) {
if name != "" {
return fmt.Sprintf("Hello, %s", name), nil
} else {
return "Hello, world", nil
}
}
from dagger import function, object_type
@object_type
class MyModule:
@function
def hello(self, name: str | None) -> str:
if name is None:
name = "world"
return f"Hello, {name}"
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 values
Function arguments can define a default value if no value is supplied for them.
Here's an example of a Dagger Function with a default value for a string argument:
- Go
- Python
- TypeScript
package main
import (
"context"
"fmt"
)
type MyModule struct{}
func (m *MyModule) Hello(
ctx context.Context,
// +optional
// +default="world"
name string,
) (string, error) {
return fmt.Sprintf("Hello, %s", name), nil
}
from dagger import function, object_type
@object_type
class MyModule:
@function
def hello(self, name: str = "world") -> str:
return f"Hello, {name}"
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
Directories and files
It is possible to automatically load a filesystem path as a Directory
or File
object in a Dagger Function, by passing it as a "default path" to the corresponding argument. The Directory
or File
loaded in this manner is not merely a string, but it is the actual filesystem state of the specified directory or file, managed by the Dagger Engine and handled in code just like any another variable.
Default contexts are only available for arguments of type Directory
and File
. They are commonly used to load constant filesystem locations, such as an application's source code directory.
When determining how to resolve a default path, Dagger first identifies a "context directory".
- For Git repositories (defined by the presence of a
.git
sub-directory), the context directory is the repository root (for absolute paths), or the directory containing adagger.json
file (for relative paths). - For all other cases, the context directory is the directory containing a
dagger.json
file.
The default path is then resolved starting from the context directory.
For security reasons, it is not possible to retrieve files or directories outside the context directory.
The best way to understand this is with an example. Consider the following directory structure, representing a project with a Dagger module in my-module
:
.
├── README.md
├── my-module
│ ├── dagger.json
│ ├── ...
│ └── src
│ └── main
├── index.html
├── public
│ └── ...
├── src
│ └── ...
Here are two Dagger Functions with default paths for their directory and file arguments:
- Go
- Python
- TypeScript
The default path is set by adding a defaultPath
pragma on the corresponding Dagger Function source
argument.
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) ReadDir(
ctx context.Context,
// +defaultPath="/"
source *dagger.Directory,
) ([]string, error) {
return source.Entries(ctx)
}
func (m *MyModule) ReadFile(
ctx context.Context,
// +defaultPath="/README.md"
source *dagger.File,
) (string, error) {
return source.Contents(ctx)
}
The default path is set by adding a DefaultPath
annotation on the corresponding Dagger Function source
argument.
from typing import Annotated
import dagger
from dagger import DefaultPath, function, object_type
@object_type
class MyModule:
@function
async def read_dir(
self,
source: Annotated[dagger.Directory, DefaultPath("/")],
) -> list[str]:
return await source.entries()
@function
async def read_file(
self,
source: Annotated[dagger.File, DefaultPath("/README.md")],
) -> list[str]:
return await source.contents()
The default path is set by adding an @argument
decorator with a defaultPath
parameter on the corresponding Dagger Function source
argument.
import { Directory, File, argument, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
async readDir(
@argument({ defaultPath: "/" }) source: Directory,
): Promise<string[]> {
return await source.entries()
}
@func()
async readFile(
@argument({ defaultPath: "/README.md" }) source: File,
): Promise<string> {
return await source.contents()
}
}
The following table describes how the context directory and source
argument is resolved, depending on whether or not the project directory is a Git repository.
When the given project directory is a Git repository:
Default path ('defaultPath`) | Context directory | Dagger Function invocation | source argument resolves to |
---|---|---|---|
/ | / | dagger call -m my-module read-dir | / |
/src | / | dagger call -m my-module read-dir | /src |
/README.md | / | dagger call -m my-module read-file | /README.md |
. | /my-module | dagger call -m my-module read-dir | /my-module |
.. | /my-module | dagger call -m my-module read-dir | / |
./README.md | /my-module | dagger call -m my-module read-file | Error: no such file or directory |
../README.md | /my-module | dagger call -m my-module read-file | /README.md |
When the given project directory is not a Git repository:
Default path ('defaultPath`) | Context directory | Dagger Function invocation | source argument resolves to |
---|---|---|---|
/ | /my-module | dagger call -m my-module read-dir | /my-module |
/src | /my-module | dagger call -m my-module read-dir | /my-module/src |
/README.md | /my-module | dagger call -m my-module read-file | Error: no such file or directory |
. | /my-module | dagger call -m my-module read-dir | /my-module |
.. | /my-module | dagger call -m my-module read-dir | Error: path outside of context directory |
./README.md | /my-module | dagger call -m my-module read-file | Error: no such file or directory |
../README.md | /my-module | dagger call -m my-module read-file | Error: path outside of context directory |
If a value is explicitly passed to the Dagger Function argument, it always overrides the default path.
Default path ('defaultPath`) | Dagger Function invocation | source argument resolves to |
---|---|---|
/ | dagger call -m my-module read-dir --source=public | ./public |
/src | dagger call -m my-module read-dir --source=public | ./public |
. | dagger call -m my-module read-dir --source=public | ./public |
.. | dagger call -m my-module read-dir --source=public | ./public |
It's also possible to provide an ignore
parameter to a contextual argument of type Directory
to automatically ignore or include files in the directory.