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. - 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.
- Local network services: Dagger Functions that accept an argument of type
Service
can be passed local network services in the formtcp://HOST:PORT
.
When passing values to Dagger Functions within Dagger Shell, required arguments are positional, while flags can be placed anywhere.
String arguments
Here is an example of a Dagger Function that accepts a string argument:
- Go
- Python
- TypeScript
- PHP
- Java
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()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction};
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function getUser(string $gender): string
{
return 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();
}
}
Even though PHP doesn't enforce type annotations at runtime, it's important to define them with Dagger Functions. The PHP SDK needs the typing information at runtime to correctly report to the API.
package io.dagger.modules.mymodule;
import static io.dagger.client.Dagger.dag;
import io.dagger.client.DaggerQueryException;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
import java.util.List;
import java.util.concurrent.ExecutionException;
@Object
public class MyModule {
@Function
public String getUser(String gender)
throws ExecutionException, DaggerQueryException, InterruptedException {
return dag().container()
.from("alpine:latest")
.withExec(List.of("apk", "add", "curl"))
.withExec(List.of("apk", "add", "jq"))
.withExec(
List.of(
"sh",
"-c",
"curl https://randomuser.me/api/?gender=%s | jq .results[0].name"
.formatted(gender)))
.stdout();
}
}
Here is an example call for this Dagger Function:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'get-user male'
get-user male
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
.
Here is an example of passing a host environment variable containing a string value to a Dagger Function:
export GREETING=bonjour
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github.com/shykes/daggerverse/hello@v0.3.0 | hello --greeting=$GREETING'
github.com/shykes/daggerverse/hello@v0.3.0 | hello --greeting=$GREETING
dagger -m github.com/shykes/daggerverse/hello@v0.3.0 call hello --greeting=$GREETING
Boolean arguments
Here is an example of a Dagger Function that accepts a Boolean argument:
- Go
- Python
- TypeScript
- PHP
- Java
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
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction};
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function hello(bool $shout): string
{
$message = 'Hello, world';
if ($shout) {
return strtoupper($message);
}
return $message;
}
}
You can either use the primitive boolean
type or the boxed java.lang.Boolean
type.
package io.dagger.modules.mymodule;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
@Object
public class MyModule {
@Function
public String hello(boolean shout) {
String message = "Hello, world";
if (shout) {
message = message.toUpperCase();
}
return message;
}
}
Here is an example call for this Dagger Function:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'hello true'
hello true
dagger call hello --shout=true
The result will look like this:
HELLO, WORLD
When passing optional boolean flags:
- To set the argument to true:
--foo=true
or--foo
- To set the argument to false:
--foo=false
Integer arguments
Here is an example of a Dagger function that accepts an integer argument:
- Go
- Python
- TypeScript
- PHP
- Java
package main
type MyModule struct{}
func (m *MyModule) AddInteger(a int, b int) int {
return a + b
}
from dagger import function, object_type
@object_type
class MyModule:
@function
async def add_integer(self, a: int, b: int) -> int:
return a + b
import { object, func } from "@dagger.io/dagger"
@object()
export class MyModule {
@func()
addInteger(a: number, b: number): number {
return a + b
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction};
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function addInteger(int $a, int $b): int
{
return $a + $b;
}
}
You can either use the primitive int
type or the boxed java.lang.Integer
type.
package io.dagger.modules.mymodule;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
@Object
public class MyModule {
@Function
public Integer addInteger(int a, int b) {
return a + b;
}
}
Here is an example call for this Dagger Function:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'add-integer 1 2'
add-integer 1 2
dagger call add-integer --a=1 --b=2
The result will look like this:
3
Floating-point number arguments
Here is an example of a Dagger function that accepts a floating-point number as argument:
- Go
- Python
- TypeScript
- PHP
- Java
package main
type MyModule struct{}
func (m *MyModule) AddFloat(a float64, b float64) float64 {
return a + b
}
from dagger import function, object_type
@object_type
class MyModule:
@function
async def add_float(self, a: float, b: float) -> float:
return a + b
There's no float
type keyword in TypeScript because the type keyword number
already supports floating point numbers.
To declare a float
argument on the function signature, import float
from @dagger.io/dagger
and use it as an argument's type.
The imported float
type is a number
underneath, so you can use it as you would use a number
inside your function.
import type { float } from "@dagger.io/dagger"
import { object, func } from "@dagger.io/dagger"
@object()
export class MyModule {
@func()
addFloat(a: float, b: float): float {
return a + b
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction};
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function addFloat(float $a, float $b): float
{
return $a + $b;
}
}
You can either use the primitive float
type or the boxed java.lang.Float
type.
package io.dagger.modules.mymodule;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
@Object
public class MyModule {
@Function
public float addFloat(float a, float b) {
return a + b;
}
}
Here is an example call for this Dagger Function:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'add-float 1.4 2.7'
add-float 1.4 2.7
dagger call add-float --a=1.4 --b=2.7
The result will look like this:
4.1
Array arguments
To pass an array argument to a Dagger Function, use a comma-separated list of values.
- Go
- Python
- TypeScript
- PHP
- Java
package main
import (
"strings"
)
type MyModule struct{}
func (m *MyModule) Hello(names []string) string {
message := "Hello"
if len(names) > 0 {
message += " " + strings.Join(names, ", ")
}
return message
}
from dagger import function, object_type
@object_type
class MyModule:
@function
def hello(self, names: list[str]) -> str:
message = "Hello"
for name in names:
message += f", {name}"
return message
import { object, func } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
hello(names: string[]): string {
let message = "Hello"
for (const name of names) {
message += ` ${name}`
}
return message
}
}
Lists must have their subtype specified by adding the #[ListOfType]
attribute to the relevant function argument.
The PHP SDK needs the typing information at runtime to correctly report to the API.
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, ListOfType};
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function hello(#[ListOfType('string')]array $names): string
{
$message = 'Hello';
if (!empty($names)) {
$message .= " " . implode(', ', $names);
}
return $message;
}
}
You can also use the java.util.List
interface to represent a list of values.
For instance instead of the String[] names
argument in the example, you can have List<String> names
.
package io.dagger.modules.mymodule;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
@Object
public class MyModule {
@Function
public String hello(String[] names) {
String message = "Hello";
if (names.length > 0) {
message += " " + String.join(", ", names);
}
return message;
}
}
Here is an example call for this Dagger Function:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'hello John,Jane'
hello John,Jane
dagger call hello --names=John,Jane
The result will look like this:
Hello John, Jane
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.
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
- PHP
- Java
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()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, ListOfType};
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function tree(Directory $src, string $depth): string
{
return dag()
->container()
->from('alpine:latest')
->withMountedDirectory('/mnt', $src)
->withWorkdir('/mnt')
->withExec(['apk', 'add', 'tree'])
->withExec(['tree', '-L', $depth])
->stdout();
}
}
package io.dagger.modules.mymodule;
import static io.dagger.client.Dagger.dag;
import io.dagger.client.DaggerQueryException;
import io.dagger.client.Directory;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
import java.util.List;
import java.util.concurrent.ExecutionException;
@Object
public class MyModule {
@Function
public String tree(Directory src, String depth)
throws ExecutionException, DaggerQueryException, InterruptedException {
return dag().container()
.from("alpine:latest")
.withMountedDirectory("/mnt", src)
.withWorkdir("/mnt")
.withExec(List.of("apk", "add", "tree"))
.withExec(List.of("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
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'tree mydir 2'
tree mydir 2
dagger call tree --src=mydir --depth=2
The result will look like this:
.
├── a
├── b
├── c
└── mysubdir
├── y
└── z
2 directories, 5 files
Here is an example of passing a remote repository (Dagger's open-source repository) over HTTPS as a Directory
argument:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
container |
from alpine:latest |
with-directory /src https://github.com/dagger/dagger |
with-exec ls /src |
stdout
EOF
container |
from alpine:latest |
with-directory /src https://github.com/dagger/dagger |
with-exec ls /src |
stdout
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:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
container |
from alpine:latest |
with-directory /src ssh://git@github.com/dagger/dagger |
with-exec ls /src |
stdout
EOF
container |
from alpine:latest |
with-directory /src ssh://git@github.com/dagger/dagger |
with-exec ls /src |
stdout
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 remote repository access, refer to the documentation on reference schemes and authentication methods.
Dagger offers two important features for working with Directory
arguments:
- Default paths: Set a default directory path to use no value is specified for the argument.
- Filters: Control which files and directories are uploaded to a Dagger Function.
File arguments
File arguments work in the same way as directory arguments. To pass a file to a Dagger Function as an argument, 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 that filesystem path or Git repository location, and pass the resulting File
object as argument to the Dagger Function.
Here's an example of a Dagger Function that accepts a File
as argument, reads it, and returns its contents:
- Go
- Python
- TypeScript
- PHP
- Java
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) ReadFile(ctx context.Context, source *dagger.File) (string, error) {
contents, err := dag.Container().
From("alpine:latest").
WithFile("/src/myfile", source).
WithExec([]string{"cat", "/src/myfile"}).
Stdout(ctx)
if err != nil {
return "", err
}
return contents, nil
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def read_file(self, source: dagger.File) -> str:
return await (
dag.container()
.from_("alpine:latest")
.with_file("/src/myfile", source)
.with_exec(["cat", "/src/myfile"])
.stdout()
)
import { dag, object, func, File } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
async readFile(source: File): Promise<string> {
return await dag
.container()
.from("alpine:latest")
.withFile("/src/myfile", source)
.withExec(["cat", "/src/myfile"])
.stdout()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, ListOfType};
use Dagger\File;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function readFile(File $source): string
{
return dag()
->container()
->from('alpine:latest')
->withFile('/src/myfile', $source)
->withExec(['cat', '/src/myfile'])
->stdout();
}
}
package io.dagger.modules.mymodule;
import static io.dagger.client.Dagger.dag;
import io.dagger.client.DaggerQueryException;
import io.dagger.client.File;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
import java.util.List;
import java.util.concurrent.ExecutionException;
@Object
public class MyModule {
@Function
public String readFile(File source)
throws ExecutionException, DaggerQueryException, InterruptedException {
return dag().container()
.from("alpine:latest")
.withFile("/src/myfile", source)
.withExec(List.of("cat", "/src/myfile"))
.stdout();
}
}
Here is an example of passing a local file to this Dagger Function as argument:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'read-file /my/file/path/README.md'
read-file /my/file/path/README.md
dagger call read-file --source=/my/file/path/README.md
And here is an example of passing a file from a remote Git repository as argument:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'read-file https://github.com/dagger/dagger.git#main:README.md'
read-file https://github.com/dagger/dagger.git#main:README.md
dagger call read-file --source=https://github.com/dagger/dagger.git#main:README.md
For more information about remote repository access, refer to the documentation on reference schemes and authentication methods.
Dagger offers two important features for working with File
arguments:
- Default paths: Set a default file path to use no value is specified for the argument.
- Filters: Control which files are uploaded to a Dagger Function.
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
- PHP
- Java
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()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction};
use Dagger\Container;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function osInfo(Container $ctr): string
{
return $ctr->withExec(['uname', '-a'])->stdout();
}
}
package io.dagger.modules.mymodule;
import io.dagger.client.Container;
import io.dagger.client.DaggerQueryException;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
import java.util.List;
import java.util.concurrent.ExecutionException;
@Object
public class MyModule {
@Function
public String osInfo(Container ctr)
throws ExecutionException, DaggerQueryException, InterruptedException {
return ctr.withExec(List.of("uname", "-a")).stdout();
}
}
Here is an example of passing a container image reference to this Dagger Function as an argument.
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'os-info ubuntu:latest'
os-info ubuntu:latest
dagger call os-info --ctr=ubuntu:latest
The result will look like this:
Linux dagger 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.
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github.com/jpadams/daggerverse/trivy@v0.3.0 | scan-container index.docker.io/alpine:latest'
github.com/jpadams/daggerverse/trivy@v0.3.0 | scan-container index.docker.io/alpine:latest
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
- PHP
- Java
package main
import (
"context"
"dagger/my-module/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()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerFunction, DaggerObject, Doc};
use Dagger\Secret;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Query the Github API')]
public function githubApi(Secret $token): string
{
return 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 "Authorization: Bearer $GITHUB_API_TOKEN"'
. ' --header "Accept: application/vnd.github+json"',
])
->stdout();
}
}
package io.dagger.modules.mymodule;
import static io.dagger.client.Dagger.dag;
import io.dagger.client.DaggerQueryException;
import io.dagger.client.Secret;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
import java.util.List;
import java.util.concurrent.ExecutionException;
@Object
public class MyModule {
@Function
public String githubApi(Secret token)
throws ExecutionException, DaggerQueryException, InterruptedException {
return dag().container()
.from("alpine:3.17")
.withSecretVariable("GITHUB_API_TOKEN", token)
.withExec(List.of("apk", "add", "curl"))
.withExec(
List.of(
"sh",
"-c",
"curl \"https://api.github.com/repos/dagger/dagger/issues\""
+ " --header \"Authorization: Bearer $GITHUB_API_TOKEN\""
+ " --header \"Accept: application/vnd.github+json\""))
.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 multiple providers. Dagger can read secrets from the host environment, the host filesystem, and the result of host command execution, as well as from external secret managers 1Password and Vault.
Host secret providers
Here is an example call for this Dagger Function, with the secret sourced from a host environment variable named GITHUB_API_TOKEN
via the env
provider:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github-api env://GITHUB_API_TOKEN'
github-api env://GITHUB_API_TOKEN
dagger call github-api --token=env://GITHUB_API_TOKEN
Secrets can also be passed from a host file using the file
provider:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github-api file://./github.txt'
github-api file://./github.txt
dagger call github-api --token=file://./github.txt
...or as the result of executing a command on the host using the cmd
provider:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github-api cmd://"gh auth token"'
github-api cmd://"gh auth token"
dagger call github-api --token=cmd://"gh auth token"
External secret providers
Secrets can also be sourced from external secret managers. Currently, Dagger supports 1Password and Vault.
1Password requires creating a service account and then setting the OP_SERVICE_ACCOUNT_TOKEN
environment variable. Alternatively, if no OP_SERVICE_ACCOUNT_TOKEN
is provided, the integration will attempt to execute the (official) op
CLI if installed in the system.
1Password secret references, in the format op://VAULT-NAME/ITEM-NAME/[SECTION-NAME/]FIELD-NAME
are supported. Here is an example:
export OP_SERVICE_ACCOUNT_TOKEN="mytoken"
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github-api op://infra/github/credential'
github-api op://infra/github/credential
dagger call github-api --token=op://infra/github/credential
Vault can be authenticated with either token or AppRole methods. The Vault host can be specified by setting the environment variable VAULT_ADDR
. For token authentication, set the environment variable VAULT_TOKEN
. For AppRole authentication, set the environment variables VAULT_APPROLE_ROLE_ID
and VAULT_APPROLE_SECRET_ID
. Additional client configuration can be specified by the default environment variables accepted by Vault.
Vault KvV2 secrets are accessed with the scheme vault://PATH/TO/SECRET.ITEM
. If your KvV2 is not mounted at /secret
, specify the mount location with the environment variable VAULT_PATH_PREFIX
. Here is an example:
export VAULT_ADDR='https://example.com:8200'
export VAULT_TOKEN=abcd_1234
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github-api vault://infra/github.credential'
github-api vault://infra/github.credential
dagger call github-api --token=vault://infra/github.credential
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 --rm -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:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
github.com/kpenfound/dagger-modules/postgres@v0.1.0 |
client tcp://localhost:5432 postgres postgres postgres
EOF
github.com/kpenfound/dagger-modules/postgres@v0.1.0 | client tcp://localhost:5432 postgres postgres postgres
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:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github.com/sipsma/daggerverse/docker-client@v0.0.1 /var/run/docker.sock | version'
github.com/sipsma/daggerverse/docker-client@v0.0.1 /var/run/docker.sock | version
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
- PHP
- Java
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"
}
}
The definition of optional varies between Dagger and PHP.
An optional argument to PHP is one that has a default value.
An optional argument to Dagger can be omitted entirely. It is truly optional.
To specify a function argument as optional, simply make it nullable. When using the Dagger CLI, if the argument is omitted; the PHP SDK will treat this as receiving the value null
.
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerFunction, DaggerObject};
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function hello(?string $name): string
{
if (isset($name)) {
return "Hello, {$name}";
}
return 'Hello, world';
}
}
Because of the usage of Optional
, primitive types can not be marked as optional. You have to use the boxed types like Integer
or Boolean
.
When an argument is not set as optional, Dagger will ensure the value is not null
by adding a call to Objects.requireNonNull
against the argument.
package io.dagger.modules.mymodule;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
import java.util.Optional;
@Object
public class MyModule {
@Function
public String hello(Optional<String> name) {
return "Hello, " + name.orElse("World");
}
}
Here is an example call for this Dagger Function, with the optional argument:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'hello --name=John'
hello --name=John
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:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c hello
hello
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
- PHP
- Java
package main
import (
"context"
"fmt"
)
type MyModule struct{}
func (m *MyModule) Hello(
ctx context.Context,
// +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}`
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerFunction, DaggerObject};
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function hello(string $name = 'world'): string
{
return "Hello, {$name}";
}
}
The default value provided must be a valid JSON string representation of the type.
For instance if the argument is of type Integer
and the default value is 123
, then the annotation must be @Default("123")
.
If the argument is of type String
and the default value is world
, then the annotation should be @Default("\"world\"")
.
In order to simplify this very specific case, if the argument is of type String
and the value doesn't start with an escaped quote,
then the SDK will automatically add the escaped quotes for you. That way you can simply write @Default("world")
.
package io.dagger.modules.mymodule;
import io.dagger.module.annotation.Default;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
@Object
public class MyModule {
@Function
public String hello(@Default("world") String name) {
return "Hello, %s".formatted(name);
}
}
Here is an example call for this Dagger Function, without the required argument:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c hello
hello
dagger call hello
The result will look like this:
Hello, world
Passing null to an optional argument signals that no default value should be used.
Dagger supports default paths for Directory
or File
arguments. Dagger will automatically use this default path when no value is specified for the corresponding argument.
Reference schemes for remote repositories
Dagger supports the use of HTTP and SSH protocols for accessing files and directories in 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 file and directory arguments:
Protocol | Scheme | Authentication | Example |
---|---|---|---|
HTTP(S) | Git HTTP | Git credential manager | https://github.com/username/repo.git[#version[:subpath]] |
SSH | Explicit | SSH keys | ssh://git@github.com/username/repo.git[#version[:subpath]] |
SSH | SCP-like | SSH keys | git@github.com:username/repo.git[#version[:subpath]] |
Dagger provides additional flexibility in referencing file and 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
.