Security
Dagger is secure by design. All Dagger Functions are fully "sandboxed" and do not have direct access to the host system. Dagger also natively supports reading secrets from multiple secret providers and has built-in safeguards to ensure that secrets do not leak into the open.
Sandboxing
Dagger is built to protect your host environment by default. This means that Dagger Functions cannot access host resources - such as the host environment, host services, host filesystem, or host SSH agent - unless you explicitly grant permission by passing them as arguments.
By requiring typed arguments such as Directory
, Socket
, Service
, or Secret
, Dagger ensures users understand exactly what they are sharing with a Dagger Function. This explicit access design helps prevent malicious or untrusted modules from inadvertently obtaining sensitive data.
Dagger’s security model treats the top-level module (i.e., the main function or highest-level call) as the single place where sensitive resources are introduced. It is only through this call that a user can explicitly provide directories, services, sockets, or secrets. In turn, the top-level module may pass these resources to other installed modules if needed, but there is never any implicit sharing.
Secrets
Dagger also natively supports the use of confidential information ("secrets") such as passwords, API keys, SSH keys, and access tokens. These secrets can be sourced from different secret providers, including the host environment, the host filesystem, the result of host command execution, and external secret managers 1Password and Vault.
Dagger has built-in safeguards to ensure that secrets are used without exposing them in plaintext logs, writing them into the filesystem of containers you're building, or inserting them into the cache. This ensures that sensitive data does not leak - for example, in the event of a crash.
Here's an example of a pipeline that receives and uses a GitHub personal access token as a secret:
- Go
- Python
- TypeScript
- PHP
- Java
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) GithubApi(
ctx context.Context,
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)
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def github_api(
self,
token: dagger.Secret,
) -> str:
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 {
@func()
async githubApi(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\{DaggerObject, DaggerFunction};
use Dagger\Secret;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
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 secret can be passed from the host environment via the env
provider:
Secrets can also be passed from host files via the file
provider (shown below) or from host command output via the cmd
provider:
Secrets can also be read from external secret managers, such as Vault (vault
):
dagger call github-api --token=vault://credentials.github
...or 1Password (op
):
dagger call github-api --token=op://infra/github/credential