Builds
This page contains practical examples for building artifacts (files and directories) with Dagger. Each section below provides code examples in multiple languages and demonstrates different approaches to building artifacts.
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 := dag.Container().
Build(workspace, dagger.ContainerBuildOpts{
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 = (
dag.container()
.build(context=workspace, 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 dag
.container()
.build(workspace, { 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
foo
with value `123:- System shell
- Dagger Shell
- Dagger CLI
dagger -c 'set foo 123'
First type 'dagger' for interactive mode.set foo 123
dagger call set --key=foo --value=123
-
Retrieve the value of the key
foo
after 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 foo
dagger 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