Cookbook
The Dagger Cookbook provides practical, real-world examples for common development workflows. Each recipe shows you how to solve specific problems using Dagger's features and APIs.
These recipes demonstrate patterns you can adapt for your own projects. They cover everything from basic container operations to advanced CI/CD workflows and AI agent integrations. Use your browser's find function (Ctrl+F or Cmd+F) to quickly locate specific topics.
Builds​
Build and compile applications in reproducible container environments. This section contains practical examples for building artifacts (files and directories) with Dagger.
Perform a multi-stage build​
The following Dagger Function performs a multi-stage build.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Build and publish Docker container
func (m *MyModule) Build(
ctx context.Context,
// source code location
// can be local directory or remote Git repository
src *dagger.Directory,
) (string, error) {
// build app
builder := dag.Container().
From("golang:latest").
WithDirectory("/src", src).
WithWorkdir("/src").
WithEnvVariable("CGO_ENABLED", "0").
WithExec([]string{"go", "build", "-o", "myapp"})
// publish binary on alpine base
prodImage := dag.Container().
From("alpine").
WithFile("/bin/myapp", builder.File("/src/myapp")).
WithEntrypoint([]string{"/bin/myapp"})
// publish to ttl.sh registry
addr, err := prodImage.Publish(ctx, "ttl.sh/myapp:latest")
if err != nil {
return "", err
}
return addr, nil
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def build(self, src: dagger.Directory) -> str:
"""Build and publish Docker container"""
# build app
builder = (
dag.container()
.from_("golang:latest")
.with_directory("/src", src)
.with_workdir("/src")
.with_env_variable("CGO_ENABLED", "0")
.with_exec(["go", "build", "-o", "myapp"])
)
# publish binary on alpine base
prod_image = (
dag.container()
.from_("alpine")
.with_file("/bin/myapp", builder.file("/src/myapp"))
.with_entrypoint(["/bin/myapp"])
)
# publish to ttl.sh registry
addr = prod_image.publish("ttl.sh/myapp:latest")
return addr
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build and publish Docker container
*/
@func()
build(src: Directory): Promise<string> {
// build app
const builder = dag
.container()
.from("golang:latest")
.withDirectory("/src", src)
.withWorkdir("/src")
.withEnvVariable("CGO_ENABLED", "0")
.withExec(["go", "build", "-o", "myapp"])
// publish binary on alpine base
const prodImage = dag
.container()
.from("alpine")
.withFile("/bin/myapp", builder.file("/src/myapp"))
.withEntrypoint(["/bin/myapp"])
// publish to ttl.sh registry
const addr = prodImage.publish("ttl.sh/myapp:latest")
return addr
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
// Build and publish Docker container
#[DaggerFunction]
public function build(Directory $src): string
{
// build app
$builder = dag()
->container()
->from('golang:latest')
->withDirectory('/src', $src)
->withWorkdir('/src')
->withEnvVariable('CGO_ENABLED', '0')
->withExec(['go', 'build', '-o', 'myapp']);
// publish binary on alpine base
$prodImage = dag()
->container()
->from('alpine')
->withFile('/bin/myapp', $builder->file('/src/myapp'))
->withEntrypoint(['/bin/myapp']);
// publish to ttl.sh registry
$addr = $prodImage->publish('ttl.sh/myapp:latest');
return $addr;
}
}
Example​
Perform a multi-stage build of the source code in the golang/example/hello repository and publish the resulting image:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'build https://github.com/golang/example#master:hello'
build https://github.com/golang/example#master:hello
dagger call build --src="https://github.com/golang/example#master:hello"
Perform a matrix build​
The following Dagger Function performs a matrix build.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
"fmt"
)
type MyModule struct{}
// Build and return directory of go binaries
func (m *MyModule) Build(
ctx context.Context,
// Source code location
src *dagger.Directory,
) *dagger.Directory {
// define build matrix
gooses := []string{"linux", "darwin"}
goarches := []string{"amd64", "arm64"}
// create empty directory to put build artifacts
outputs := dag.Directory()
golang := dag.Container().
From("golang:latest").
WithDirectory("/src", src).
WithWorkdir("/src")
for _, goos := range gooses {
for _, goarch := range goarches {
// create directory for each OS and architecture
path := fmt.Sprintf("build/%s/%s/", goos, goarch)
// build artifact
build := golang.
WithEnvVariable("GOOS", goos).
WithEnvVariable("GOARCH", goarch).
WithExec([]string{"go", "build", "-o", path})
// add build to outputs
outputs = outputs.WithDirectory(path, build.Directory(path))
}
}
// return build directory
return outputs
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def build(self, src: dagger.Directory) -> dagger.Directory:
"""Build and return directory of go binaries"""
# define build matrix
gooses = ["linux", "darwin"]
goarches = ["amd64", "arm64"]
# create empty directory to put build artifacts
outputs = dag.directory()
golang = (
dag.container()
.from_("golang:latest")
.with_directory("/src", src)
.with_workdir("/src")
)
for goos in gooses:
for goarch in goarches:
# create directory for each OS and architecture
path = f"build/{goos}/{goarch}/"
# build artifact
build = (
golang.with_env_variable("GOOS", goos)
.with_env_variable("GOARCH", goarch)
.with_exec(["go", "build", "-o", path])
)
# add build to outputs
outputs = outputs.with_directory(path, build.directory(path))
return await outputs
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build and return directory of go binaries
*/
@func()
build(src: Directory): Directory {
// define build matrix
const gooses = ["linux", "darwin"]
const goarches = ["amd64", "arm64"]
// create empty directory to put build artifacts
let outputs = dag.directory()
const golang = dag
.container()
.from("golang:latest")
.withDirectory("/src", src)
.withWorkdir("/src")
for (const goos of gooses) {
for (const goarch of goarches) {
// create a directory for each OS and architecture
const path = `build/${goos}/${goarch}/`
// build artifact
const build = golang
.withEnvVariable("GOOS", goos)
.withEnvVariable("GOARCH", goarch)
.withExec(["go", "build", "-o", path])
// add build to outputs
outputs = outputs.withDirectory(path, build.directory(path))
}
}
return outputs
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
// define build matrix
const GOOSES = ['linux', 'darwin'];
const GOARCHES = ['amd64', 'arm64'];
// Build and return directory of go binaries
#[DaggerFunction]
public function build(Directory $src): Directory
{
// create empty directory to put build artifacts
$outputs = dag()->directory();
$golang = dag()
->container()
->from('golang:latest')
->withDirectory('/src', $src)
->withWorkdir('/src');
foreach (self::GOOSES as $goos) {
foreach (self::GOARCHES as $goarch) {
// create a directory for each OS and architecture
$path = "build/$goos/$goarch/";
// build artifact
$build = $golang
->withEnvVariable('GOOS', $goos)
->withEnvVariable('GOARCH', $goarch)
->withExec(['go', 'build', '-o', $path]);
// add build to outputs
$outputs = $outputs->withDirectory($path, $build->directory($path));
}
}
return $outputs;
}
}
Example​
Perform a matrix build of the source code in the golang/example/hello repository and export build directory with go binaries for different operating systems and architectures.
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'build https://github.com/golang/example#master:hello | export /tmp/matrix-builds'
build https://github.com/golang/example#master:hello | export /tmp/matrix-builds
dagger call \
build --src="https://github.com/golang/example#master:hello" \
export --path=/tmp/matrix-builds
Inspect the contents of the exported directory with tree /tmp/matrix-builds. The output should look like this:
/tmp/matrix-builds
└── build
├── darwin
│ ├── amd64
│ │ └── hello
│ └── arm64
│ └── hello
└── linux
├── amd64
│ └── hello
└── arm64
└── hello
8 directories, 4 files
Build multi-arch image​
The following Dagger Function builds a single image for different CPU architectures using native emulation.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Build and publish multi-platform image
func (m *MyModule) Build(
ctx context.Context,
// Source code location
// can be local directory or remote Git repository
src *dagger.Directory,
) (string, error) {
// platforms to build for and push in a multi-platform image
var platforms = []dagger.Platform{
"linux/amd64", // a.k.a. x86_64
"linux/arm64", // a.k.a. aarch64
"linux/s390x", // a.k.a. IBM S/390
}
// container registry for the multi-platform image
const imageRepo = "ttl.sh/myapp:latest"
platformVariants := make([]*dagger.Container, 0, len(platforms))
for _, platform := range platforms {
// pull golang image for this platform
ctr := dag.Container(dagger.ContainerOpts{Platform: platform}).
From("golang:1.20-alpine").
// mount source code
WithDirectory("/src", src).
// mount empty dir where built binary will live
WithDirectory("/output", dag.Directory()).
// ensure binary will be statically linked and thus executable
// in the final image
WithEnvVariable("CGO_ENABLED", "0").
// build binary and put result at mounted output directory
WithWorkdir("/src").
WithExec([]string{"go", "build", "-o", "/output/hello"})
// select output directory
outputDir := ctr.Directory("/output")
// wrap the output directory in the new empty container marked
// with the same platform
binaryCtr := dag.Container(dagger.ContainerOpts{Platform: platform}).
WithRootfs(outputDir)
platformVariants = append(platformVariants, binaryCtr)
}
// publish to registry
imageDigest, err := dag.Container().
Publish(ctx, imageRepo, dagger.ContainerPublishOpts{
PlatformVariants: platformVariants,
})
if err != nil {
return "", err
}
// return build directory
return imageDigest, nil
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def build(
self,
src: Annotated[
dagger.Directory,
Doc(
"Source code location can be local directory or remote Git \
repository"
),
],
) -> str:
"""Build and publish multi-platform image"""
# platforms to build for and push in a multi-platform image
platforms = [
dagger.Platform("linux/amd64"), # a.k.a. x86_64
dagger.Platform("linux/arm64"), # a.k.a. aarch64
dagger.Platform("linux/s390x"), # a.k.a. IBM S/390
]
# container registry for multi-platform image
image_repo = "ttl.sh/myapp:latest"
platform_variants = []
for platform in platforms:
# pull golang image for this platform
ctr = (
dag.container(platform=platform)
.from_("golang:1.20-alpine")
# mount source
.with_directory("/src", src)
# mount empty dir where built binary will live
.with_directory("/output", dag.directory())
# ensure binary will be statically linked and thus executable
# in the final image
.with_env_variable("CGO_ENABLED", "0")
# build binary and put result at mounted output directory
.with_workdir("/src")
.with_exec(["go", "build", "-o", "/output/hello"])
)
# select output directory
output_dir = ctr.directory("/output")
# wrap output directory in a new empty container marked
# with the same platform
binary_ctr = dag.container(platform=platform).with_rootfs(output_dir)
platform_variants.append(binary_ctr)
# publish to registry
image_digest = dag.container().publish(
image_repo, platform_variants=platform_variants
)
return await image_digest
import {
dag,
Container,
Directory,
Platform,
object,
func,
} from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build and publish multi-platform image
* @param src source code location
*/
@func()
async build(src: Directory): Promise<string> {
// platforms to build for and push in a multi-platform image
const platforms: Platform[] = [
"linux/amd64" as Platform, // a.k.a. x86_64
"linux/arm64" as Platform, // a.k.a. aarch64
"linux/s390x" as Platform, // a.k.a. IBM S/390
]
// container registry for multi-platform image
const imageRepo = "ttl.sh/myapp:latest"
const platformVariants: Array<Container> = []
for (const platform of platforms) {
const ctr = dag
.container({ platform: platform })
.from("golang:1.21-alpine")
// mount source
.withDirectory("/src", src)
// mount empty dir where built binary will live
.withDirectory("/output", dag.directory())
// ensure binary will be statically linked and thus executable
// in the final image
.withEnvVariable("CGO_ENABLED", "0")
.withWorkdir("/src")
.withExec(["go", "build", "-o", "/output/hello"])
// select output directory
const outputDir = ctr.directory("/output")
// wrap output directory in a new empty container marked
// with the same platform
const binaryCtr = await dag
.container({ platform: platform })
.withRootfs(outputDir)
platformVariants.push(binaryCtr)
}
// publish to registry
const imageDigest = await dag
.container()
.publish(imageRepo, { platformVariants: platformVariants })
return imageDigest
}
}
Example​
Build and publish a multi-platform image:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'build https://github.com/golang/example#master:hello'
build https://github.com/golang/example#master:hello
dagger call build --src="https://github.com/golang/example#master:hello"
Build multi-arch image with cross-compliation​
The following Dagger Function builds a single image for different CPU architectures using cross-compilation.
This Dagger Function uses the containerd utility module. To run it locally
install the module first with dagger install github.com/levlaz/daggerverse/containerd@v0.1.2
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Build and publish multi-platform image
func (m *MyModule) Build(
ctx context.Context,
// Source code location
// can be local directory or remote Git repository
src *dagger.Directory,
) (string, error) {
// platforms to build for and push in a multi-platform image
var platforms = []dagger.Platform{
"linux/amd64", // a.k.a. x86_64
"linux/arm64", // a.k.a. aarch64
"linux/s390x", // a.k.a. IBM S/390
}
// container registry for the multi-platform image
const imageRepo = "ttl.sh/myapp:latest"
platformVariants := make([]*dagger.Container, 0, len(platforms))
for _, platform := range platforms {
// parse architecture using containerd utility module
platformArch, err := dag.Containerd().ArchitectureOf(ctx, platform)
if err != nil {
return "", err
}
// pull golang image for the *host* platform, this is done by
// not specifying the a platform. The default is the host platform.
ctr := dag.Container().
From("golang:1.21-alpine").
// mount source code
WithDirectory("/src", src).
// mount empty dir where built binary will live
WithDirectory("/output", dag.Directory()).
// ensure binary will be statically linked and thus executable
// in the final image
WithEnvVariable("CGO_ENABLED", "0").
// configure go compiler to use cross-compilation targeting the
// desired platform
WithEnvVariable("GOOS", "linux").
WithEnvVariable("GOARCH", platformArch).
// build binary and put result at mounted output directory
WithWorkdir("/src").
WithExec([]string{"go", "build", "-o", "/output/hello"})
// select output directory
outputDir := ctr.Directory("/output")
// wrap the output directory in the new empty container marked
// with the same platform
binaryCtr := dag.Container(dagger.ContainerOpts{Platform: platform}).
WithRootfs(outputDir).
WithEntrypoint([]string{"/hello"})
platformVariants = append(platformVariants, binaryCtr)
}
// publish to registry
imageDigest, err := dag.Container().
Publish(ctx, imageRepo, dagger.ContainerPublishOpts{
PlatformVariants: platformVariants,
})
if err != nil {
return "", err
}
// return build directory
return imageDigest, nil
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def build(
self,
src: Annotated[
dagger.Directory,
Doc(
"Source code location can be local directory or remote Git \
repository"
),
],
) -> str:
"""Build an publish multi-platform image"""
# platforms to build for and push in a multi-platform image
platforms = [
dagger.Platform("linux/amd64"), # a.k.a. x86_64
dagger.Platform("linux/arm64"), # a.k.a. aarch64
dagger.Platform("linux/s390x"), # a.k.a. IBM S/390
]
# container registry for multi-platform image
image_repo = "ttl.sh/myapp:latest"
platform_variants = []
for platform in platforms:
# parse architecture using containerd utility module
platform_arch = await dag.containerd().architecture_of(platform)
# pull golang image for the *host* platform, this is done by
# not specifying the a platform. The default is the host platform.
ctr = (
dag.container()
.from_("golang:1.21-alpine")
# mount source
.with_directory("/src", src)
# mount empty dir where built binary will live
.with_directory("/output", dag.directory())
# ensure binary will be statically linked and thus executable
# in the final image
.with_env_variable("CGO_ENABLED", "0")
# configure go compiler to use cross-compilation targeting the
# desired platform
.with_env_variable("GOOS", "linux")
.with_env_variable("GOARCH", platform_arch)
# build binary and put result at mounted output directory
.with_workdir("/src")
.with_exec(["go", "build", "-o", "/output/hello"])
)
# selelct output directory
output_dir = ctr.directory("/output")
# wrap output directory in a new empty container marked
# with the same platform
binary_ctr = (
dag.container(platform=platform)
.with_rootfs(output_dir)
.with_entrypoint(["/hello"])
)
platform_variants.append(binary_ctr)
# publish to registry
image_digest = dag.container().publish(
image_repo, platform_variants=platform_variants
)
return await image_digest
import {
dag,
Container,
Directory,
Platform,
object,
func,
} from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build and publish multi-platform image
* @param src source code location
*/
@func()
async build(src: Directory): Promise<string> {
// platforms to build for and push in a multi-platform image
const platforms: Platform[] = [
"linux/amd64" as Platform, // a.k.a. x86_64
"linux/arm64" as Platform, // a.k.a. aarch64
"linux/s390x" as Platform, // a.k.a. IBM S/390
]
// container registry for multi-platform image
const imageRepo = "ttl.sh/myapp:latest"
const platformVariants: Array<Container> = []
for (const platform of platforms) {
// parse architecture using containerd utility module
const platformArch = await dag.containerd().architectureOf(platform)
const ctr = dag
// pull golang image for the *host* platform, this is done by
// not specifying the a platform. The default is the host platform.
.container()
.from("golang:1.21-alpine")
// mount source
.withDirectory("/src", src)
// mount empty dir where built binary will live
.withDirectory("/output", dag.directory())
// ensure binary will be statically linked and thus executable
// in the final image
.withEnvVariable("CGO_ENABLED", "0")
// configure go compiler to use cross-compilation targeting the
// desired platform
.withEnvVariable("GOOS", "linux")
.withEnvVariable("GOARCH", platformArch)
.withWorkdir("/src")
.withExec(["go", "build", "-o", "/output/hello"])
// select output directory
const outputDir = ctr.directory("/output")
// wrap output directory in a new empty container marked
// with the same platform
const binaryCtr = await dag
.container({ platform: platform })
.withRootfs(outputDir)
.withEntrypoint(["/hello"])
platformVariants.push(binaryCtr)
}
// publish to registry
const imageDigest = await dag
.container()
.publish(imageRepo, { platformVariants: platformVariants })
return imageDigest
}
}
Example​
Build and publish a multi-platform image with cross compliation:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'build https://github.com/golang/example#master:hello'
build https://github.com/golang/example#master:hello
dagger call build --src="https://github.com/golang/example#master:hello"
Build image from Dockerfile​
The following Dagger Function builds an image from a Dockerfile.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Build and publish image from existing Dockerfile
func (m *MyModule) Build(
ctx context.Context,
// location of directory containing Dockerfile
src *dagger.Directory,
) (string, error) {
ref, err := src.
DockerBuild(). // build from Dockerfile
Publish(ctx, "ttl.sh/hello-dagger")
if err != nil {
return "", err
}
return ref, nil
}
from typing import Annotated
import dagger
from dagger import Doc, function, object_type
@object_type
class MyModule:
@function
async def build(
self,
src: Annotated[
dagger.Directory,
Doc("location of directory containing Dockerfile"),
],
) -> str:
"""Build and publish image from existing Dockerfile"""
ref = src.docker_build().publish("ttl.sh/hello-dagger") # build from Dockerfile
return await ref
import { dag, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build and publish image from existing Dockerfile
* @param src location of directory containing Dockerfile
*/
@func()
async build(src: Directory): Promise<string> {
const ref = await src
.dockerBuild() // build from Dockerfile
.publish("ttl.sh/hello-dagger")
return ref
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
// Build and publish image from existing Dockerfile
#[DaggerFunction]
public function build(
// location of directory containing Dockerfile
Directory $src,
): string {
$ref = $src
->dockerBuild() // build from Dockerfile
->publish('ttl.sh/hello-dagger');
return $ref;
}
}
Example​
Build and publish an image from an existing Dockerfile
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'build https://github.com/dockersamples/python-flask-redis'
build https://github.com/dockersamples/python-flask-redis
dagger call build --src="https://github.com/dockersamples/python-flask-redis"
Build image from Dockerfile using different build context​
The following function builds an image from a Dockerfile with a build context that is different than the current working directory.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Build and publish image from Dockerfile using a build context directory
// in a different location than the current working directory
func (m *MyModule) Build(
ctx context.Context,
// location of source directory
src *dagger.Directory,
// location of Dockerfile
dockerfile *dagger.File,
) (string, error) {
// get build context with dockerfile added
workspace := dag.Container().
WithDirectory("/src", src).
WithWorkdir("/src").
WithFile("/src/custom.Dockerfile", dockerfile).
Directory("/src")
// build using Dockerfile and publish to registry
ref, err := workspace.DockerBuild(dagger.DirectoryDockerBuildOpts{
Dockerfile: "custom.Dockerfile",
}).Publish(ctx, "ttl.sh/hello-dagger")
if err != nil {
return "", err
}
return ref, nil
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def build(
self,
src: Annotated[
dagger.Directory,
Doc("location of source directory"),
],
dockerfile: Annotated[
dagger.File,
Doc("location of Dockerfile"),
],
) -> str:
"""
Build and publish image from Dockerfile
This example uses a build context directory in a different location
than the current working directory.
"""
# get build context with dockerfile added
workspace = (
dag.container()
.with_directory("/src", src)
.with_workdir("/src")
.with_file("/src/custom.Dockerfile", dockerfile)
.directory("/src")
)
# build using Dockerfile and publish to registry
ref = workspace.docker_build(dockerfile="custom.Dockerfile").publish(
"ttl.sh/hello-dagger"
)
return await ref
import { dag, Directory, File, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build and publish image from existing Dockerfile. This example uses a
* build context directory in a different location than the current working
* directory.
* @param src location of source directory
* @param dockerfile location of dockerfile
*/
@func()
async build(src: Directory, dockerfile: File): Promise<string> {
// get build context with Dockerfile added
const workspace = await dag
.container()
.withDirectory("/src", src)
.withWorkdir("/src")
.withFile("/src/custom.Dockerfile", dockerfile)
.directory("/src")
// build using Dockerfile and publish to registry
const ref = await workspace
.dockerBuild({ dockerfile: "custom.Dockerfile" })
.publish("ttl.sh/hello-dagger")
return ref
}
}
Example​
Build an image from the source code in https://github.com/dockersamples/python-flask-redis using the Dockerfile from a different build context, at https://github.com/vimagick/dockerfiles#master:registry-cli/Dockerfile:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'build https://github.com/dockersamples/python-flask-redis https://github.com/vimagick/dockerfiles#master:registry-cli/Dockerfile'
build https://github.com/dockersamples/python-flask-redis https://github.com/vimagick/dockerfiles#master:registry-cli/Dockerfile
dagger call \
build --src="https://github.com/dockersamples/python-flask-redis" \
--dockerfile=https://github.com/vimagick/dockerfiles#master:registry-cli/Dockerfile
Cache application dependencies​
The following Dagger Function uses a cache volume for application dependencies. This enables Dagger to reuse the contents of the cache across Dagger Function runs and reduce execution time.
- Go
- Python
- TypeScript
- PHP
package main
import "dagger/my-module/internal/dagger"
type MyModule struct{}
// Build an application using cached dependencies
func (m *MyModule) Build(
// Source code location
source *dagger.Directory,
) *dagger.Container {
return dag.Container().
From("golang:1.21").
WithDirectory("/src", source).
WithWorkdir("/src").
WithMountedCache("/go/pkg/mod", dag.CacheVolume("go-mod-121")).
WithEnvVariable("GOMODCACHE", "/go/pkg/mod").
WithMountedCache("/go/build-cache", dag.CacheVolume("go-build-121")).
WithEnvVariable("GOCACHE", "/go/build-cache").
WithExec([]string{"go", "build"})
}
The default location for the cache directory depends on the package manager (~/.cache/pip for pip or ~/.cache/pypoetry for poetry).
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
def build(
self, source: Annotated[dagger.Directory, Doc("Source code location")]
) -> dagger.Container:
"""Build an application using cached dependencies"""
return (
dag.container()
.from_("python:3.11")
.with_directory("/src", source)
.with_workdir("/src")
# if using pip
.with_mounted_cache("/root/.cache/pip", dag.cache_volume("pip_cache"))
.with_exec(["pip", "install", "-r", "requirements.txt"])
# if using poetry
.with_mounted_cache(
"/root/.cache/pypoetry", dag.cache_volume("poetry_cache")
)
.with_exec(
[
"pip",
"install",
"--user",
"poetry==1.5.1",
"poetry-dynamic-versioning==0.23.0",
]
)
# No root first uses dependencies but not the project itself
.with_exec(["poetry", "install", "--no-root", "--no-interaction"])
.with_exec(["poetry", "install", "--no-interaction", "--only-root"])
)
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build an application using cached dependencies
*/
@func()
build(
/**
* Source code location
*/
source: Directory,
): Container {
return dag
.container()
.from("node:21")
.withDirectory("/src", source)
.withWorkdir("/src")
.withMountedCache("/root/.npm", dag.cacheVolume("node-21"))
.withExec(["npm", "install"])
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Container;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
// Build an application using cached dependencies
#[DaggerFunction]
public function build(
// source code location
Directory $source,
): Container {
return dag()
->container()
->from('php:8.3-cli')
->withDirectory('/src', $source)
->withWorkdir('/src')
->withMountedCache('/root/.composer', dag()->cacheVolume('composer'))
->withExec(['composer', 'install']);
}
}
Example​
Build an application using cached dependencies:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'build .'
build .
dagger call build --source=.
Execute functions concurrently​
The following Dagger Function demonstrates how to use native-language concurrency features (errgroups in Go, task groups in Python), and promises in TypeScript to execute other Dagger Functions concurrently. If any of the concurrently-running functions fails, the remaining ones will be immediately cancelled.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
"golang.org/x/sync/errgroup"
)
// Constructor
func New(
source *dagger.Directory,
) *MyModule {
return &MyModule{
Source: source,
}
}
type MyModule struct {
Source *dagger.Directory
}
// Return the result of running unit tests
func (m *MyModule) Test(ctx context.Context) (string, error) {
return m.BuildEnv().
WithExec([]string{"npm", "run", "test:unit", "run"}).
Stdout(ctx)
}
// Return the result of running the linter
func (m *MyModule) Lint(ctx context.Context) (string, error) {
return m.BuildEnv().
WithExec([]string{"npm", "run", "lint"}).
Stdout(ctx)
}
// Return the result of running the type-checker
func (m *MyModule) Typecheck(ctx context.Context) (string, error) {
return m.BuildEnv().
WithExec([]string{"npm", "run", "type-check"}).
Stdout(ctx)
}
// Run linter, type-checker, unit tests concurrently
func (m *MyModule) RunAllTests(ctx context.Context) error {
// Create error group
eg, gctx := errgroup.WithContext(ctx)
// Run linter
eg.Go(func() error {
_, err := m.Lint(gctx)
return err
})
// Run type-checker
eg.Go(func() error {
_, err := m.Typecheck(gctx)
return err
})
// Run unit tests
eg.Go(func() error {
_, err := m.Test(gctx)
return err
})
// Wait for all tests to complete
// If any test fails, the error will be returned
return eg.Wait()
}
// Build a ready-to-use development environment
func (m *MyModule) BuildEnv() *dagger.Container {
nodeCache := dag.CacheVolume("node")
return dag.Container().
From("node:21-slim").
WithDirectory("/src", m.Source).
WithMountedCache("/root/.npm", nodeCache).
WithWorkdir("/src").
WithExec([]string{"npm", "install"})
}
import anyio
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
source: dagger.Directory
@function
async def test(self) -> str:
"""Return the result of running unit tests"""
return await (
self.build_env().with_exec(["npm", "run", "test:unit", "run"]).stdout()
)
@function
async def typecheck(self) -> str:
"""Return the result of running the type checker"""
return await self.build_env().with_exec(["npm", "run", "type-check"]).stdout()
@function
async def lint(self) -> str:
"""Return the result of running the linter"""
return await self.build_env().with_exec(["npm", "run", "lint"]).stdout()
@function
async def run_all_tests(self):
"""Run linter, type-checker, unit tests concurrently"""
async with anyio.create_task_group() as tg:
tg.start_soon(self.lint)
tg.start_soon(self.typecheck)
tg.start_soon(self.test)
@function
def build_env(self) -> 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", self.source)
.with_mounted_cache("/root/.npm", node_cache)
.with_workdir("/src")
.with_exec(["npm", "install"])
)
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
source: Directory
constructor(source: Directory) {
this.source = source
}
/**
* Return the result of running unit tests
*/
@func()
async test(): Promise<string> {
return this.buildEnv().withExec(["npm", "run", "test:unit", "run"]).stdout()
}
/**
* Return the result of running the linter
*/
@func()
async lint(): Promise<string> {
return this.buildEnv().withExec(["npm", "run", "lint"]).stdout()
}
/**
* Return the result of running the type-checker
*/
@func()
async typecheck(): Promise<string> {
return this.buildEnv().withExec(["npm", "run", "type-check"]).stdout()
}
/**
* Run linter, type-checker, unit tests concurrently
*/
@func()
async runAllTests(): Promise<void> {
await Promise.all([this.test(), this.lint(), this.typecheck()])
}
/**
* Build a ready-to-use development environment
*/
@func()
buildEnv(): Container {
const nodeCache = dag.cacheVolume("node")
return dag
.container()
.from("node:21-slim")
.withDirectory("/src", this.source)
.withMountedCache("/root/.npm", nodeCache)
.withWorkdir("/src")
.withExec(["npm", "install"])
}
}
Example​
Execute a Dagger Function which performs different types of tests by executing other Dagger Functions concurrently.
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'my-module $(host | directory .) | run-all-tests'
my-module $(host | directory .) | run-all-tests
dagger call --source=. run-all-tests
Persist service state across runs​
The following Dagger Function uses a cache volume to persist a Redis service's data across Dagger Function runs.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Create Redis service and client
func (m *MyModule) Redis(ctx context.Context) *dagger.Container {
redisSrv := dag.Container().
From("redis").
WithExposedPort(6379).
WithMountedCache("/data", dag.CacheVolume("my-redis")).
WithWorkdir("/data").
AsService(dagger.ContainerAsServiceOpts{UseEntrypoint: true})
redisCLI := dag.Container().
From("redis").
WithServiceBinding("redis-srv", redisSrv).
WithEntrypoint([]string{"redis-cli", "-h", "redis-srv"})
return redisCLI
}
var execOpts = dagger.ContainerWithExecOpts{
UseEntrypoint: true,
}
// Set key and value in Redis service
func (m *MyModule) Set(
ctx context.Context,
// The cache key to set
key string,
// The cache value to set
value string,
) (string, error) {
return m.Redis(ctx).
WithExec([]string{"set", key, value}, execOpts).
WithExec([]string{"save"}, execOpts).
Stdout(ctx)
}
// Get value from Redis service
func (m *MyModule) Get(
ctx context.Context,
// The cache key to get
key string,
) (string, error) {
return m.Redis(ctx).
WithExec([]string{"get", key}, execOpts).
Stdout(ctx)
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
def redis(self) -> dagger.Container:
"""Create Redis service and client"""
redis_srv = (
dag.container()
.from_("redis")
.with_exposed_port(6379)
.with_mounted_cache("/data", dag.cache_volume("my-redis"))
.with_workdir("/data")
.as_service(use_entrypoint=True)
)
# create Redis client container
redis_cli = (
dag.container()
.from_("redis")
.with_service_binding("redis-srv", redis_srv)
.with_entrypoint(["redis-cli", "-h", "redis-srv"])
)
return redis_cli
@function
async def set(
self,
key: Annotated[str, Doc("The cache key to set")],
value: Annotated[str, Doc("The cache value to set")],
) -> str:
"""Set key and value in Redis service"""
return (
await self.redis()
.with_exec(["set", key, value], use_entrypoint=True)
.with_exec(["save"], use_entrypoint=True)
.stdout()
)
@function
async def get(
self,
key: Annotated[str, Doc("The cache key to get")],
) -> str:
"""Get value from Redis service"""
return await self.redis().with_exec(["get", key], use_entrypoint=True).stdout()
import { dag, object, func, Container } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Create Redis service and client
*/
@func()
redis(): Container {
const redisSrv = dag
.container()
.from("redis")
.withExposedPort(6379)
.withMountedCache("/data", dag.cacheVolume("my-redis"))
.withWorkdir("/data")
.asService({ useEntrypoint: true })
const redisCLI = dag
.container()
.from("redis")
.withServiceBinding("redis-srv", redisSrv)
.withEntrypoint(["redis-cli", "-h", "redis-srv"])
return redisCLI
}
/**
* Set key and value in Redis service
*/
@func()
async set(
/**
* The cache key to set
*/
key: string,
/**
* The cache value to set
*/
value: string,
): Promise<string> {
return await this.redis()
.withExec(["set", key, value], { useEntrypoint: true })
.withExec(["save"], { useEntrypoint: true })
.stdout()
}
/**
* Get value from Redis service
*/
@func()
async get(
/**
* The cache key to get
*/
key: string,
): Promise<string> {
return await this.redis()
.withExec(["get", key], { useEntrypoint: true })
.stdout()
}
}
Example​
-
Save data to a Redis service which uses a cache volume to persist a key named
foowith value `123:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'set foo 123'First type 'dagger' for interactive mode.set foo 123dagger call set --key=foo --value=123 -
Retrieve the value of the key
fooafter recreating the service state from the cache volume:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'get foo'First type 'dagger' for interactive mode.get foodagger call get --key=foo
Add OCI annotations to image​
The following Dagger Function adds OpenContainer Initiative (OCI) annotations to an image.
- Go
- Python
- TypeScript
package main
import (
"context"
"fmt"
"math"
"math/rand/v2"
)
type MyModule struct{}
// Build and publish image with OCI annotations
func (m *MyModule) Build(ctx context.Context) (string, error) {
address, err := dag.Container().
From("alpine:latest").
WithExec([]string{"apk", "add", "git"}).
WithWorkdir("/src").
WithExec([]string{"git", "clone", "https://github.com/dagger/dagger", "."}).
WithAnnotation("org.opencontainers.image.authors", "John Doe").
WithAnnotation("org.opencontainers.image.title", "Dagger source image viewer").
Publish(ctx, fmt.Sprintf("ttl.sh/custom-image-%.0f", math.Floor(rand.Float64()*10000000))) //#nosec
if err != nil {
return "", err
}
return address, nil
}
import random
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def build(self) -> str:
"""Build and publish image with OCI annotations"""
address = await (
dag.container()
.from_("alpine:latest")
.with_exec(["apk", "add", "git"])
.with_workdir("/src")
.with_exec(["git", "clone", "https://github.com/dagger/dagger", "."])
.with_annotation("org.opencontainers.image.authors", "John Doe")
.with_annotation(
"org.opencontainers.image.title", "Dagger source image viewer"
)
.publish(f"ttl.sh/custom-image-{random.randrange(10 * 7)}")
)
return address
import { dag, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build and publish image with OCI annotations
*/
@func()
async build(): Promise<string> {
const address = await dag
.container()
.from("alpine:latest")
.withExec(["apk", "add", "git"])
.withWorkdir("/src")
.withExec(["git", "clone", "https://github.com/dagger/dagger", "."])
.withAnnotation("org.opencontainers.image.authors", "John Doe")
.withAnnotation(
"org.opencontainers.image.title",
"Dagger source image viewer",
)
.publish(`ttl.sh/custom-image-${Math.floor(Math.random() * 10000000)}`)
return address
}
}
Example​
Build and publish an image with OCI annotations:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c build
build
dagger call build
Add OCI labels to image​
The following Dagger Function adds OpenContainer Initiative (OCI) labels to an image.
- Go
- Python
- TypeScript
package main
import (
"context"
"time"
)
type MyModule struct{}
// Build and publish image with OCI labels
func (m *MyModule) Build(
ctx context.Context,
) (string, error) {
ref, err := dag.Container().
From("alpine").
WithLabel("org.opencontainers.image.title", "my-alpine").
WithLabel("org.opencontainers.image.version", "1.0").
WithLabel("org.opencontainers.image.created", time.Now().String()).
WithLabel("org.opencontainers.image.source", "https://github.com/alpinelinux/docker-alpine").
WithLabel("org.opencontainers.image.licenses", "MIT").
Publish(ctx, "ttl.sh/my-alpine")
if err != nil {
return "", err
}
return ref, nil
}
from datetime import datetime, timezone
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def build(self) -> str:
"""Build and publish image with OCI labels"""
ref = (
dag.container()
.from_("alpine")
.with_label("org.opencontainers.image.title", "my-alpine")
.with_label("org.opencontainers.image.version", "1.0")
.with_label(
"org.opencontainers.image.created",
datetime.now(timezone.utc).isoformat(),
)
.with_label(
"org.opencontainers.image.source",
"https://github.com/alpinelinux/docker-alpine",
)
.with_label("org.opencontainers.image.licenses", "MIT")
.publish("ttl.sh/my-alpine")
)
return await ref
import { dag, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build and publish image with OCI labels
*/
@func()
async build(): Promise<string> {
const ref = await dag
.container()
.from("alpine")
.withLabel("org.opencontainers.image.title", "my-alpine")
.withLabel("org.opencontainers.image.version", "1.0")
.withLabel("org.opencontainers.image.created", new Date())
.withLabel(
"org.opencontainers.image.source",
"https://github.com/alpinelinux/docker-alpine",
)
.withLabel("org.opencontainers.image.licenses", "MIT")
.publish("ttl.sh/hello-dagger")
return ref
}
}
Example​
Build and publish an image with OCI labels:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c build
build
dagger call build
Invalidate cache​
The following function demonstrates how to invalidate the Dagger layer cache and force execution of subsequent workflow steps, by introducing a volatile time variable at a specific point in the Dagger workflow.
- This is a temporary workaround until cache invalidation support is officially added to Dagger.
- Changes in mounted cache volumes or secrets do not invalidate the Dagger layer cache.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"time"
)
type MyModule struct{}
// Run a build with cache invalidation
func (m *MyModule) Build(
ctx context.Context,
) (string, error) {
output, err := dag.Container().
From("alpine").
// comment out the line below to see the cached date output
WithEnvVariable("CACHEBUSTER", time.Now().String()).
WithExec([]string{"date"}).
Stdout(ctx)
if err != nil {
return "", err
}
return output, nil
}
from datetime import datetime
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def build(self) -> str:
"""Run a build with cache invalidation"""
output = (
dag.container()
.from_("alpine")
# comment out the line below to see the cached date output
.with_env_variable("CACHEBUSTER", str(datetime.now()))
.with_exec(["date"])
.stdout()
)
return await output
import { dag, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Run a build with cache invalidation
*/
@func()
async build(): Promise<string> {
const ref = await dag
.container()
.from("alpine")
// comment out the line below to see the cached date output
.withEnvVariable("CACHEBUSTER", Date.now().toString())
.withExec(["date"])
.stdout()
return ref
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
// Run a build with cache invalidation
#[DaggerFunction]
public function build(): string
{
return dag()
->container()
->from('alpine')
// comment out the line below to see the cached date output
->withEnvVariable('CACHEBUSTER', date(DATE_RFC2822))
->withExec(['date'])
->stdout();
}
}
Example​
Print the date and time, invalidating the cache on each run. However, if the CACHEBUSTER environment variable is removed, the same value (the date and time on the first run) is printed on every run.
- System shell
- Dagger Shell
- Dagger CLI
dagger -c build
build
dagger call build
Container Images​
Work with container images and registries. Dagger allows you to build, publish, and export container images, also known as just-in-time artifacts, as part of your Dagger Functions.
Publish a container image to a private registry​
The following Dagger Function publishes a just-in-time container image to a private registry.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Publish a container image to a private registry
func (m *MyModule) Publish(
ctx context.Context,
// Registry address
registry string,
// Registry username
username string,
// Registry password
password *dagger.Secret,
) (string, error) {
return dag.Container().
From("nginx:1.23-alpine").
WithNewFile(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
dagger.ContainerWithNewFileOpts{Permissions: 0o400},
).
WithRegistryAuth(registry, username, password).
Publish(ctx, fmt.Sprintf("%s/%s/my-nginx", registry, username))
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def publish(
self,
registry: Annotated[str, Doc("Registry address")],
username: Annotated[str, Doc("Registry username")],
password: Annotated[dagger.Secret, Doc("Registry password")],
) -> str:
"""Publish a container image to a private registry"""
return await (
dag.container()
.from_("nginx:1.23-alpine")
.with_new_file(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
permissions=0o400,
)
.with_registry_auth(registry, username, password)
.publish(f"{registry}/{username}/my-nginx")
)
import { dag, Secret, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Publish a container image to a private registry
*/
@func()
async publish(
/**
* Registry address
*/
registry: string,
/**
* Registry username
*/
username: string,
/**
* Registry password
*/
password: Secret,
): Promise<string> {
return await dag
.container()
.from("nginx:1.23-alpine")
.withNewFile("/usr/share/nginx/html/index.html", "Hello from Dagger!", {
permissions: 0o400,
})
.withRegistryAuth(registry, username, password)
.publish(`${registry}/${username}/my-nginx`)
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\File;
use Dagger\Secret;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Publish a container image to a private registry')]
public function publish(
#[Doc('registry address')]
string $registry,
#[Doc('registry username')]
string $username,
#[Doc('registry password')]
Secret $password,
): string {
return dag()
->container()
->from('nginx:1.23-alpine')
->withNewFile('/usr/share/nginx/html/index.html', 'Hello from Dagger!', 400)
->withRegistryAuth($registry, $username, $password)
->publish("$registry/$username/my-nginx");
}
}
Examples​
Publish a just-in-time container image to Docker Hub, using the account username user and the password set in the PASSWORD environment variable:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'publish docker.io user env://PASSWORD'
publish docker.io user env://PASSWORD
dagger call publish --registry=docker.io --username=user --password=env://PASSWORD
Publish a just-in-time container image to GitHub Container Registry, using the account username user and the GitHub personal access token set in the PASSWORD environment variable:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'publish ghcr.io user env://PASSWORD'
publish ghcr.io user env://PASSWORD
dagger call publish --registry=ghcr.io --username=user --password=env://PASSWORD
Publish a container image to a private registry with multiple tags​
The following Dagger Function tags a just-in-time container image multiple times and publishes it to a private registry.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Tag a container image multiple times and publish it to a private registry
func (m *MyModule) Publish(
ctx context.Context,
// Registry address
registry string,
// Registry username
username string,
// Registry password
password *dagger.Secret,
) ([]string, error) {
tags := [4]string{"latest", "1.0-alpine", "1.0", "1.0.0"}
addr := []string{}
ctr := dag.Container().
From("nginx:1.23-alpine").
WithNewFile(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
dagger.ContainerWithNewFileOpts{Permissions: 0o400},
).
WithRegistryAuth(registry, username, password)
for _, tag := range tags {
a, err := ctr.Publish(ctx, fmt.Sprintf("%s/%s/my-nginx:%s", registry, username, tag))
if err != nil {
return addr, err
}
addr = append(addr, a)
}
return addr, nil
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def publish(
self,
registry: Annotated[str, Doc("Registry address")],
username: Annotated[str, Doc("Registry username")],
password: Annotated[dagger.Secret, Doc("Registry password")],
) -> list[str]:
"""Tag a container image multiple times and publish it to a private registry"""
tags = ["latest", "1.0-alpine", "1.0", "1.0.0"]
addr = []
container = (
dag.container()
.from_("nginx:1.23-alpine")
.with_new_file(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
permissions=0o400,
)
.with_registry_auth(registry, username, password)
)
for tag in tags:
a = await container.publish(f"{registry}/{username}/my-nginx:{tag}")
addr.append(a)
return addr
import { dag, Secret, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Tag a container image multiple times and publish it to a private registry
*/
@func()
async publish(
/**
* Registry address
*/
registry: string,
/**
* Registry username
*/
username: string,
/**
* Registry password
*/
password: Secret,
): Promise<string[]> {
const tags = ["latest", "1.0-alpine", "1.0", "1.0.0"]
const addr: string[] = []
const container = dag
.container()
.from("nginx:1.23-alpine")
.withNewFile("/usr/share/nginx/html/index.html", "Hello from Dagger!", {
permissions: 0o400,
})
.withRegistryAuth(registry, username, password)
for (const tag in tags) {
const a = await container.publish(
`${registry}/${username}/my-nginx:${tags[tag]}`,
)
addr.push(a)
}
return addr
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\File;
use Dagger\Secret;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Tag a container image multiple times and publish it to a private registry')]
public function publish(
#[Doc('registry address')]
string $registry,
#[Doc('registry username')]
string $username,
#[Doc('registry password')]
Secret $password,
): string {
$tags = ['latest', '1.0-alpine', '1.0', '1.0.0'];
$address = [];
$container = dag()
->container()
->from('nginx:1.23-alpine')
->withNewFile('/usr/share/nginx/html/index.html', 'Hello from Dagger!', 400)
->withRegistryAuth($registry, $username, $password);
foreach ($tags as $tag) {
$a = $container->publish("$registry/$username/my-nginx:$tag");
$address[] = $a;
}
return implode(',', $address);
}
}
Examples​
Tag and publish a just-in-time container image to Docker Hub, using the account username user and the password set in the PASSWORD environment variable:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'publish docker.io user env://PASSWORD'
publish docker.io user env://PASSWORD
dagger call publish --registry=docker.io --username=user --password=env://PASSWORD
Tag and publish a just-in-time container image to GitHub Container Registry, using the account username user and the GitHub personal access token set in the PASSWORD environment variable:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'publish ghcr.io user env://PASSWORD'
publish ghcr.io user env://PASSWORD
dagger call publish --registry=ghcr.io --username=user --password=env://PASSWORD
Export a container image to the host​
The following Dagger Function returns a just-in-time container. This can be exported to the host as an OCI tarball.
- Go
- Python
- TypeScript
- PHP
package main
import "dagger/my-module/internal/dagger"
type MyModule struct{}
// Return a container
func (m *MyModule) Base() *dagger.Container {
return dag.Container().
From("alpine:latest").
WithExec([]string{"mkdir", "/src"}).
WithExec([]string{"touch", "/src/foo", "/src/bar"})
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def base(self) -> dagger.Container:
"""Return a container"""
return (
dag.container()
.from_("alpine:latest")
.with_exec(["mkdir", "/src"])
.with_exec(["touch", "/src/foo", "/src/bar"])
)
import { dag, Container, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container
*/
@func()
base(): Container {
return dag
.container()
.from("alpine:latest")
.withExec(["mkdir", "/src"])
.withExec(["touch", "/src/foo", "/src/bar"])
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container')]
public function base(): Container
{
return dag()
->container()
->from('alpine:latest')
->withExec(['mkdir', '/src'])
->withExec(['touch', '/src/foo', '/src/bar']);
}
}
Examples​
Load the container image returned by the Dagger Function into Docker:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'base | export-image myimage'
base | export-image myimage
dagger call base export-image --name myimage
Load the container image returned by the Dagger Function as a tarball to the host fileysystem:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'base | export /home/admin/mycontainer.tgz'
base | export /home/admin/mycontainer.tgz
dagger call base export --path=/home/admin/mycontainer.tgz
Set environment variables in a container​
The following Dagger Function demonstrates how to set a single environment variable in a container.
- Go
- Python
- TypeScript
- PHP
package main
import "context"
type MyModule struct{}
// Set a single environment variable in a container
func (m *MyModule) SetEnvVar(ctx context.Context) (string, error) {
return dag.Container().
From("alpine").
WithEnvVariable("ENV_VAR", "VALUE").
WithExec([]string{"env"}).
Stdout(ctx)
}
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def set_env_var(self) -> str:
"""Set a single environment variable in a container"""
return await (
dag.container()
.from_("alpine")
.with_env_variable("ENV_VAR", "VALUE")
.with_exec(["env"])
.stdout()
)
import { dag, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Set a single environment variable in a container
*/
@func()
async setEnvVar(): Promise<string> {
return await dag
.container()
.from("alpine")
.withEnvVariable("ENV_VAR", "VALUE")
.withExec(["env"])
.stdout()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
// Set a single environment variable in a container
#[DaggerFunction]
public function setEnvVar(): string
{
return dag()
->container()
->from('alpine')
->withEnvVariable('ENV_VAR', 'VALUE')
->withExec(['env'])
->stdout();
}
}
The following Dagger Function demonstrates how to set multiple environment variables in a container.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
type EnvVar struct {
Name string
Value string
}
// Set environment variables in a container
func (m *MyModule) SetEnvVars(ctx context.Context) (string, error) {
return dag.Container().
From("alpine").
With(EnvVariables([]*EnvVar{
{"ENV_VAR_1", "VALUE 1"},
{"ENV_VAR_2", "VALUE 2"},
{"ENV_VAR_3", "VALUE 3"},
})).
WithExec([]string{"env"}).
Stdout(ctx)
}
func EnvVariables(envs []*EnvVar) dagger.WithContainerFunc {
return func(c *dagger.Container) *dagger.Container {
for _, e := range envs {
c = c.WithEnvVariable(e.Name, e.Value)
}
return c
}
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def set_env_vars(self) -> str:
"""Set environment variables in a container"""
return await (
dag.container()
.from_("alpine")
.with_(
self.env_variables(
[
("ENV_VAR_1", "VALUE 1"),
("ENV_VAR_2", "VALUE 2"),
("ENV_VAR_3", "VALUE 3"),
]
)
)
.with_exec(["env"])
.stdout()
)
def env_variables(self, envs: list[tuple[str, str]]):
def env_variables_inner(ctr: dagger.Container):
for key, value in envs:
ctr = ctr.with_env_variable(key, value)
return ctr
return env_variables_inner
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Set environment variables in a container
*/
@func()
async setEnvVars(): Promise<string> {
return await dag
.container()
.from("alpine")
.with(
envVariables([
["ENV_VAR_1", "VALUE 1"],
["ENV_VAR_2", "VALUE 2"],
["ENV_VAR_3", "VALUE_3"],
]),
)
.withExec(["env"])
.stdout()
}
}
function envVariables(envs: Array<[string, string]>) {
return (c: Container): Container => {
for (const [key, value] of envs) {
c = c.withEnvVariable(key, value)
}
return c
}
}
Example​
Set a single environment variable in a container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c set-env-var
set-env-var
dagger call set-env-var
Set multiple environment variables in a container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c set-env-vars
set-env-vars
dagger call set-env-vars
Filesystems​
Manage files, directories, and artifacts across your workflows. This section contains practical examples for working with files and directories in Dagger.
Clone a remote Git repository into a container​
The following Dagger Function accepts a Git repository URL and a Git reference. It copies the repository at the specified reference to the /src path in a container and returns the modified container.
For examples of SSH-based cloning, including private or public Git repositories with an SSH reference format, select the SSH tabs below. This approach requires explicitly forwarding the host SSH_AUTH_SOCK to the Dagger Function. Learn more about this in Dagger's sandboxed runtime model.
- Go
- Go (SSH)
- Python
- Python (SSH)
- TypeScript
- TypeScript (SSH)
- PHP
- PHP (SSH)
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
// Demonstrates cloning a Git repository over HTTP(S).
//
// For SSH usage, see the SSH snippet (CloneWithSsh).
type MyModule struct{}
func (m *MyModule) Clone(ctx context.Context, repository string, ref string) *dagger.Container {
d := dag.Git(repository).Ref(ref).Tree()
return dag.Container().
From("alpine:latest").
WithDirectory("/src", d).
WithWorkdir("/src")
}
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
// Demonstrates an SSH-based clone requiring a user-supplied SSHAuthSocket.
type MyModule struct{}
func (m *MyModule) CloneWithSsh(ctx context.Context, repository string, ref string, sock *dagger.Socket) *dagger.Container {
d := dag.Git(repository, dagger.GitOpts{SSHAuthSocket: sock}).Ref(ref).Tree()
return dag.Container().
From("alpine:latest").
WithDirectory("/src", d).
WithWorkdir("/src")
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
"""
Demonstrates cloning a Git repository over HTTP(S).
For SSH usage, see the SSH snippet (clone_with_ssh).
"""
@function
async def clone(
self,
repository: str,
ref: str,
) -> dagger.Container:
repo_dir = dag.git(repository).ref(ref).tree()
return (
dag.container()
.from_("alpine:latest")
.with_directory("/src", repo_dir)
.with_workdir("/src")
)
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
"""Demonstrates an SSH-based clone requiring a user-supplied ssh_auth_socket."""
@function
async def clone_with_ssh(
self, repository: str, ref: str, sock: dagger.Socket
) -> dagger.Container:
repo_dir = dag.git(repository, ssh_auth_socket=sock).ref(ref).tree()
return (
dag.container()
.from_("alpine:latest")
.with_directory("/src", repo_dir)
.with_workdir("/src")
)
import { dag, Directory, Container, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
Demonstrates cloning a Git repository over HTTP(S).
For SSH usage, see the SSH snippet (cloneWithSsh).
*/
@func()
clone(repository: string, ref: string): Container {
const repoDir = dag.git(repository, { sshAuthSocket: sock }).ref(ref).tree()
return dag
.container()
.from("alpine:latest")
.withDirectory("/src", repoDir)
.withWorkdir("/src")
}
}
import { dag, Container, object, func, Socket } from "@dagger.io/dagger"
@object()
class MyModule {
/**
Demonstrates an SSH-based clone requiring a user-supplied sshAuthSocket.
*/
@func()
cloneWithSsh(repository: string, ref: string, sock: Socket): Container {
const repoDir = dag.git(repository, { sshAuthSocket: sock }).ref(ref).tree()
return dag
.container()
.from("alpine:latest")
.withDirectory("/src", repoDir)
.withWorkdir("/src")
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function clone(string $repository, string $ref): Container
{
$repoDir = dag()->git($repository)->ref($ref)->tree();
return dag()
->container()
->from('alpine:latest')
->withDirectory('/src', $repoDir);
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\Socket;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Demonstrates an SSH-based clone requiring a user-supplied sshAuthSocket.')]
public function cloneWithSsh(string $repository, string $ref, Socket $sock): Container
{
$repoDir = dag()
->git($repository, true, '', $sock)
->ref($ref)
->tree();
return dag()
->container()
->from('alpine:latest')
->withDirectory('/src', $repoDir)
->withWorkdir('/src');
}
}
Examples
Clone the public dagger/dagger GitHub repository to /src in the container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'clone https://github.com/dagger/dagger 196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5'
clone https://github.com/dagger/dagger 196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5
dagger call clone --repository=https://github.com/dagger/dagger --ref=196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5
Clone the public dagger/dagger GitHub repository at reference 196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5 to /src in the container and open an interactive terminal to inspect the container filesystem:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
clone https://github.com/dagger/dagger 196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5 |
terminal
EOF
clone https://github.com/dagger/dagger 196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5 | terminal
dagger call \
clone --repository=https://github.com/dagger/dagger --ref=196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5 \
terminal
Clone over SSH with socket forwarding:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
clone-with-ssh git@github.com:dagger/dagger.git 196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5 $SSH_AUTH_SOCK |
terminal
EOF
clone-with-ssh git@github.com:dagger/dagger.git 196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5 $SSH_AUTH_SOCK | terminal
dagger call \
clone-with-ssh --repository=git@github.com:dagger/dagger.git --ref=196f232a4d6b2d1d3db5f5e040cf20b6a76a76c5 --sock=$SSH_AUTH_SOCK \
terminal
Mount or copy a directory or remote repository to a container​
The following Dagger Function accepts a Directory argument, which could reference either a directory from the local filesystem or from a remote Git repository. It mounts the specified directory to the /src path in a container and returns the modified container.
When working with private Git repositories, ensure that SSH authentication is properly configured on your Dagger host.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Return a container with a mounted directory
func (m *MyModule) MountDirectory(
ctx context.Context,
// Source directory
source *dagger.Directory,
) *dagger.Container {
return dag.Container().
From("alpine:latest").
WithMountedDirectory("/src", source)
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
def mount_directory(
self, source: Annotated[dagger.Directory, Doc("Source directory")]
) -> dagger.Container:
"""Return a container with a mounted directory"""
return (
dag.container()
.from_("alpine:latest")
.with_mounted_directory("/src", source)
)
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container with a mounted directory
*/
@func()
mountDirectory(
/**
* Source directory
*/
source: Directory,
): Container {
return dag
.container()
.from("alpine:latest")
.withMountedDirectory("/src", source)
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container with a mounted directory')]
public function mountDirectory(Directory $source): Container
{
return dag()
->container()
->from('alpine:latest')
->withMountedDirectory('/src', $source);
}
}
An alternative option is to copy the target directory in the container. The difference between these two approaches is that mounts only take effect within your workflow invocation; they are not copied to, or included, in the final image. In addition, any changes to mounted files and/or directories will only be reflected in the target directory and not in the mount sources.
Besides helping with the final image size, mounts are more performant and resource-efficient. The rule of thumb should be to always use mounts where possible.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Return a container with a specified directory
func (m *MyModule) CopyDirectory(
ctx context.Context,
// Source directory
source *dagger.Directory,
) *dagger.Container {
return dag.Container().
From("alpine:latest").
WithDirectory("/src", source)
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
def copy_directory(
self, source: Annotated[dagger.Directory, Doc("Source directory")]
) -> dagger.Container:
"""Return a container with a specified directory"""
return dag.container().from_("alpine:latest").with_directory("/src", source)
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container with a specified directory
*/
@func()
copyDirectory(
/**
* Source directory
*/
source: Directory,
): Container {
return dag.container().from("alpine:latest").withDirectory("/src", source)
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container with a specified directory')]
public function copyDirectory(Directory $source): Container
{
return dag()
->container()
->from('alpine:latest')
->withDirectory('/src', $source);
}
}
Examples
-
Mount the
/myapphost directory to/srcin the container and return the modified container:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'mount-directory ./myapp/'First type 'dagger' for interactive mode.mount-directory ./myapp/dagger call mount-directory --source=./myapp/ -
Mount the public
dagger/daggerGitHub repository to/srcin the container and return the modified container:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'mount-directory https://github.com/dagger/dagger#main'First type 'dagger' for interactive mode.mount-directory https://github.com/dagger/dagger#maindagger call mount-directory --source=https://github.com/dagger/dagger#main -
Mount the private
user/fooGitHub repository to/srcin the container and return the modified container:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'mount-directory ssh://git@github.com/user/foo#main'First type 'dagger' for interactive mode.mount-directory ssh://git@github.com/user/foo#maindagger call mount-directory --source=ssh://git@github.com/user/foo#main -
Mount the public
dagger/daggerGitHub repository to/srcin the container and list the contents of the directory:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'mount-directory https://github.com/dagger/dagger#main | directory /src | entries'First type 'dagger' for interactive mode.mount-directory https://github.com/dagger/dagger#main | directory /src | entriesdagger call \
mount-directory --source=https://github.com/dagger/dagger#main \
directory --path=/src \
entries -
Copy the
/myapphost directory to/srcin the container and return the modified container:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-directory ./myapp/'First type 'dagger' for interactive mode.copy-directory ./myapp/dagger call copy-directory --source=./myapp/ -
Copy the public
dagger/daggerGitHub repository to/srcin the container and return the modified container:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-directory https://github.com/dagger/dagger#main'First type 'dagger' for interactive mode.copy-directory https://github.com/dagger/dagger#maindagger call copy-directory --source=https://github.com/dagger/dagger#main -
Copy the private
user/fooGitHub repository to/srcin the container and return the modified container:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-directory ssh://git@github.com/user/foo#main'First type 'dagger' for interactive mode.copy-directory ssh://git@github.com/user/foo#maindagger call copy-directory --source=ssh://git@github.com/user/foo#main -
Copy the public
dagger/daggerGitHub repository to/srcin the container and list the contents of the directory:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-directory https://github.com/dagger/dagger#main | directory /src | entries'First type 'dagger' for interactive mode.copy-directory https://github.com/dagger/dagger#main | directory /src | entriesdagger call \
copy-directory --source=https://github.com/dagger/dagger#main \
directory --path=/src \
entries
Modify a copied directory or remote repository in a container​
The following Dagger Function accepts a Directory argument, which could reference either a directory from the local filesystem or from a remote Git repository. It copies the specified directory to the /src path in a container, adds a file to it, and returns the modified container.
When a host directory or file is copied or mounted to a container's filesystem, modifications made to it in the container do not automatically transfer back to the host.
Data flows only one way between Dagger operations, because they are connected in a DAG. To transfer modifications back to the local host, you must explicitly export the directory or file back to the host filesystem.
When working with private Git repositories, ensure that SSH authentication is properly configured on your Dagger host.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Return a container with a specified directory and an additional file
func (m *MyModule) CopyAndModifyDirectory(
ctx context.Context,
// Source directory
source *dagger.Directory,
) *dagger.Container {
return dag.Container().
From("alpine:latest").
WithDirectory("/src", source).
WithExec([]string{"/bin/sh", "-c", `echo foo > /src/foo`})
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
def copy_and_modify_directory(
self, source: Annotated[dagger.Directory, Doc("Source directory")]
) -> dagger.Container:
"""Return a container with a specified directory and an additional file"""
return (
dag.container()
.from_("alpine:latest")
.with_directory("/src", source)
.with_exec(["/bin/sh", "-c", "`echo foo > /src/foo`"])
)
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container with a specified directory and an additional file
*/
@func()
copyAndModifyDirectory(
/**
* Source directory
*/
source: Directory,
): Container {
return dag
.container()
.from("alpine:latest")
.withDirectory("/src", source)
.withExec(["/bin/sh", "-c", "`echo foo > /src/foo`"])
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container with a specified directory and an additional file')]
public function copyAndModifyDirectory(Directory $source): Container
{
return dag()
->container()
->from('alpine:latest')
->withDirectory('/src', $source)
->withExec(['/bin/sh', '-c', `echo foo > /src/foo`]);
}
}
Examples
Copy the /myapp host directory to /src in the container, add a file to it, and return the modified container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-and-modify-directory ./myapp/'
copy-and-modify-directory ./myapp/
dagger call copy-and-modify-directory --source=./myapp/
Copy the public dagger/dagger GitHub repository to /src in the container, add a file to it, and return the modified container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-and-modify-directory github.com/dagger/dagger#main'
copy-and-modify-directory github.com/dagger/dagger#main
dagger call copy-and-modify-directory --source=github.com/dagger/dagger#main
Copy the private user/foo GitHub repository to /src in the container, add a file to it, and return the modified container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-and-modify-directory ssh://git@github.com/user/foo#main'
copy-and-modify-directory ssh://git@github.com/user/foo#main
dagger call copy-and-modify-directory --source=ssh://git@github.com/user/foo#main
Copy the public dagger/dagger GitHub repository to /src in the container, add a file to it, and list the contents of the directory:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-and-modify-directory https://github.com/dagger/dagger#main | directory /src | entries'
copy-and-modify-directory https://github.com/dagger/dagger#main | directory /src | entries
dagger call \
copy-and-modify-directory --source=https://github.com/dagger/dagger#main \
directory --path=/src \
entries
Copy a subset of a directory or remote repository to a container using filters specified at run-time​
The following Dagger Function accepts a Directory argument, which could reference either a directory from the local filesystem or a remote Git repository. It copies the specified directory to the /src path in a container, using a filter pattern specified at call-time to exclude specified sub-directories and files, and returns the modified container.
When working with private Git repositories, ensure that SSH authentication is properly configured on your Dagger host.
This is an example of post-call filtering with directory filters.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Return a container with a filtered directory
func (m *MyModule) CopyDirectoryWithExclusions(
ctx context.Context,
// Source directory
source *dagger.Directory,
// Exclusion pattern
// +optional
exclude []string,
) *dagger.Container {
return dag.Container().
From("alpine:latest").
WithDirectory("/src", source, dagger.ContainerWithDirectoryOpts{Exclude: exclude})
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
def copy_directory_with_exclusions(
self,
source: Annotated[dagger.Directory, Doc("Source directory")],
exclude: Annotated[list[str], Doc("Exclusion pattern")] | None,
) -> dagger.Container:
"""Return a container with a filtered directory"""
return (
dag.container()
.from_("alpine:latest")
.with_directory("/src", source, exclude=exclude)
)
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container with a filtered directory
*/
@func()
copyDirectoryWithExclusions(
/**
* Source directory
*/
source: Directory,
/**
* Exclusion pattern
*/
exclude?: string[],
): Container {
return dag
.container()
.from("alpine:latest")
.withDirectory("/src", source, { exclude: exclude })
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Attribute\ListOfType;
use Dagger\Container;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container with a filtered directory')]
public function copyDirectoryWithExclusions(
#[Doc('source directory')]
Directory $source,
#[Doc('exclusion pattern')]
#[ListOfType('string')]
?array $exclude = null
): Container {
return dag()
->container()
->from('alpine:latest')
->withDirectory('/src', $source, $exclude);
}
}
Examples
Copy the current host directory to /src in the container, excluding all sub-directories and files starting with dagger, and return the modified container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-directory-with-exclusions . --exclude=dagger*'
copy-directory-with-exclusions . --exclude=dagger*
dagger call copy-directory-with-exclusions --source=. --exclude=dagger*
Copy the public dagger/dagger GitHub repository to /src in the container, excluding all Markdown files, and list the contents of the directory:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
copy-directory-with-exclusions https://github.com/dagger/dagger#main --exclude=*.md |
directory /src |
entries
EOF
copy-directory-with-exclusions https://github.com/dagger/dagger#main --exclude=*.md | directory /src | entries
dagger call \
copy-directory-with-exclusions --source=https://github.com/dagger/dagger#main --exclude=*.md \
directory --path=/src \
entries
Copy the private user/foo GitHub repository to /src in the container, excluding all Markdown files, and list the contents of the directory:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
copy-directory-with-exclusions ssh://git@github.com/user/foo#main --exclude=*.md |
directory /src |
entries
EOF
copy-directory-with-exclusions ssh://git@github.com/user/foo#main --exclude=*.md | directory /src | entries
dagger call \
copy-directory-with-exclusions --source=ssh://git@github.com/user/foo#main --exclude=*.md \
directory --path=/src \
entries
Copy a subset of a directory or remote repository to a container using pre-defined filters​
The following Dagger Function accepts a Directory argument, which could reference either a directory from the local filesystem or a remote Git repository. It copies the specified directory to the /src path in a container, using pre-defined filter patterns to exclude specified sub-directories and files, and returns the modified container.
When working with private Git repositories, ensure that SSH authentication is properly configured on your Dagger host.
This is an example of pre-call filtering with directory filters.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Return a container with a filtered directory
func (m *MyModule) CopyDirectoryWithExclusions(
ctx context.Context,
// +ignore=["*", "!**/*.md"]
source *dagger.Directory,
) (*dagger.Container, error) {
return dag.
Container().
From("alpine:latest").
WithDirectory("/src", source).
Sync(ctx)
}
from typing import Annotated
import dagger
from dagger import Ignore, dag, function, object_type
@object_type
class MyModule:
@function
async def copy_directory_with_exclusions(
self,
source: Annotated[dagger.Directory, Ignore(["*", "!*.md"])],
) -> dagger.Container:
"""Return a container with a filtered directory"""
return await (
dag.container().from_("alpine:latest").with_directory("/src", source).sync()
)
import {
dag,
object,
argument,
func,
Directory,
Container,
} from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container with a filtered directory
*/
@func()
async copy_directory_with_exclusions(
@argument({ ignore: ["*", "!**/*.md"] }) source: Directory,
): Promise<Container> {
return await dag
.container()
.from("alpine:latest")
.withDirectory("/src", source)
.sync()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Attribute\Ignore;
use Dagger\Container;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container with a filtered directory')]
public function copyDirectoryWithExclusions(
#[Doc('source directory')]
#[Ignore('*', '!**/*.md')]
Directory $source,
): Container {
return dag()
->container()
->from('alpine:latest')
->withDirectory('/src', $source);
}
}
Examples
Copy the specified host directory to /src in the container, excluding everything except Markdown files, and return the modified container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-directory-with-exclusions ../docs'
copy-directory-with-exclusions ../docs
dagger call copy-directory-with-exclusions --source=../docs
Copy the public dagger/dagger GitHub repository to /src in the container, excluding everything except Markdown files, and list the contents of the /src directory in the container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-directory-with-exclusions https://github.com/dagger/dagger#main | directory /src | entries'
copy-directory-with-exclusions https://github.com/dagger/dagger#main | directory /src | entries
dagger call \
copy-directory-with-exclusions --source=https://github.com/dagger/dagger#main \
directory --path=/src \
entries
Mount or copy a local or remote file to a container​
The following Dagger Function accepts a File argument, which could reference either a file from the local filesystem or from a remote Git repository. It mounts the specified file to a container in the /src/ directory and returns the modified container.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Return a container with a mounted file
func (m *MyModule) MountFile(
ctx context.Context,
// Source file
f *dagger.File,
) *dagger.Container {
name, _ := f.Name(ctx)
return dag.Container().
From("alpine:latest").
WithMountedFile(fmt.Sprintf("/src/%s", name), f)
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def copy_file(
self,
f: Annotated[dagger.File, Doc("Source file")],
) -> dagger.Container:
"""Return a container with a mounted file"""
name = await f.name()
return (
dag.container().from_("alpine:latest").with_mounted_file(f"/src/{name}", f)
)
import { dag, Container, File, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container with a mounted file
*/
@func()
async copyFile(
/**
* Source file
*/
f: File,
): Promise<Container> {
const name = await f.name()
return dag
.container()
.from("alpine:latest")
.withMountedFile(`/src/${name}`, f)
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\File;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container with a mounted file')]
public function mountFile(
#[Doc('source file')]
File $f,
): Container {
$name = $f->name();
return dag()
->container()
->from('alpine:latest')
->withMountedFile("/src/$name", $f);
}
}
An alternative option is to copy the target file to the container. The difference between these two approaches is that mounts only take effect within your workflow invocation; they are not copied to, or included, in the final image. In addition, any changes to mounted files and/or directories will only be reflected in the target directory and not in the mount sources.
Besides helping with the final image size, mounts are more performant and resource-efficient. The rule of thumb should be to always use mounts where possible.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Return a container with a specified file
func (m *MyModule) CopyFile(
ctx context.Context,
// Source file
f *dagger.File,
) *dagger.Container {
name, _ := f.Name(ctx)
return dag.Container().
From("alpine:latest").
WithFile(fmt.Sprintf("/src/%s", name), f)
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def copy_file(
self,
f: Annotated[dagger.File, Doc("Source file")],
) -> dagger.Container:
"""Return a container with a specified file"""
name = await f.name()
return dag.container().from_("alpine:latest").with_file(f"/src/{name}", f)
import { dag, Container, File, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a container with a specified file
*/
@func()
async copyFile(
/**
* Source file
*/
f: File,
): Promise<Container> {
const name = await f.name()
return dag.container().from("alpine:latest").withFile(`/src/${name}`, f)
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\File;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a container with a specified file')]
public function copyFile(
#[Doc('source file')]
File $f,
): Container {
$name = $f->name();
return dag()
->container()
->from('alpine:latest')
->withFile("/src/$name", $f);
}
}
Examples
Mount the /home/admin/archives.zip file on the host to the /src directory in the container and return the modified container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'mount-file /home/admin/archives.zip'
mount-file /home/admin/archives.zip
dagger call mount-file --f=/home/admin/archives.zip
Mount the README.md file from the public dagger/dagger GitHub repository to the /src directory in the container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'mount-file https://github.com/dagger/dagger.git#main:README.md'
mount-file https://github.com/dagger/dagger.git#main:README.md
dagger call mount-file --f=https://github.com/dagger/dagger.git#main:README.md
Mount the README.md file from the public dagger/dagger GitHub repository to the /src directory in the container and display its contents:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
mount-file https://github.com/dagger/dagger.git#main:README.md |
file /src/README.md |
contents
EOF
mount-file https://github.com/dagger/dagger.git#main:README.md | file /src/README.md | contents
dagger call \
mount-file --f=https://github.com/dagger/dagger.git#main:README.md \
file --path=/src/README.md \
contents
Copy the /home/admin/archives.zip file on the host to the /src directory in the container and return the modified container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-file /home/admin/archives.zip'
copy-file /home/admin/archives.zip
dagger call copy-file --f=/home/admin/archives.zip
Copy the /home/admin/archives.zip file on the host to the /src directory in the container and list the contents of the directory:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-file /home/admin/archives.zip | directory /src | entries'
copy-file /home/admin/archives.zip | directory /src | entries
dagger call \
copy-file --f=/home/admin/archives.zip \
directory --path=/src \
entries
Copy the README.md file from the public dagger/dagger GitHub repository to the /src directory in the container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-file https://github.com/dagger/dagger.git#main:README.md'
copy-file https://github.com/dagger/dagger.git#main:README.md
dagger call copy-file --f=https://github.com/dagger/dagger.git#main:README.md
Copy the README.md file from the public dagger/dagger GitHub repository to the /src directory in the container and display its contents:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
copy-file https://github.com/dagger/dagger.git#main:README.md |
file /src/README.md |
contents
EOF
copy-file https://github.com/dagger/dagger.git#main:README.md | file /src/README.md | contents
dagger call \
copy-file --f=https://github.com/dagger/dagger.git#main:README.md \
file --path=/src/README.md \
contents
Copy a file to the Dagger module runtime container for custom processing​
The following Dagger Function accepts a File argument and copies the specified file to the Dagger module runtime container. This makes it possible to add one or more files to the runtime container and manipulate them using custom logic.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
"fmt"
"os"
)
type MyModule struct{}
// Copy a file to the Dagger module runtime container for custom processing
func (m *MyModule) CopyFile(ctx context.Context, source *dagger.File) {
source.Export(ctx, "foo.txt")
// your custom logic here
// for example, read and print the file in the Dagger Engine container
fmt.Println(os.ReadFile("foo.txt"))
}
import anyio
import dagger
from dagger import function, object_type
@object_type
class MyModule:
@function
async def copy_file(self, source: dagger.File):
"""Copy a file to the Dagger module runtime container for custom processing"""
await source.export("foo.txt")
# your custom logic here
# for example, read and print the file in the Dagger Engine container
print(await anyio.Path("foo.txt").read_text())
import { object, func, File } from "@dagger.io/dagger"
import * as fs from "fs"
@object()
class MyModule {
// Copy a file to the Dagger module runtime container for custom processing
@func()
async copyFile(source: File) {
await source.export("foo.txt")
// your custom logic here
// for example, read and print the file in the Dagger Engine container
console.log(fs.readFileSync("foo.txt", "utf8"))
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\File;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function copyFile(
#[Doc('source file')]
File $source
): void {
$source->export('foo.txt');
// your custom logic here
// for example, read and print the file in the Dagger Engine container
echo file_get_contents('foo.txt');
}
}
Examples
Copy the data.json host file to the runtime container and process it:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'copy-file ../data.json'
copy-file ../data.json
dagger call copy-file --source=../data.json
Export a directory or file to the host​
The following Dagger Functions return a just-in-time directory and file. These outputs can be exported to the host with dagger call ... export ....
When a host directory or file is copied or mounted to a container's filesystem, modifications made to it in the container do not automatically transfer back to the host. Data flows only one way between Dagger operations, because they are connected in a DAG. To transfer modifications back to the local host, you must explicitly export the directory or file back to the host filesystem.
- Go
- Python
- TypeScript
- PHP
package main
import "dagger/my-module/internal/dagger"
type MyModule struct{}
// Return a directory
func (m *MyModule) GetDir() *dagger.Directory {
return m.Base().
Directory("/src")
}
// Return a file
func (m *MyModule) GetFile() *dagger.File {
return m.Base().
File("/src/foo")
}
// Return a base container
func (m *MyModule) Base() *dagger.Container {
return dag.Container().
From("alpine:latest").
WithExec([]string{"mkdir", "/src"}).
WithExec([]string{"touch", "/src/foo", "/src/bar"})
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def get_dir(self) -> dagger.Directory:
"""Return a directory"""
return self.base().directory("/src")
@function
def get_file(self) -> dagger.File:
"""Return a file"""
return self.base().file("/src/foo")
@function
def base(self) -> dagger.Container:
"""Return a base container"""
return (
dag.container()
.from_("alpine:latest")
.with_exec(["mkdir", "/src"])
.with_exec(["touch", "/src/foo", "/src/bar"])
)
import {
dag,
Directory,
Container,
File,
object,
func,
} from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Return a directory
*/
@func()
getDir(): Directory {
return this.base().directory("/src")
}
/**
* Return a file
*/
@func()
getFile(): File {
return this.base().file("/src/foo")
}
/**
* Return a base container
*/
@func()
base(): Container {
return dag
.container()
.from("alpine:latest")
.withExec(["mkdir", "/src"])
.withExec(["touch", "/src/foo", "/src/bar"])
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use Dagger\File;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Return a directory')]
public function getDir(): Directory {
return $this->base()
->directory('/src');
}
#[DaggerFunction]
#[Doc('Return a file')]
public function getFile(): File {
return $this->base()
->file('/src/foo');
}
#[DaggerFunction]
#[Doc('Return a base container')]
public function base(): Container {
return dag()
->container()
->from('alpine:latest')
->withExec(["mkdir", "/src"])
->withExec(["touch", "/src/foo", "/src/bar"]);
}
}
Examples​
-
Export the directory returned by the Dagger Function to the
/home/admin/exportpath on the host:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'get-dir | export /home/admin/export'First type 'dagger' for interactive mode.get-dir | export /home/admin/exportdagger call get-dir export --path=/home/admin/export -
Export the file returned by the Dagger Function to the
/home/admin/myfilepath on the host:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'get-file | export /home/admin/myfile'First type 'dagger' for interactive mode.get-file | export /home/admin/myfiledagger call get-file export --path=/home/admin/myfile
Request a file over HTTP/HTTPS and save it in a container​
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) ReadFileHttp(
ctx context.Context,
url string,
) *dagger.Container {
file := dag.HTTP(url)
return dag.Container().
From("alpine:latest").
WithFile("/src/myfile", file)
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def read_file_http(self, url: str) -> dagger.Container:
file = dag.http(url)
return dag.container().from_("alpine:latest").with_file("/src/myfile", file)
import { dag, object, func, Container } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
readFileHttp(url: string): Container {
const file = dag.http(url)
return dag.container().from("alpine:latest").withFile("/src/myfile", file)
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Container;
use Dagger\File;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function readFileHttp(
string $url,
): Container {
$file = dag()->http($url);
return dag()
->container()
->from('alpine:latest')
->withFile('/src/myfile', $file);
}
}
Examples
Request the README.md file from the public dagger/dagger GitHub repository over HTTPS, save it as /src/myfile in the container, and return the container:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'read-file-http https://raw.githubusercontent.com/dagger/dagger/refs/heads/main/README.md'
read-file-http https://raw.githubusercontent.com/dagger/dagger/refs/heads/main/README.md
dagger call read-file-http --url=https://raw.githubusercontent.com/dagger/dagger/refs/heads/main/README.md
Request the README.md file from the public dagger/dagger GitHub repository over HTTPS, save it as /src/myfile in the container, and display its contents:
- System shell
- Dagger Shell
- Dagger CLI
dagger <<EOF
read-file-http https://raw.githubusercontent.com/dagger/dagger/refs/heads/main/README.md |
file /src/myfile |
contents
EOF
read-file-http https://raw.githubusercontent.com/dagger/dagger/refs/heads/main/README.md | file /src/myfile | contents
dagger call \
read-file-http --url=https://raw.githubusercontent.com/dagger/dagger/refs/heads/main/README.md \
file --path=/src/myfile \
contents
Set a module-wide default path​
The following Dagger module uses a constructor to set a module-wide Directory argument and point it to a default path on the host. This eliminates the need to pass a common Directory argument individually to each Dagger Function in the module. If required, the default path can be overridden by specifying a different Directory value at call time.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct {
Source *dagger.Directory
}
func New(
// +defaultPath="."
source *dagger.Directory,
) *MyModule {
return &MyModule{
Source: source,
}
}
func (m *MyModule) Foo(ctx context.Context) ([]string, error) {
return dag.Container().
From("alpine:latest").
WithMountedDirectory("/app", m.Source).
Directory("/app").
Entries(ctx)
}
from typing import Annotated
import dagger
from dagger import DefaultPath, dag, function, object_type
@object_type
class MyModule:
source: Annotated[dagger.Directory, DefaultPath(".")]
@function
async def foo(self) -> str:
return await (
dag.container()
.from_("alpine:latest")
.with_mounted_directory("/app", self.source)
.directory("/app")
.entries()
)
import { dag, Directory, object, func, argument } from "@dagger.io/dagger"
@object()
class MyModule {
source: Directory
constructor(
@argument({ defaultPath: "." })
source: Directory,
) {
this.source = source
}
@func()
async foo(): Promise<string[]> {
return await dag
.container()
.from("alpine:latest")
.withMountedDirectory("/app", this.source)
.directory("/app")
.entries()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\DefaultPath;
use Dagger\Attribute\ReturnsListOfType;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function __construct(
#[DefaultPath('.')]
public Directory $source
) {
}
#[DaggerFunction]
#[ReturnsListOfType('string')]
public function foo(): array
{
return dag()
->container()
->from('alpine:latest')
->withMountedDirectory('/app', $this->source)
->directory('/app')
->entries();
}
}
Examples
Call the Dagger Function without arguments. The default path (the current directory .) is used:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c foo
foo
dagger call foo
Call the Dagger Function with a constructor argument. The specified directory (/src/myapp) is used:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'my-module --source $(host | directory /src/myapp) | foo'
my-module --source $(host | directory /src/myapp) | foo
dagger call --source=/src/myapp foo
Services​
Integrate external services into your workflows (databases, caches, APIs). This section contains practical examples for working with services in Dagger.
Bind and use services in Dagger Functions​
The first Dagger Function below creates and returns an HTTP service. This service is bound and used from a different Dagger Function, via a service binding using an alias like www.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Start and return an HTTP service
func (m *MyModule) HttpService() *dagger.Service {
return dag.Container().
From("python").
WithWorkdir("/srv").
WithNewFile("index.html", "Hello, world!").
WithExposedPort(8080).
AsService(dagger.ContainerAsServiceOpts{Args: []string{"python", "-m", "http.server", "8080"}})
}
// Send a request to an HTTP service and return the response
func (m *MyModule) Get(ctx context.Context) (string, error) {
return dag.Container().
From("alpine").
WithServiceBinding("www", m.HttpService()).
WithExec([]string{"wget", "-O-", "http://www:8080"}).
Stdout(ctx)
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def http_service(self) -> dagger.Service:
"""Start and return an HTTP service."""
return (
dag.container()
.from_("python")
.with_workdir("/srv")
.with_new_file("index.html", "Hello, world!")
.with_exposed_port(8080)
.as_service(args=["python", "-m", "http.server", "8080"])
)
@function
async def get(self) -> str:
"""Send a request to an HTTP service and return the response."""
return await (
dag.container()
.from_("alpine")
.with_service_binding("www", self.http_service())
.with_exec(["wget", "-O-", "http://www:8080"])
.stdout()
)
import { dag, object, func, Service } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Start and return an HTTP service
*/
@func()
httpService(): Service {
return dag
.container()
.from("python")
.withWorkdir("/srv")
.withNewFile("index.html", "Hello, world!")
.withExposedPort(8080)
.asService({ args: ["python", "-m", "http.server", "8080"] })
}
/**
* Send a request to an HTTP service and return the response
*/
@func()
async get(): Promise<string> {
return await dag
.container()
.from("alpine")
.withServiceBinding("www", this.httpService())
.withExec(["wget", "-O-", "http://www:8080"])
.stdout()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, Doc};
use Dagger\Service;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Start and return an HTTP service')]
public function httpService(): Service
{
return dag()
->container()
->from('python')
->withWorkdir('/srv')
->withNewFile('index.html', 'Hello, world!')
->withExposedPort(8080)
->asService(args: ['python', '-m', 'http.server', '8080']);
}
#[DaggerFunction]
#[Doc('Send a request to an HTTP service and return the response')]
public function get(): string
{
return dag()
->container()
->from('alpine')
->withServiceBinding('www', $this->httpService())
->withExec(['wget', '-O-', 'http://www:8080'])
->stdout();
}
}
Example​
Send a request from one Dagger Function to a bound HTTP service instantiated by a different Dagger Function:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c get
get
dagger call get
Expose services in Dagger Functions to the host​
The Dagger Function below creates and returns an HTTP service. This service can be used from the host.
- Go
- Python
- TypeScript
- PHP
package main
import (
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Start and return an HTTP service
func (m *MyModule) HttpService() *dagger.Service {
return dag.Container().
From("python").
WithWorkdir("/srv").
WithNewFile("index.html", "Hello, world!").
WithExposedPort(8080).
AsService(dagger.ContainerAsServiceOpts{Args: []string{"python", "-m", "http.server", "8080"}})
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def http_service(self) -> dagger.Service:
"""Start and return an HTTP service."""
return (
dag.container()
.from_("python")
.with_workdir("/srv")
.with_new_file("index.html", "Hello, world!")
.with_exposed_port(8080)
.as_service(args=["python", "-m", "http.server", "8080"])
)
import { dag, object, func, Service } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Start and return an HTTP service
*/
@func()
httpService(): Service {
return dag
.container()
.from("python")
.withWorkdir("/srv")
.withNewFile("index.html", "Hello, world!")
.withExposedPort(8080)
.asService({ args: ["python", "-m", "http.server", "8080"] })
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, Doc};
use Dagger\Service;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Start and return an HTTP service')]
public function httpService(): Service
{
return dag()
->container()
->from('python')
->withWorkdir('/srv')
->withNewFile('index.html', 'Hello, world!')
->withExposedPort(8080)
->asService(args: ['python', '-m', 'http.server', '8080']);
}
}
Examples​
- Expose the HTTP service instantiated by a Dagger Function to the host on the default port:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'http-service | up'
http-service | up
dagger call http-service up
Access the service from the host:
curl localhost:8080
- Expose the HTTP service instantiated by a Dagger Function to the host on a different host port:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'http-service | up --ports 9000:8080'
http-service | up --ports 9000:8080
dagger call http-service up --ports 9000:8080
Access the service from the host:
curl localhost:9000
Expose host services to Dagger Functions​
The following Dagger Function accepts a Service running on the host, binds it using an alias, and creates a client to access it via the service binding. This example uses a MariaDB database service running on host port 3306, aliased as db in the Dagger Function.
This implies that a service is already listening on a port on the host, out-of-band of Dagger.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Send a query to a MariaDB service and return the response
func (m *MyModule) UserList(
ctx context.Context,
// Host service
svc *dagger.Service,
) (string, error) {
return dag.Container().
From("mariadb:10.11.2").
WithServiceBinding("db", svc).
WithExec([]string{"/usr/bin/mysql", "--user=root", "--password=secret", "--host=db", "-e", "SELECT Host, User FROM mysql.user"}).
Stdout(ctx)
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def user_list(
self, svc: Annotated[dagger.Service, Doc("Host service")]
) -> str:
"""Send a query to a MariaDB service and return the response."""
return await (
dag.container()
.from_("mariadb:10.11.2")
.with_service_binding("db", svc)
.with_exec(
[
"/usr/bin/mysql",
"--user=root",
"--password=secret",
"--host=db",
"-e",
"SELECT Host, User FROM mysql.user",
]
)
.stdout()
)
import { dag, object, func, Service } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Send a query to a MariaDB service and return the response
*/
@func()
async userList(
/**
* Host service
*/
svc: Service,
): Promise<string> {
return await dag
.container()
.from("mariadb:10.11.2")
.withServiceBinding("db", svc)
.withExec([
"/usr/bin/mysql",
"--user=root",
"--password=secret",
"--host=db",
"-e",
"SELECT Host, User FROM mysql.user",
])
.stdout()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, Doc};
use Dagger\Service;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Send a query to a MariaDB service and return the response')]
public function userList(
#[Doc('host service')]
Service $svc,
): string {
return dag()
->container()
->from('mariadb:10.11.2')
->withServiceBinding('db', $svc)
->withExec([
'/usr/bin/mysql',
'--user=root',
'--password=secret',
'--host=db',
'-e',
'SELECT Host, User FROM mysql.user',
])
->stdout();
}
}
Example​
Send a query to the database service listening on host port 3306 and return the result as a string:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'user-list tcp://localhost:3306'
user-list tcp://localhost:3306
dagger call user-list --svc=tcp://localhost:3306
Use service endpoints​
The following Dagger Function starts a service manually, then retrieves its endpoint and sends a request. This example uses a NGINX HTTP service running on host port 80.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) Get(ctx context.Context) (string, error) {
// Start NGINX service
service := dag.Container().From("nginx").WithExposedPort(80).AsService()
service, err := service.Start(ctx)
if err != nil {
return "", err
}
// Wait for service endpoint
endpoint, err := service.Endpoint(ctx, dagger.ServiceEndpointOpts{Scheme: "http", Port: 80})
if err != nil {
return "", err
}
// Send HTTP request to service endpoint
return dag.HTTP(endpoint).Contents(ctx)
}
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def get(self) -> str:
# start NGINX service
service = dag.container().from_("nginx").with_exposed_port(80).as_service()
await service.start()
# wait for service endpoint
endpoint = await service.endpoint(port=80, scheme="http")
# s end HTTP request to service endpoint
return await dag.http(endpoint).contents()
import { dag, object, func } from "@dagger.io/dagger"
@object()
export class MyModule {
@func()
async get(): Promise<string> {
// start NGINX service
let service = dag.container().from("nginx").withExposedPort(80).asService()
service = await service.start()
// wait for service to be ready
const endpoint = await service.endpoint({ port: 80, scheme: "http" })
// send HTTP request to service endpoint
return await dag.http(endpoint).contents()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, Doc};
use Dagger\Service;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function get(): string
{
// start NGINX service
$service = dag()->container()->from('nginx')->withExposedPort(80)->asService();
$service->start();
// wait for service to be ready
$endpoint = $service->endpoint(80, 'http');
// send HTTP request to service endpoint
return dag()->http($endpoint)->contents();
}
}
Example​
Send a query to the HTTP service listening on host port 80 and return the result as a string:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c get
get
dagger call get
Create a transient service for unit tests​
The following Dagger Function creates a service and binds it to an application container for unit testing. In this example, the application being tested is Drupal. Drupal includes a large number of unit tests, including tests which depend on an active database service. This database service is created on-the-fly by the Dagger Function.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Run unit tests against a database service
func (m *MyModule) Test(ctx context.Context) (string, error) {
mariadb := dag.Container().
From("mariadb:10.11.2").
WithEnvVariable("MARIADB_USER", "user").
WithEnvVariable("MARIADB_PASSWORD", "password").
WithEnvVariable("MARIADB_DATABASE", "drupal").
WithEnvVariable("MARIADB_ROOT_PASSWORD", "root").
WithExposedPort(3306).
AsService(dagger.ContainerAsServiceOpts{UseEntrypoint: true})
// get Drupal base image
// install additional dependencies
drupal := dag.Container().
From("drupal:10.0.7-php8.2-fpm").
WithExec([]string{"composer", "require", "drupal/core-dev", "--dev", "--update-with-all-dependencies"})
// add service binding for MariaDB
// run kernel tests using PHPUnit
return drupal.
WithServiceBinding("db", mariadb).
WithEnvVariable("SIMPLETEST_DB", "mysql://user:password@db/drupal").
WithEnvVariable("SYMFONY_DEPRECATIONS_HELPER", "disabled").
WithWorkdir("/opt/drupal/web/core").
WithExec([]string{"../../vendor/bin/phpunit", "-v", "--group", "KernelTests"}).
Stdout(ctx)
}
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def test(self) -> str:
"""Run unit tests against a database service."""
# get MariaDB base image
mariadb = (
dag.container()
.from_("mariadb:10.11.2")
.with_env_variable("MARIADB_USER", "user")
.with_env_variable("MARIADB_PASSWORD", "password")
.with_env_variable("MARIADB_DATABASE", "drupal")
.with_env_variable("MARIADB_ROOT_PASSWORD", "root")
.with_exposed_port(3306)
.as_service(use_entrypoint=True)
)
# get Drupal base image
# install additional dependencies
drupal = (
dag.container()
.from_("drupal:10.0.7-php8.2-fpm")
.with_exec(
[
"composer",
"require",
"drupal/core-dev",
"--dev",
"--update-with-all-dependencies",
]
)
)
# add service binding for MariaDB
# run kernel tests using PHPUnit
return await (
drupal.with_service_binding("db", mariadb)
.with_env_variable("SIMPLETEST_DB", "mysql://user:password@db/drupal")
.with_env_variable("SYMFONY_DEPRECATIONS_HELPER", "disabled")
.with_workdir("/opt/drupal/web/core")
.with_exec(["../../vendor/bin/phpunit", "-v", "--group", "KernelTests"])
.stdout()
)
import { dag, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Run unit tests against a database service
*/
@func()
async test(): Promise<string> {
const mariadb = dag
.container()
.from("mariadb:10.11.2")
.withEnvVariable("MARIADB_USER", "user")
.withEnvVariable("MARIADB_PASSWORD", "password")
.withEnvVariable("MARIADB_DATABASE", "drupal")
.withEnvVariable("MARIADB_ROOT_PASSWORD", "root")
.withExposedPort(3306)
.asService({ useEntrypoint: true })
// get Drupal base image
// install additional dependencies
const drupal = dag
.container()
.from("drupal:10.0.7-php8.2-fpm")
.withExec([
"composer",
"require",
"drupal/core-dev",
"--dev",
"--update-with-all-dependencies",
])
// add service binding for MariaDB
// run kernel tests using PHPUnit
return await drupal
.withServiceBinding("db", mariadb)
.withEnvVariable("SIMPLETEST_DB", "mysql://user:password@db/drupal")
.withEnvVariable("SYMFONY_DEPRECATIONS_HELPER", "disabled")
.withWorkdir("/opt/drupal/web/core")
.withExec(["../../vendor/bin/phpunit", "-v", "--group", "KernelTests"])
.stdout()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, Doc};
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Run unit tests against a database service')]
public function test(): string
{
$mariadb = dag()
->container()
->from('mariadb:10.11.2')
->withEnvVariable('MARIADB_USER', 'user')
->withEnvVariable('MARIADB_PASSWORD', 'password')
->withEnvVariable('MARIADB_DATABASE', 'drupal')
->withEnvVariable('MARIADB_ROOT_PASSWORD', 'root')
->withExposedPort(3306)
->asService(useEntrypoint: true);
// get Drupal base image
// install additional dependencies
$drupal = dag()
->from('drupal:10.0.7-php8.2-fpm')
->withExec([
'composer',
'require',
'drupal/core-dev',
'--dev',
'--update-with-all-dependencies',
]);
// add service binding for MariaDB
// run kernel tests using PHPUnit
return $drupal
->withServiceBinding('db', mariadb)
->withEnvVariable('SIMPLETEST_DB', 'mysql://user:password@db/drupal')
->withEnvVariable('SYMFONY_DEPRECATIONS_HELPER', 'disabled')
->withWorkdir('/opt/drupal/web/core')
->withExec(['../../vendor/bin/phpunit', '-v', '--group', 'KernelTests'])
->stdout();
}
}
Example​
Run Drupal's unit tests, instantiating a database service during the process:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c test
test
dagger call test
Start and stop services​
The following Dagger Function demonstrates how to control a service's lifecycle by explicitly starting and stopping a service. This example uses a Redis service.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Explicitly start and stop a Redis service
func (m *MyModule) RedisService(ctx context.Context) (string, error) {
redisSrv := dag.Container().
From("redis").
WithExposedPort(6379).
AsService(dagger.ContainerAsServiceOpts{UseEntrypoint: true})
// start Redis ahead of time so it stays up for the duration of the test
redisSrv, err := redisSrv.Start(ctx)
if err != nil {
return "", err
}
// stop the service when done
defer redisSrv.Stop(ctx)
// create Redis client container
redisCLI := dag.Container().
From("redis").
WithServiceBinding("redis-srv", redisSrv)
args := []string{"redis-cli", "-h", "redis-srv"}
// set value
setter, err := redisCLI.
WithExec(append(args, "set", "foo", "abc")).
Stdout(ctx)
if err != nil {
return "", err
}
// get value
getter, err := redisCLI.
WithExec(append(args, "get", "foo")).
Stdout(ctx)
if err != nil {
return "", err
}
return setter + getter, nil
}
import contextlib
import dagger
from dagger import dag, function, object_type
@contextlib.asynccontextmanager
async def managed_service(svc: dagger.Service):
"""Start and stop a service."""
yield await svc.start()
await svc.stop()
@object_type
class MyModule:
@function
async def redis_service(self) -> str:
"""Explicitly start and stop a Redis service."""
redis_srv = dag.container().from_("redis").with_exposed_port(6379).as_service()
# start Redis ahead of time so it stays up for the duration of the test
# and stop when done
async with managed_service(redis_srv) as redis_srv:
# create Redis client container
redis_cli = (
dag.container()
.from_("redis")
.with_service_binding("redis-srv", redis_srv)
)
args = ["redis-cli", "-h", "redis-srv"]
# set value
setter = await redis_cli.with_exec([*args, "set", "foo", "abc"]).stdout()
# get value
getter = await redis_cli.with_exec([*args, "get", "foo"]).stdout()
return setter + getter
import { dag, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Explicitly start and stop a Redis service
*/
@func()
async redisService(): Promise<string> {
let redisSrv = dag
.container()
.from("redis")
.withExposedPort(6379)
.asService()
// start Redis ahead of time so it stays up for the duration of the test
redisSrv = await redisSrv.start()
// stop the service when done
await redisSrv.stop()
// create Redis client container
const redisCLI = dag
.container()
.from("redis")
.withServiceBinding("redis-srv", redisSrv)
const args = ["redis-cli", "-h", "redis-srv"]
// set value
const setter = await redisCLI
.withExec([...args, "set", "foo", "abc"])
.stdout()
// get value
const getter = await redisCLI.withExec([...args, "get", "foo"]).stdout()
return setter + getter
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, Doc};
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Explicitly start and stop a Redis service')]
public function redisService(): string
{
$redisSrv = dag()
->container()
->from('redis')
->withExposedPort(6379)
->asService();
// Start Redis ahead of time so it stays up for the duration of the test
$redisSrv->start();
// stop the service when done
$redisSrv->stop();
$redisCLI = dag()
->container()
->from('redis')
->withServiceBinding('redis-srv', $redisSrv);
$args = ['redis-cli', '-h', 'redis-srv'];
$setter = $redisCLI
->withExec([...$args, 'set', 'foo', 'abc'])
->stdout();
$getter = $redisCLI
->withExec([...$args, 'get', 'foo'])
->stdout();
return $setter . $getter;
}
}
Example​
Start and stop a Redis service:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c redis-service
redis-service
dagger call redis-service
Create interdependent services​
The following Dagger Function runs two services, service A and service B, that depend on each other. The services are set up with custom hostnames, svca and svcb, allowing each service to communicate with the other by hostname.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Run two services which are dependent on each other
func (m *MyModule) Services(ctx context.Context) (*dagger.Service, error) {
svcA := dag.Container().From("nginx").
WithExposedPort(80).
AsService(dagger.ContainerAsServiceOpts{Args: []string{"sh", "-c", `nginx & while true; do curl svcb:80 && sleep 1; done`}}).
WithHostname("svca")
_, err := svcA.Start(ctx)
if err != nil {
return nil, err
}
svcB := dag.Container().From("nginx").
WithExposedPort(80).
AsService(dagger.ContainerAsServiceOpts{Args: []string{"sh", "-c", `nginx & while true; do curl svca:80 && sleep 1; done`}}).
WithHostname("svcb")
svcB, err = svcB.Start(ctx)
if err != nil {
return nil, err
}
return svcB, nil
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def services(self) -> dagger.Service:
"""Run two services which are dependent on each other"""
svc_a = (
dag.container()
.from_("nginx")
.with_exposed_port(80)
.as_service(
args=[
"sh",
"-c",
"nginx & while true; do curl svcb:80 && sleep 1; done",
]
)
.with_hostname("svca")
)
await svc_a.start()
svc_b = (
dag.container()
.from_("nginx")
.with_exposed_port(80)
.as_service(
args=[
"sh",
"-c",
"nginx & while true; do curl svca:80 && sleep 1; done",
]
)
.with_hostname("svcb")
)
await svc_b.start()
return svc_b
import { dag, object, func, Service } from "@dagger.io/dagger"
@object()
class MyModule {
// Run two services which are dependent on each other
@func()
async services(): Promise<Service> {
const svcA = dag
.container()
.from("nginx")
.withExposedPort(80)
.asService({
args: [
"sh",
"-c",
`nginx & while true; do curl svcb:80 && sleep 1; done`,
],
})
.withHostname("svca")
await svcA.start()
const svcB = dag
.container()
.from("nginx")
.withExposedPort(80)
.asService({
args: [
"sh",
"-c",
`nginx & while true; do curl svca:80 && sleep 1; done`,
],
})
.withHostname("svcb")
await svcB.start()
return svcB
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\{DaggerObject, DaggerFunction, Doc};
use Dagger\Service;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
#[Doc('Run two services which are dependent on each other')]
public function services(): Service
{
$svcA = dag()
->container()
->from('nginx')
->withExposedPort(80)
->asService(args: [
'sh',
'-c',
'nginx & while true; do curl svcb:80 && sleep 1; done'
])
->withHostname('svca');
$svcA->start();
$svcB = dag()
->container()
->from('nginx')
->withExposedPort(80)
->asService(args: [
'sh',
'-c',
'nginx & while true; do curl svca:80 && sleep 1; done'
])
->withHostname('svcb');
$svcB->start();
return $svcB;
}
}
Example​
Start two inter-dependent services:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'services | up --ports 8080:80'
services | up --ports 8080:80
dagger call services up --ports 8080:80
Secrets​
Securely handle sensitive data in your workflows. This section contains practical examples for working with secrets in Dagger.
Use secret variables​
The following Dagger Function accepts a GitHub personal access token as a Secret, and uses the Secret to authorize a request to the GitHub API. The secret may be sourced from the host (via an environment variable, host file, or host command) or from an external secrets manager (1Password or Vault):
- Go
- Python
- TypeScript
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()
}
}
Using Environment Variables​
You can use a secret sourced from an environment variable by running the following command:
- 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
Passing Files​
You can also pass files to secrets by following the example below:
- 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
Secrets from command output​
Secrets also support capturing data from running commands.
- 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"
1Password​
Use a secret from 1Password:
If using a 1Password service account, ensure that the OP_SERVICE_ACCOUNT_TOKEN environment variable is set.
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
Hashicorp Vault​
You can retrieve secrets from Hashicorp Vault.
Ensure that the VAULT_ADDR and either the VAULT_TOKEN or VAULT_APPROLE_ROLE_ID (for Vault AppRole authentication) environment variables are set.
export VAULT_ADDR="https://127.0.0.1:8200"
export VAULT_TOKEN="gue55me7"
export VAULT_APPROLE_ROLE_ID="roleid-xxx-yyy-zzz"
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github-api vault://credentials.github'
github-api vault://credentials.github
dagger call github-api --token=vault://credentials.github
Mount files as secrets​
The following Dagger Function accepts a GitHub hosts configuration file as a Secret, and mounts the file as a Secret to a container to authorize a request to GitHub.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Query the GitHub API
func (m *MyModule) GithubAuth(
ctx context.Context,
// GitHub Hosts configuration file
ghCreds *dagger.Secret,
) (string, error) {
return dag.Container().
From("alpine:3.17").
WithExec([]string{"apk", "add", "github-cli"}).
WithMountedSecret("/root/.config/gh/hosts.yml", ghCreds).
WithWorkdir("/root").
WithExec([]string{"gh", "auth", "status"}).
Stdout(ctx)
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def github_auth(
self,
gh_creds: Annotated[dagger.Secret, Doc("GitHub Hosts configuration file")],
) -> str:
"""Query the GitHub API"""
return await (
dag.container()
.from_("alpine:3.17")
.with_exec(["apk", "add", "github-cli"])
.with_mounted_secret("/root/.config/gh/hosts.yml", gh_creds)
.with_workdir("/root")
.with_exec(["gh", "auth", "status"])
.stdout()
)
import { dag, object, func, Secret } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Query the GitHub API
*/
@func()
async githubAuth(
/**
* GitHub Hosts configuration File
*/
ghCreds: Secret,
): Promise<string> {
return await dag
.container()
.from("alpine:3.17")
.withExec(["apk", "add", "github-cli"])
.withMountedSecret("/root/.config/gh/hosts.yml", ghCreds)
.withExec(["gh", "auth", "status"])
.stdout()
}
}
Mounting files​
To mount a file as a secret, you can use the following:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'github-auth file://$HOME/.config/gh/hosts.yml'
github-auth file://$HOME/.config/gh/hosts.yml
dagger call github-auth --gh-creds=file://$HOME/.config/gh/hosts.yml
Use secret in Dockerfile build​
The following code listing demonstrates how to inject a secret into a Dockerfile build. The secret is automatically mounted in the build container at /run/secrets/SECRET-ID.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Build a Container from a Dockerfile
func (m *MyModule) Build(
ctx context.Context,
// The source code to build
source *dagger.Directory,
// The secret to use in the Dockerfile
secret *dagger.Secret,
) (*dagger.Container, error) {
// Ensure the Dagger secret's name matches what the Dockerfile
// expects as the id for the secret mount.
secretVal, err := secret.Plaintext(ctx)
if err != nil {
return nil, err
}
buildSecret := dag.SetSecret("gh-secret", secretVal)
return source.
DockerBuild(dagger.DirectoryDockerBuildOpts{
Secrets: []*dagger.Secret{buildSecret},
}), nil
}
from typing import Annotated
import dagger
from dagger import Doc, dag, function, object_type
@object_type
class MyModule:
@function
async def build(
self,
source: Annotated[dagger.Directory, Doc("The source code to build")],
secret: Annotated[dagger.Secret, Doc("The secret to use in the Dockerfile")],
) -> dagger.Container:
"""Build a Container from a Dockerfile"""
# Ensure the Dagger secret's name matches what the Dockerfile
# expects as the id for the secret mount.
build_secret = dag.set_secret("gh-secret", await secret.plaintext())
return source.docker_build(secrets=[build_secret])
import { dag, object, func, Secret } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Build a Container from a Dockerfile
*/
@func()
async build(
/**
* The source code to build
*/
source: Directory,
/**
* The secret to use in the Dockerfile
*/
secret: Secret,
): Promise<Container> {
// Ensure the Dagger secret's name matches what the Dockerfile
// expects as the id for the secret mount.
const buildSecret = dag.setSecret("gh-secret", await secret.plaintext())
return source.dockerBuild({ secrets: [buildSecret] })
}
}
The sample Dockerfile below demonstrates the process of mounting the secret using a secret filesystem mount type and using it in the Dockerfile build process:
FROM alpine:3.17
RUN apk add curl
RUN --mount=type=secret,id=gh-secret \
curl "https://api.github.com/repos/dagger/dagger/issues" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer $(cat /run/secrets/gh-secret)"
Dockerfile build with mounted secret​
Build from a Dockerfile with a mounted secret from the host environment:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'build . env://GITHUB_API_TOKEN'
build . env://GITHUB_API_TOKEN
dagger call build --source=. --secret=env://GITHUB_API_TOKEN
LLMs and Environments​
Build AI-powered workflows and agents with LLM integration. This section contains practical examples for working with LLMs and environments in Dagger.
Use string inputs and outputs​
The following Dagger Function creates an environment with a string input and a string output. The input is a question, and the output represents the answer. The environment is given to an LLM with a prompt describing how it should complete its work.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
)
type MyModule struct{}
func (m *MyModule) Agent(
ctx context.Context,
question string,
) (string, error) {
environment := dag.Env().
WithStringInput("question", question, "the question").
WithStringOutput("answer", "the answer to the question")
work := dag.LLM().
WithEnv(environment).
WithPrompt(`
You are an assistant that helps with complex questions.
You will receive a question and you need to provide a detailed answer.
Make sure to use the provided context and environment variables.
Your answer should be clear and concise.
Your question is: $question
`)
return work.
Env().
Output("answer").
AsString(ctx)
}
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def agent(self, question: str) -> str:
environment = (
dag.env()
.with_string_input("question", question, "the question")
.with_string_output("answer", "the answer to the question")
)
work = (
dag.llm()
.with_env(environment)
.with_prompt(
"""
You are an assistant that helps with complex questions.
You will receive a question and you need to provide a detailed answer.
Make sure to use the provided context and environment variables.
Your answer should be clear and concise.
Your question is: $question
"""
)
)
return await work.env().output("answer").as_string()
import { dag, object, func } from "@dagger.io/dagger"
@object()
export class MyModule {
@func()
async agent(question: string): Promise<string> {
const environment = dag
.env()
.withStringInput("question", question, "the question")
.withStringOutput("answer", "the answer to the question")
const work = dag
.llm()
.withEnv(environment)
.withPrompt(
`You are an assistant that helps with complex questions.
You will receive a question and you need to provide a detailed answer.
Make sure to use the provided context and environment variables.
Your answer should be clear and concise.
Your question is: $question`,
)
return await work.env().output("answer").asString()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function agent(
string $question,
): string {
$environment = dag()
->env()
->withStringInput('question', $question, 'the question')
->withStringOutput('answer', 'the answer to the question');
$work = dag()
->llm()
->withEnv($environment)
->withPrompt(<<<PROMPT
You are an assistant that helps with complex questions.
You will receive a question and you need to provide a detailed answer.
Make sure to use the provided context and environment variables.
Your answer should be clear and concise.
Your question is: $question
PROMPT);
return $work
->env()
->output('answer')
->asString();
}
}
Example​
Ask a question:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'agent "what is an ion"'
agent "what is an ion"
dagger call agent --question="what is an ion?"
Use container inputs and outputs​
The following Dagger Function creates an environment with a container input and a container output. The input is a base alpine container, and the output is the same container, updated with additional libraries and tools. The environment is given to an LLM with a prompt describing how it should update the container.
- Go
- Python
- TypeScript
- PHP
package main
import (
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) Agent() *dagger.Container {
base := dag.Container().From("alpine:latest")
environment := dag.Env().
WithContainerInput("base", base, "a base container to use").
WithContainerOutput("result", "the updated container")
work := dag.LLM().
WithEnv(environment).
WithPrompt(`
You are a software engineer with deep knowledge of Web application development.
You have access to a container.
Install the necessary tools and libraries to create a
complete development environment for Web applications.
Once complete, return the updated container.
`)
return work.
Env().
Output("result").
AsContainer()
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def agent(self) -> dagger.Container:
base = dag.container().from_("alpine:latest")
environment = (
dag.env()
.with_container_input("base", base, "a base container to use")
.with_container_output("result", "the updated container")
)
work = (
dag.llm()
.with_env(environment)
.with_prompt(
"""
You are a software engineer with deep knowledge of Web application
development.
You have access to a container.
Install the necessary tools and libraries to create a
complete development environment for Web applications.
Once complete, return the updated container.
"""
)
)
return work.env().output("result").as_container()
import { dag, object, func, Container } from "@dagger.io/dagger"
@object()
export class MyModule {
@func()
agent(): Container {
const base = dag.container().from("alpine:latest")
const environment = dag
.env()
.withContainerInput("base", base, "a base container to use")
.withContainerOutput("result", "the updated container")
const work = dag
.llm()
.withEnv(environment)
.withPrompt(
`You are a software engineer with deep knowledge of Web application development.
You have access to a container.
Install the necessary tools and libraries to create a
complete development environment for Web applications.
Once complete, return the updated container.`,
)
return work.env().output("result").asContainer()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function agent(): Container {
$base = dag()
->container()
->from('alpine:latest');
$environment = dag()
->env()
->withContainerInput('base', $base, 'a base container to use')
->withContainerOutput('result', 'the updated container');
$work = dag()
->llm()
->withEnv($environment)
->withPrompt(<<<'PROMPT'
You are a software engineer with deep knowledge of Web application development.
You have access to a container.
Install the necessary tools and libraries to create a
complete development environment for Web applications.
Once complete, return the updated container.
PROMPT);
return $work
->env()
->output('result')
->asContainer();
}
}
Example​
Have the agent update the container and open an interactive terminal to inspect the container filesystem:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'agent | terminal'
agent | terminal
dagger call agent terminal
Use directory inputs and outputs​
The following Dagger Function creates an environment with a directory input and a directory output. The input is a directory containing various files, and the output is a different directory containing translations of the original files. The environment is given to an LLM with a prompt describing how it should complete its work.
- Go
- Python
- TypeScript
- PHP
package main
import (
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) Agent() *dagger.Directory {
dir := dag.Git("github.com/dagger/dagger").Branch("main").Tree()
environment := dag.Env().
WithDirectoryInput("source", dir, "the source directory to use").
WithDirectoryOutput("result", "the updated directory")
work := dag.LLM().
WithEnv(environment).
WithPrompt(`
You have access to a directory containing various files.
Translate only the README file in the directory to French and Spanish.
Ensure that the translations are accurate and maintain the original formatting.
Do not modify any other files in the directory.
Create a sub-directory named 'translations' to store the translated files.
For French, add an 'fr' suffix to the translated file name.
For Spanish, add an 'es' suffix to the translated file name.
Do not create any other new files or directories.
Do not delete any files or directories.
Do not investigate any sub-directories.
Once complete, return the 'translations' directory.
`)
return work.
Env().
Output("result").
AsDirectory()
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def agent(self) -> dagger.Directory:
dirname = dag.git("github.com/dagger/dagger").branch("main").tree()
environment = (
dag.env()
.with_directory_input("source", dirname, "the source directory to use")
.with_directory_output("result", "the updated directory")
)
work = (
dag.llm()
.with_env(environment)
.with_prompt(
"""
You have access to a directory containing various files.
Translate only the README file in the directory to French and Spanish.
Ensure that the translations are accurate and maintain the original
formatting.
Do not modify any other files in the directory.
Create a subdirectory named translations to store the translated files.
For French, add an 'fr' suffix to the translated file name.
For Spanish, add an 'es' suffix to the translated file name.
Do not create any other new files or directories.
Do not delete any files or directories.
Do not investigate any sub-directories.
Once complete, return the 'translations' directory.
"""
)
)
return work.env().output("result").as_directory()
import { dag, object, func, Directory } from "@dagger.io/dagger"
@object()
export class MyModule {
@func()
agent(): Directory {
const dir = dag.git("github.com/dagger/dagger").branch("main").tree()
const environment = dag
.env()
.withDirectoryInput("source", dir, "the source directory to use")
.withDirectoryOutput("result", "the updated directory")
const work = dag
.llm()
.withEnv(environment)
.withPrompt(
`You have access to a directory containing various files.
Translate only the README file in the directory to French and Spanish.
Ensure that the translations are accurate and maintain the original formatting.
Do not modify any other files in the directory.
Create a sub-directory named 'translations' to store the translated files.
For French, add an 'fr' suffix to the translated file name.
For Spanish, add an 'es' suffix to the translated file name.
Do not create any other new files or directories.
Do not delete any files or directories.
Do not investigate any sub-directories.
Once complete, return the 'translations' directory.
`,
)
return work.env().output("result").asDirectory()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Directory;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function agent(): Directory {
$dir = dag()
->git('github.com/dagger/dagger')
->branch('main')
->tree();
$environment = dag()
->env()
->withDirectoryInput('source', $dir, 'the source directory to use')
->withDirectoryOutput('result', 'the updated directory');
$work = dag()
->llm()
->withEnv($environment)
->withPrompt(<<<'PROMPT'
You have access to a directory containing various files.
Translate only the README file in the directory to French and Spanish.
Ensure that the translations are accurate and maintain the original formatting.
Do not modify any other files in the directory.
Create a sub-directory named 'translations' to store the translated files.
For French, add an 'fr' suffix to the translated file name.
For Spanish, add an 'es' suffix to the translated file name.
Do not create any other new files or directories.
Do not delete any files or directories.
Do not investigate any sub-directories.
Once complete, return the 'translations' directory.
PROMPT);
return $work
->env()
->output('result')
->asDirectory();
}
}
Example​
Have the agent create the translations and export the directory of translated files to the host:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'agent | export /tmp/out'
agent | export /tmp/out
dagger call agent export --path=/tmp/out
Combine inputs and outputs of multiple types​
The following Dagger Function creates an environment with two inputs (a container and a directory) and one output (a file). The input container is a base golang container, the input directory is a Git repository containing Golang source code, and the output is the built Go executable. The environment is given to an LLM with a prompt describing how it should work.
- Go
- Python
- TypeScript
- PHP
package main
import (
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) Agent() *dagger.File {
dir := dag.Git("github.com/golang/example").Branch("master").Tree().Directory("/hello")
builder := dag.Container().From("golang:latest")
environment := dag.Env().
WithContainerInput("container", builder, "a Golang container").
WithDirectoryInput("directory", dir, "a directory with source code").
WithFileOutput("file", "the built Go executable")
work := dag.LLM().
WithEnv(environment).
WithPrompt(`
You have access to a Golang container.
You also have access to a directory containing Go source code.
Mount the directory into the container and build the Go application.
Once complete, return only the built binary.
`)
return work.
Env().
Output("file").
AsFile()
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def agent(self) -> dagger.File:
dirname = dag.git("github.com/golang/example").branch("master").tree()
builder = dag.container().from_("golang:latest")
environment = (
dag.env()
.with_container_input("container", builder, "a Golang container")
.with_directory_input("directory", dirname, "a directory with source code")
.with_file_output("file", "the built Go executable")
)
work = (
dag.llm()
.with_env(environment)
.with_prompt(
"""
You have access to a Golang container.
You also have access to a directory containing Go source code.
Mount the directory into the container and build the Go application.
Once complete, return only the built binary.
"""
)
)
return work.env().output("file").as_file()
import { dag, object, func, File } from "@dagger.io/dagger"
@object()
export class MyModule {
@func()
agent(): File {
const dir = dag.git("github.com/golang/example").branch("master").tree()
const builder = dag.container().from("golang:latest")
const environment = dag
.env()
.withContainerInput("container", builder, "a Golang container")
.withDirectoryInput("directory", dir, "a directory with source code")
.withFileOutput("file", "the built Go executable")
const work = dag
.llm()
.withEnv(environment)
.withPrompt(
`You have access to a Golang container.
You also have access to a directory containing Go source code.
Mount the directory into the container and build the Go application.
Once complete, return only the built binary.`,
)
return work.env().output("file").asFile()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\File;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function agent(): File {
$dir = dag()
->git('github.com/golang/example')
->branch('master')
->tree();
$builder = dag()
->container()
->from('golang:latest');
$environment = dag()
->env()
->withContainerInput('container', $builder, 'a Golang container')
->withDirectoryInput('directory', $dir, 'a directory with source code')
->withFileOutput('file', 'the built Go executable');
$work = dag()
->llm()
->withEnv($environment)
->withPrompt(<<<'PROMPT'
You have access to a Golang container.
You also have access to a directory containing Go source code.
Mount the directory into the container and build the Go application.
Once complete, return only the built binary.
PROMPT);
return $work
->env()
->output('file')
->asFile();
}
}
Example​
Have the agent build the Go executable and export it to the host:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'agent | export /tmp/out/myfile'
agent | export /tmp/out/myfile
dagger call agent export --path=/tmp/out/myfile
Build a Golang coding agent​
The following Dagger Function creates a Golang coding agent. The input is a programming assignment and the output is a Go executable satisfying the assignment. The environment consists of a base golang container, which is given to an LLM with a prompt describing how it should perform the work.
- Go
- Python
- TypeScript
- PHP
- Java
package main
import (
"dagger/coding-agent/internal/dagger"
)
type CodingAgent struct{}
// Write a Go program
func (m *CodingAgent) GoProgram(
// The programming assignment, e.g. "write me a curl clone"
assignment string,
) *dagger.Container {
environment := dag.Env().
WithStringInput("assignment", assignment, "the assignment to complete").
WithContainerInput("builder",
dag.Container().From("golang").WithWorkdir("/app"),
"a container to use for building Go code").
WithContainerOutput("completed", "the completed assignment in the Golang container")
work := dag.LLM().
WithEnv(environment).
WithPrompt(`
You are an expert Go programmer with an assignment to create a Go program
Create files in the default directory in $builder
Always build the code to make sure it is valid
Do not stop until your assignment is completed and the code builds
Your assignment is: $assignment
`)
return work.
Env().
Output("completed").
AsContainer()
}
import dagger
from dagger import dag, function, object_type
@object_type
class CodingAgent:
@function
def go_program(
self,
assignment: str,
) -> dagger.Container:
"""Write a Go program"""
environment = (
dag.env()
.with_string_input("assignment", assignment, "the assignment to complete")
.with_container_input(
"builder",
dag.container().from_("golang").with_workdir("/app"),
"a container to use for building Go code",
)
.with_container_output(
"completed", "the completed assignment in the Golang container"
)
)
work = (
dag.llm()
.with_env(environment)
.with_prompt(
"""
You are an expert Go programmer
with an assignment to create a Go program
Create files in the default directory in $builder
Always build the code to make sure it is valid
Do not stop until your assignment is completed and the code builds
Your assignment is: $assignment"""
)
)
return work.env().output("completed").as_container()
import { dag, object, func, Container } from "@dagger.io/dagger"
@object()
export class CodingAgent {
/**
* Write a Go program
*/
@func()
goProgram(
/**
* The programming assignment, e.g. "write me a curl clone"
*/
assignment: string,
): Container {
const environment = dag
.env()
.withStringInput("assignment", assignment, "the assignment to complete")
.withContainerInput(
"builder",
dag.container().from("golang").withWorkdir("/app"),
"a container to use for building Go code",
)
.withContainerOutput(
"completed",
"the completed assignment in the Golang container",
)
const work = dag
.llm()
.withEnv(environment)
.withPrompt(
`You are an expert Go programmer with an assignment to create a Go program
Create files in the default directory in $builder
Always build the code to make sure it is valid
Do not stop until your assignment is completed and the code builds
Your assignment is: $assignment`,
)
return work.env().output("completed").asContainer()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
#[Doc('A generated module for CodingAgent functions')]
class CodingAgent
{
#[DaggerFunction]
#[Doc('Write a Go program')]
public function goProgram(string $assignment): Container
{
$environment = dag()->env()
->withStringInput('assignment', $assignment, 'the assignment to complete')
->withContainerInput('builder', dag()->container()->from('golang')->withWorkdir('/app'), 'a container to use for building Go code')
->withContainerOutput('completed', 'the completed assignment in the Golang container');
return dag()
->llm()
->withEnv($environment)
->withPrompt('
You are an expert Go programmer with an assignment to create a Go program
Create files in the default directory in $builder
Always build the code to make sure it is valid
Do not stop until your assignment is completed and the code builds
Your assignment is: $assignment')
->env()
->output('completed')
->asContainer();
}
}
package io.dagger.modules.codingagent;
import static io.dagger.client.Dagger.dag;
import io.dagger.client.Container;
import io.dagger.client.Env;
import io.dagger.module.annotation.Function;
import io.dagger.module.annotation.Object;
/** CodingAgent main object */
@Object
public class CodingAgent {
/** Write a Go program */
@Function
public Container goProgram(String assignment) {
Env environment = dag().env()
.withStringInput("assignment", assignment, "the assignment to complete")
.withContainerInput("builder", dag().container().from("golang").withWorkdir("/app"), "a container to use for building Go code")
.withContainerOutput("completed", "the completed assignment in the Golang container");
return dag()
.llm()
.withEnv(environment)
.withPrompt("""
You are an expert Go programmer with an assignment to create a Go program
Create files in the default directory in $builder
Always build the code to make sure it is valid
Do not stop until your assignment is completed and the code builds
Your assignment is: $assignment
""")
.env()
.output("completed")
.asContainer();
}
}
Example​
Have the agent update the container and open an interactive terminal to inspect the container filesystem:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'agent | terminal'
agent | terminal
dagger call agent terminal
Errors and Debugging​
Handle errors gracefully and build resilient workflows. This section contains practical examples for handling and debugging errors in Dagger workflows.
Terminate gracefully​
The following Dagger Function demonstrates how to handle errors in a workflow.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"errors"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
// Generate an error
func (m *MyModule) Test(ctx context.Context) (string, error) {
out, err := dag.
Container().
From("alpine").
// ERROR: cat: read error: Is a directory
WithExec([]string{"cat", "/"}).
Stdout(ctx)
var e *dagger.ExecError
if errors.As(err, &e) {
return fmt.Sprintf("Test pipeline failure: %s", e.Stderr), nil
} else if err != nil {
return "", err
}
return out, nil
}
from dagger import DaggerError, dag, function, object_type
@object_type
class MyModule:
@function
async def test(self) -> str:
"""Generate an error"""
try:
return await (
dag.container()
.from_("alpine")
# ERROR: cat: read error: Is a directory
.with_exec(["cat", "/"])
.stdout()
)
except DaggerError as e:
# DaggerError is the base class for all errors raised by dagger
return "Test pipeline failure: " + e.stderr
import { dag, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
/**
* Generate an error
*/
@func()
async test(): Promise<string> {
try {
return await dag
.container()
.from("alpine")
// ERROR: cat: read error: Is a directory
.withExec(["cat", "/"])
.stdout()
} catch (e) {
return `Test pipeline failure: ${e.stderr}`
}
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function test(): string {
try {
return dag()
->container()
->from('alpine')
// ERROR: cat: read error: Is a directory
->withExec(['cat', '/'])
->stdout();
} catch (\Exception $e) {
return 'Test pipeline failure: ' . $e->stderr();
}
}
}
Example​
Execute a Dagger Function which creates a container and runs a command in it. If the command fails, the error is captured and the Dagger Function is gracefully terminated with a custom error message.
- System shell
- Dagger Shell
- Dagger CLI
dagger -c test
test
dagger call test
Continue using a container after command execution fails​
The following Dagger Function demonstrates how to continue using a container after a command executed within it fails. A common use case for this is to export a report that a test suite tool generates.
The caveat with this approach is that forcing a zero exit code on a failure caches the failure. This may not be desired depending on the use case.
- Go
- Python
- TypeScript
package main
import (
"context"
"fmt"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
var script = `#!/bin/sh
echo "Test Suite"
echo "=========="
echo "Test 1: PASS" | tee -a report.txt
echo "Test 2: FAIL" | tee -a report.txt
echo "Test 3: PASS" | tee -a report.txt
exit 1
`
type TestResult struct {
Report *dagger.File
ExitCode int
}
// Handle errors
func (m *MyModule) Test(ctx context.Context) (*TestResult, error) {
ctr, err := dag.
Container().
From("alpine").
// add script with execution permission to simulate a testing tool
WithNewFile("/run-tests", script, dagger.ContainerWithNewFileOpts{Permissions: 0o750}).
// run-tests but allow any return code
WithExec([]string{"/run-tests"}, dagger.ContainerWithExecOpts{Expect: dagger.ReturnTypeAny}).
// the result of `sync` is the container, which allows continued chaining
Sync(ctx)
if err != nil {
// unexpected error, could be network failure.
return nil, fmt.Errorf("run tests: %w", err)
}
// save report for inspection.
report := ctr.File("report.txt")
// use the saved exit code to determine if the tests passed.
exitCode, err := ctr.ExitCode(ctx)
if err != nil {
// exit code not found
return nil, fmt.Errorf("get exit code: %w", err)
}
// Return custom type
return &TestResult{
Report: report,
ExitCode: exitCode,
}, nil
}
import dagger
from dagger import DaggerError, dag, field, function, object_type
SCRIPT = """#!/bin/sh
echo "Test Suite"
echo "=========="
echo "Test 1: PASS" | tee -a report.txt
echo "Test 2: FAIL" | tee -a report.txt
echo "Test 3: PASS" | tee -a report.txt
exit 1
"""
@object_type
class TestResult:
report: dagger.File = field()
exit_code: int = field()
@object_type
class MyModule:
@function
async def test(self) -> TestResult:
"""Handle errors"""
try:
ctr = await (
dag.container()
.from_("alpine")
# add script with execution permission to simulate a testing tool.
.with_new_file("/run-tests", SCRIPT, permissions=0o750)
# run-tests but allow any return code
.with_exec(["/run-tests"], expect=dagger.ReturnType.ANY)
# the result of `sync` is the container, which allows continued chaining
.sync()
)
# save report for inspection.
report = ctr.file("report.txt")
# use the saved exit code to determine if the tests passed.
exit_code = await ctr.exit_code()
return TestResult(report=report, exit_code=exit_code)
except DaggerError as e:
# DaggerError is the base class for all errors raised by Dagger
msg = "Unexpected Dagger error"
raise RuntimeError(msg) from e
import { dag, object, func, File, ReturnType } from "@dagger.io/dagger"
const SCRIPT = `#!/bin/sh
echo "Test Suite"
echo "=========="
echo "Test 1: PASS" | tee -a report.txt
echo "Test 2: FAIL" | tee -a report.txt
echo "Test 3: PASS" | tee -a report.txt
exit 1
`
@object()
class TestResult {
@func()
report: File
@func()
exitCode: number
}
@object()
class MyModule {
/**
* Handle errors
*/
@func()
async test(): Promise<TestResult> {
const ctr = await dag
.container()
.from("alpine")
// add script with execution permission to simulate a testing tool.
.withNewFile("/run-tests", SCRIPT, { permissions: 0o750 })
// run-tests but allow any return code
.withExec(["/run-tests"], { expect: ReturnType.Any })
// the result of `sync` is the container, which allows continued chaining
.sync()
const result = new TestResult()
// save report for inspection.
result.report = ctr.file("report.txt")
// use the saved exit code to determine if the tests passed
result.exitCode = await ctr.exitCode()
return result
}
}
Example​
Continue executing a Dagger Function even after a command within it fails. The Dagger Function returns a custom TestResult object containing a test report and the exit code of the failed command.
Obtain the exit code:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'test | exit-code'
test | exit-code
dagger call test exit-code
Obtain the report:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'test | report | contents'
test | report | contents
dagger call test report contents
Debug workflows with the interactive terminal​
Dagger provides two features that can help greatly when trying to debug a workflow - opening an interactive terminal session at the failure point, or at explicit breakpoints throughout your workflow code. All context is available at the point of failure. Multiple terminals are supported in the same Dagger Function; they will open in sequence.
The following Dagger Function opens an interactive terminal session at different stages in a Dagger workflow to debug a container build.
- Go
- Python
- TypeScript
- PHP
package main
import (
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) Container() *dagger.Container {
return dag.Container().
From("alpine:latest").
Terminal().
WithExec([]string{"sh", "-c", "echo hello world > /foo && cat /foo"}).
Terminal()
}
import dagger
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
def container(self) -> dagger.Container:
return (
dag.container()
.from_("alpine:latest")
.terminal()
.with_exec(["sh", "-c", "echo hello world > /foo && cat /foo"])
.terminal()
)
import { dag, Container, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
container(): Container {
return dag
.container()
.from("alpine:latest")
.terminal()
.withExec(["sh", "-c", "echo hello world > /foo && cat /foo"])
.terminal()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function container(): Container {
return dag()
->container()
->from('alpine:latest')
->terminal()
->withExec(['sh', '-c', 'echo hello world > /foo && cat /foo'])
->terminal();
}
}
Example​
Execute a Dagger Function to build a container, and open an interactive terminal at two different points in the build process. The interactive terminal enables you to inspect the container filesystem and environment "live", during the build process.
- System shell
- Dagger Shell
- Dagger CLI
dagger -c container
container
dagger call container
Inspect directories and files​
The following Dagger Function clones Dagger's GitHub repository and opens an interactive terminal session to inspect it. Under the hood, this creates a new container (defaults to alpine) and starts a shell, mounting the repository directory inside.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
)
type MyModule struct{}
func (m *MyModule) SimpleDirectory(ctx context.Context) (string, error) {
return dag.
Git("https://github.com/dagger/dagger.git").
Head().
Tree().
Terminal().
File("README.md").
Contents(ctx)
}
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def simple_directory(self) -> str:
return await (
dag.git("https://github.com/dagger/dagger.git")
.head()
.tree()
.terminal()
.file("README.md")
.contents()
)
import { dag, Container, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
async simpleDirectory(): Promise<string> {
return await dag
.git("https://github.com/dagger/dagger.git")
.head()
.tree()
.terminal()
.file("README.md")
.contents()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function simpleDirectory(): string {
return dag()
->git('https://github.com/dagger/dagger.git')
->head()
->tree()
->terminal()
->file('README.md')
->contents();
}
}
The container created to mount the directory can be customized using additional options. The following Dagger Function revised the previous example to demonstrate this, using an ubuntu container image and bash shell instead of the defaults.
- Go
- Python
- TypeScript
- PHP
package main
import (
"context"
"dagger/my-module/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) AdvancedDirectory(ctx context.Context) (string, error) {
return dag.
Git("https://github.com/dagger/dagger.git").
Head().
Tree().
Terminal(dagger.DirectoryTerminalOpts{
Container: dag.Container().From("ubuntu"),
Cmd: []string{"/bin/bash"},
}).
File("README.md").
Contents(ctx)
}
from dagger import dag, function, object_type
@object_type
class MyModule:
@function
async def advanced_directory(self) -> str:
return await (
dag.git("https://github.com/dagger/dagger.git")
.head()
.tree()
.terminal(
container=dag.container().from_("ubuntu"),
cmd=["/bin/bash"],
)
.file("README.md")
.contents()
)
import { dag, Container, object, func } from "@dagger.io/dagger"
@object()
class MyModule {
@func()
async advancedDirectory(): Promise<string> {
return await dag
.git("https://github.com/dagger/dagger.git")
.head()
.tree()
.terminal({
container: dag.container().from("ubuntu"),
cmd: ["/bin/bash"],
})
.file("README.md")
.contents()
}
}
<?php
declare(strict_types=1);
namespace DaggerModule;
use Dagger\Attribute\DaggerFunction;
use Dagger\Attribute\DaggerObject;
use Dagger\Attribute\Doc;
use Dagger\Container;
use function Dagger\dag;
#[DaggerObject]
class MyModule
{
#[DaggerFunction]
public function advancedDirectory(): string {
return dag()
->git('https://github.com/dagger/dagger.git')
->head()
->tree()
->terminal(
container: dag()->container()->from('ubuntu'),
cmd: ['/bin/bash'],
)
->file('README.md')
->contents();
}
}
Example​
-
Execute a Dagger Function to clone Dagger's GitHub repository and open a terminal session in the repository directory:
- System shell
- Dagger Shell
- Dagger CLI
dagger -c simple-directoryFirst type 'dagger' for interactive mode.simple-directorydagger call simple-directory -
Execute another Dagger Function that does the same as the previous one, except using an
ubuntucontainer image as base and initializing the terminal with thebashshell:- System shell
- Dagger Shell
- Dagger CLI
dagger -c advanced-directoryFirst type 'dagger' for interactive mode.advanced-directorydagger call advanced-directory
Create custom spans​
Dagger represents operations performed by a Dagger Function as OpenTelemetry spans. Spans are typically used to separate tasks that are running in parallel, with each branch waiting for completion.
It is possible to instrument custom OpenTelemetry spans inside any Dagger Function. This allows you to define logical boundaries within complex workflows, measure execution time, and track nested operations with greater granularity. These custom spans appear in the Dagger TUI and Traces.
The following Dagger Function demonstrates this by emitting custom spans for various tasks.
The approach described below is experimental and may be deprecated in favor of a new OpenTelemetry span API. Contribute to the ongoing discussion of this topic on GitHub.
- Go
- Python
- TypeScript
package main
import (
"context"
"dagger/my-module/internal/telemetry"
"golang.org/x/sync/errgroup"
)
type MyModule struct{}
func (m *MyModule) Foo(ctx context.Context) error {
// clone the source code repository
source := dag.
Git("https://github.com/dagger/hello-dagger").
Branch("main").Tree()
// list versions to test against
versions := []string{"20", "22", "23"}
// define errorgroup
eg := new(errgroup.Group)
// run tests concurrently
// emit a span for each
for _, version := range versions {
eg.Go(func() (rerr error) {
ctx, span := Tracer().Start(ctx, "running unit tests with Node "+version)
defer telemetry.End(span, func() error { return rerr })
_, err := dag.Container().
From("node:"+version).
WithDirectory("/src", source).
WithWorkdir("/src").
WithExec([]string{"npm", "install"}).
WithExec([]string{"npm", "run", "test:unit", "run"}).
Sync(ctx)
return err
})
}
return eg.Wait()
}
import anyio
from opentelemetry import trace
from dagger import dag, function, object_type
tracer = trace.get_tracer(__name__)
@object_type
class MyModule:
@function
async def foo(self):
# clone the source code repository
source = dag.git("https://github.com/dagger/hello-dagger").branch("main").tree()
# list versions to test against
versions = ["20", "22", "23"]
async def _test(version: str):
with tracer.start_as_current_span(
f"running unit tests with Node {version}"
):
await (
dag.container()
.from_(f"node:{version}")
.with_directory("/src", source)
.with_workdir("/src")
.with_exec(["npm", "install"])
.with_exec(["npm", "run", "test:unit", "run"])
.sync()
)
# run tests concurrently
# emit a span for each
async with anyio.create_task_group() as tg:
for version in versions:
tg.start_soon(_test, version)
import { dag, object, func } from "@dagger.io/dagger"
import * as trace from "@dagger.io/dagger/telemetry"
@object()
export class MyModule {
@func()
async foo(): Promise<void> {
// clone the source code repository
const source = dag
.git("https://github.com/dagger/hello-dagger")
.branch("main")
.tree()
// list versions to test against
const versions = ["20", "22", "23"]
const tracer = trace.getTracer(MyModule.name)
// run tests concurrently
// emit a span for each
await Promise.all(
versions.map(async (version) => {
await tracer.startActiveSpan(
`running unit tests with Node ${version}`,
async () => {
await dag
.container()
.from(`node:${version}`)
.withDirectory("/src", source)
.withWorkdir("/src")
.withExec(["npm", "install"])
.withExec(["npm", "run", "test:unit", "run"])
.sync()
},
)
}),
)
}
}
When using spans to group and measure Dagger API function calls, ensure that the function calls are not lazily evaluated. The duration of the corresponding spans in this case will always be zero.
Example​
Execute a Dagger Function to run unit tests on the dagger/hello-dagger source code repository with different versions of Node.js, emitting a custom span for each version tested:
dagger call foo
Need Something Else?​
Can't find what you're looking for? Check out:
- Features - Explore Dagger's capabilities
- Module Development - Build reusable modules
- Reference - Deep dive into APIs and configuration
Have a recipe idea? Contribute to the cookbook!