Skip to main content

Cookbook

Filesystem

List host directory contents

The following code listing obtains a reference to the host working directory and lists the directory's contents.

package main

import (
"context"
"fmt"
"log"
"os"

"dagger.io/dagger"
)

func main() {
ctx := context.Background()

client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
log.Println(err)
return
}
defer client.Close()

entries, err := client.Host().Directory(".").Entries(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(entries)
}

When the Dagger pipeline code is in a sub-directory, it may be more useful to set the parent directory (the project's root directory) as the working directory.

The following listing revises the previous one, obtaining a reference to the parent directory on the host and listing its contents.

package main

import (
"context"
"fmt"
"log"
"os"

"dagger.io/dagger"
)

func main() {
ctx := context.Background()

client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr), dagger.WithWorkdir(".."))
if err != nil {
log.Println(err)
return
}
defer client.Close()

entries, err := client.Host().Directory(".").Entries(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(entries)
}

Learn more

Mount host directory in container

The following code listing mounts a host directory in a container at the /host container path and then executes a command in the container referencing the mounted directory.

package main

import (
"context"
"fmt"
"log"
"os"

"dagger.io/dagger"
)

func main() {
ctx := context.Background()

client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
log.Println(err)
return
}
defer client.Close()

contents, err := client.Container().
From("alpine:latest").
WithDirectory("/host", client.Host().Directory(".")).
WithExec([]string{"ls", "/host"}).
Stdout(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(contents)
}

Learn more

Get host directory with exclusions

The following code listing obtains a reference to the host working directory containing all files except *.txt files.

package main

import (
"context"
"fmt"
"log"
"os"
"path/filepath"

"dagger.io/dagger"
)

func main() {
dir := os.TempDir()
os.WriteFile(filepath.Join(dir, "foo.txt"), []byte("1"), 0600)
os.WriteFile(filepath.Join(dir, "bar.txt"), []byte("2"), 0600)
os.WriteFile(filepath.Join(dir, "baz.rar"), []byte("3"), 0600)

ctx := context.Background()

client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr), dagger.WithWorkdir(dir))
if err != nil {
log.Println(err)
return
}
defer client.Close()

entries, err := client.Host().Directory(".", dagger.HostDirectoryOpts{
Exclude: []string{"*.txt"},
}).Entries(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(entries)
}

Learn more

Get host directory with inclusions

The following code listing obtains a reference to the host working directory containing only *.rar files.

package main

import (
"context"
"fmt"
"log"
"os"
"path/filepath"

"dagger.io/dagger"
)

func main() {
dir := os.TempDir()
os.WriteFile(filepath.Join(dir, "foo.txt"), []byte("1"), 0600)
os.WriteFile(filepath.Join(dir, "bar.txt"), []byte("2"), 0600)
os.WriteFile(filepath.Join(dir, "baz.rar"), []byte("3"), 0600)

ctx := context.Background()

client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr), dagger.WithWorkdir(dir))
if err != nil {
log.Println(err)
return
}
defer client.Close()

entries, err := client.Host().Directory(".", dagger.HostDirectoryOpts{
Include: []string{"*.rar"},
}).Entries(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(entries)
}

Learn more

Get host directory with exclusions and inclusions

The following code listing obtains a reference to the host working directory containing all files except *.rar files.

package main

import (
"context"
"fmt"
"log"
"os"
"path/filepath"

"dagger.io/dagger"
)

func main() {
dir := os.TempDir()
os.WriteFile(filepath.Join(dir, "foo.txt"), []byte("1"), 0600)
os.WriteFile(filepath.Join(dir, "bar.txt"), []byte("2"), 0600)
os.WriteFile(filepath.Join(dir, "baz.rar"), []byte("3"), 0600)

ctx := context.Background()

client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr), dagger.WithWorkdir(dir))
if err != nil {
log.Println(err)
return
}
defer client.Close()

entries, err := client.Host().Directory(".", dagger.HostDirectoryOpts{
Include: []string{"*.*"},
Exclude: []string{"*.rar"},
}).Entries(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(entries)
}

Learn more

Builds

Perform multi-stage build

The following code listing performs a multi-stage build.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
// create dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// get host directory
project := client.Host().Directory(".")

// build app
builder := client.Container().
From("golang:latest").
WithDirectory("/src", project).
WithWorkdir("/src").
WithEnvVariable("CGO_ENABLED", "0").
WithExec([]string{"go", "build", "-o", "myapp"})

// publish binary on alpine base
prodImage := client.Container().
From("alpine").
WithFile("/bin/myapp", builder.File("/src/myapp")).
WithEntrypoint([]string{"/bin/myapp"})

addr, err := prodImage.Publish(ctx, "localhost:5000/multistage")
if err != nil {
panic(err)
}

fmt.Println(addr)
}

Learn more

Perform matrix build

The following code listing builds separate images for multiple OS and CPU architecture combinations.

// Create a multi-build pipeline for a Go application.
package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
println("Building with Dagger")

// define build matrix
geese := []string{"linux", "darwin"}
goarches := []string{"amd64", "arm64"}

ctx := context.Background()
// initialize dagger client
c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}

// get reference to the local project
src := c.Host().Directory(".")

// create empty directory to put build outputs
outputs := c.Directory()

golang := c.Container().
// get golang image
From("golang:latest").
// mount source code into golang image
WithDirectory("/src", src).
WithWorkdir("/src")

for _, goos := range geese {
for _, goarch := range goarches {
// create a directory for each OS and architecture
path := fmt.Sprintf("build/%s/%s/", goos, goarch)

build := golang.
// set GOARCH and GOOS in the build environment
WithEnvVariable("GOOS", goos).
WithEnvVariable("GOARCH", goarch).
WithExec([]string{"go", "build", "-o", path})

// add build to outputs
outputs = outputs.WithDirectory(path, build.Directory(path))
}
}

// write build artifacts to host
ok, err := outputs.Export(ctx, ".")
if err != nil {
panic(err)
}

if !ok {
panic("did not export files")
}
}

Learn more

Build multi-arch image

The following code listing builds a single image for different CPU architectures using native emulation.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

// the 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
}

// the container registry for the multi-platform image
const imageRepo = "localhost/testrepo:latest"

func main() {
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// the git repository containing code for the binary to be built
gitRepo := client.Git("https://github.com/dagger/dagger.git").
Branch("086862926433e19e1f24cd709e6165c36bdb2633").
Tree()

platformVariants := make([]*dagger.Container, 0, len(platforms))
for _, platform := range platforms {
// pull the golang image for this platform
ctr := client.Container(dagger.ContainerOpts{Platform: platform})
ctr = ctr.From("golang:1.20-alpine")

// mount in source code
ctr = ctr.WithDirectory("/src", gitRepo)

// mount in an empty dir where the built binary will live
ctr = ctr.WithDirectory("/output", client.Directory())

// ensure the binary will be statically linked and thus executable
// in the final image
ctr = ctr.WithEnvVariable("CGO_ENABLED", "0")

// build the binary and put the result at the mounted output
// directory
ctr = ctr.WithWorkdir("/src")
ctr = ctr.WithExec([]string{
"go", "build",
"-o", "/output/dagger",
"/src/cmd/dagger",
})

// select the output directory
outputDir := ctr.Directory("/output")

// wrap the output directory in a new empty container marked
// with the same platform
binaryCtr := client.
Container(dagger.ContainerOpts{Platform: platform}).
WithRootfs(outputDir)
platformVariants = append(platformVariants, binaryCtr)
}

// publishing the final image uses the same API as single-platform
// images, but now additionally specify the `PlatformVariants`
// option with the containers built before.
imageDigest, err := client.
Container().
Publish(ctx, imageRepo, dagger.ContainerPublishOpts{
PlatformVariants: platformVariants,
})
if err != nil {
panic(err)
}
fmt.Println("Pushed multi-platform image w/ digest: ", imageDigest)
}

Learn more

Build multi-arch image with cross-compilation

The following code listing builds a single image for different CPU architectures using cross-compilation.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
platformFormat "github.com/containerd/containerd/platforms"
)

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
}

// the container registry for the multi-platform image
const imageRepo = "localhost/testrepo:latest"

// util that returns the architecture of the provided platform
func architectureOf(platform dagger.Platform) string {
return platformFormat.MustParse(string(platform)).Architecture
}

func main() {
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

gitRepo := client.Git("https://github.com/dagger/dagger.git").
Branch("086862926433e19e1f24cd709e6165c36bdb2633").
Tree()

platformVariants := make([]*dagger.Container, 0, len(platforms))
for _, platform := range platforms {
// pull the golang image for the *host platform*. This is
// accomplished by just not specifying a platform; the default
// is that of the host.
ctr := client.Container()
ctr = ctr.From("golang:1.20-alpine")

// mount in our source code
ctr = ctr.WithDirectory("/src", gitRepo)

// mount in an empty dir to put the built binary
ctr = ctr.WithDirectory("/output", client.Directory())

// ensure the binary will be statically linked and thus executable
// in the final image
ctr = ctr.WithEnvVariable("CGO_ENABLED", "0")

// configure the go compiler to use cross-compilation targeting the
// desired platform
ctr = ctr.WithEnvVariable("GOOS", "linux")
ctr = ctr.WithEnvVariable("GOARCH", architectureOf(platform))

// build the binary and put the result at the mounted output
// directory
ctr = ctr.WithWorkdir("/src")
ctr = ctr.WithExec([]string{
"go", "build",
"-o", "/output/dagger",
"/src/cmd/dagger",
})
// select the output directory
outputDir := ctr.Directory("/output")

// wrap the output directory in a new empty container marked
// with the platform
binaryCtr := client.
Container(dagger.ContainerOpts{Platform: platform}).
WithRootfs(outputDir)
platformVariants = append(platformVariants, binaryCtr)
}

// publishing the final image uses the same API as single-platform
// images, but now additionally specify the `PlatformVariants`
// option with the containers built before.
imageDigest, err := client.
Container().
Publish(ctx, imageRepo, dagger.ContainerPublishOpts{
PlatformVariants: platformVariants,
})
if err != nil {
panic(err)
}
fmt.Println("published multi-platform image with digest", imageDigest)
}

Learn more

Build image from Dockerfile

The following code listing builds an image using an existing Dockerfile.

package main

import (
"context"
"fmt"
"math"
"math/rand"
"os"

"dagger.io/dagger"
)

func main() {
ctx := context.Background()

// initialize Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

contextDir := client.Host().Directory(".")

ref, err := contextDir.
DockerBuild().
Publish(ctx, fmt.Sprintf("ttl.sh/hello-dagger-%.0f", math.Floor(rand.Float64()*10000000))) //#nosec
if err != nil {
panic(err)
}

fmt.Printf("Published image to :%s\n", ref)
}

Learn more

Add OCI annotations to image

The following code listing adds OpenContainer Initiative (OCI) annotations to an image.

package main

import (
"context"
"fmt"
"os"
"time"

"dagger.io/dagger"
)

func main() {
// create Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// create and publish image with annotations
ctr := client.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")

addr, err := ctr.Publish(ctx, "ttl.sh/my-alpine")
if err != nil {
panic(err)
}

fmt.Println(addr)
}

Define build-time variables

The following code listing defines various environment variables for build purposes.

// Create a multi-build pipeline for a Go application.
package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
println("Building with Dagger")

// define build matrix
geese := []string{"linux", "darwin"}
goarches := []string{"amd64", "arm64"}

ctx := context.Background()
// initialize dagger client
c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}

// get reference to the local project
src := c.Host().Directory(".")

// create empty directory to put build outputs
outputs := c.Directory()

golang := c.Container().
// get golang image
From("golang:latest").
// mount source code into golang image
WithDirectory("/src", src).
WithWorkdir("/src")

for _, goos := range geese {
for _, goarch := range goarches {
// create a directory for each OS and architecture
path := fmt.Sprintf("build/%s/%s/", goos, goarch)

build := golang.
// set GOARCH and GOOS in the build environment
WithEnvVariable("GOOS", goos).
WithEnvVariable("GOARCH", goarch).
WithExec([]string{"go", "build", "-o", path})

// add build to outputs
outputs = outputs.WithDirectory(path, build.Directory(path))
}
}

// write build artifacts to host
ok, err := outputs.Export(ctx, ".")
if err != nil {
panic(err)
}

if !ok {
panic("did not export files")
}
}

Learn more

Access private Git repository

The following code listing demonstrates how to access a private Git repository using SSH.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
ctx := context.Background()
client, err := dagger.Connect(ctx)
if err != nil {
panic(err)
}
defer client.Close()

// Retrieve path of authentication agent socket from host
sshAgentPath := os.Getenv("SSH_AUTH_SOCK")

// Private repository with a README.md file at the root.
readme, err := client.
Git("git@private-repository.git").
Branch("main").
Tree(
dagger.GitRefTreeOpts{
SSHAuthSocket: client.Host().UnixSocket(sshAgentPath),
},
).
File("README.md").
Contents(ctx)

if err != nil {
panic(err)
}

fmt.Println("readme", readme)
}

Use transient database for application tests

The following code listing creates a temporary MariaDB database service and binds it to an application container for unit/integration testing.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
ctx := context.Background()

// create Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))

if err != nil {
panic(err)
}
defer client.Close()

// get MariaDB base image
mariadb := client.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)

// get Drupal base image
// install additional dependencies
drupal := client.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
test, err := 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)

if err != nil {
panic(err)
}

fmt.Println(test)
}

Learn more

Invalidate cache

The following code listing demonstrates how to invalidate the Dagger cache and thereby force execution of subsequent pipeline steps, by introducing a volatile time variable at a specific point in the Dagger pipeline.

note

This is a temporary workaround until cache invalidation support is officially added to Dagger.

package main

import (
"context"
"fmt"
"os"
"time"

"dagger.io/dagger"
)

func main() {
// create Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// invalidate cache to force execution
// of second WithExec() operation
output, err := client.Pipeline("test").
Container().
From("alpine").
WithExec([]string{"apk", "add", "curl"}).
WithEnvVariable("CACHEBUSTER", time.Now().String()).
WithExec([]string{"apk", "add", "zip"}).
Stdout(ctx)
if err != nil {
panic(err)
}
fmt.Println(output)
}

Outputs

Publish image to registry

The following code listing publishes a container image to a remote registry (Docker Hub). Replace the DOCKER-HUB-USERNAME and DOCKER-HUB-PASSWORD placeholders with your Docker Hub username and password respectively.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
// initialize Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// set secret as string value
secret := client.SetSecret("password", "DOCKER-HUB-PASSWORD")

// create container
c := client.Container(dagger.ContainerOpts{Platform: "linux/amd64"}).
From("nginx:1.23-alpine").
WithNewFile("/usr/share/nginx/html/index.html", dagger.ContainerWithNewFileOpts{
Contents: "Hello from Dagger!",
Permissions: 0o400,
})

// use secret for registry authentication
addr, err := c.
WithRegistryAuth("docker.io", "DOCKER-HUB-USERNAME", secret).
Publish(ctx, "DOCKER-HUB-USERNAME/my-nginx")
if err != nil {
panic(err)
}

// print result
fmt.Println("Published at:", addr)
}

Learn more

Export image to host

The following code listing exports a container image from a Dagger pipeline to the host.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
// initialize Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// use NGINX container
// add new webserver index page
c := client.Container(dagger.ContainerOpts{Platform: "linux/amd64"}).
From("nginx:1.23-alpine").
WithNewFile("/usr/share/nginx/html/index.html", dagger.ContainerWithNewFileOpts{
Contents: "Hello from Dagger!",
Permissions: 0o400,
})

// export to host filesystem
val, err := c.Export(ctx, "/tmp/my-nginx.tar")
if err != nil {
panic(err)
}

// print result
fmt.Println("Exported image: ", val)
}

Learn more

Export container directory to host

The following code listing exports the contents of a container directory to the host's temporary directory.

package main

import (
"context"
"fmt"
"log"
"os"
"path/filepath"

"dagger.io/dagger"
)

func main() {
hostdir := os.TempDir()

ctx := context.Background()

client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
log.Println(err)
return
}
defer client.Close()

_, err = client.Container().From("alpine:latest").
WithWorkdir("/tmp").
WithExec([]string{"wget", "https://dagger.io"}).
Directory(".").
Export(ctx, hostdir)
if err != nil {
log.Println(err)
return
}
contents, err := os.ReadFile(filepath.Join(hostdir, "index.html"))
if err != nil {
log.Println(err)
return
}
fmt.Println(string(contents))
}

Learn more

Publish image to registry with multiple tags

The following code listing tags a container image multiple times and publishes it to a remote registry (Docker Hub). Set the Docker Hub username and password as host environment variables named DOCKERHUB_USERNAME and DOCKERHUB_PASSWORD respectively.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
// create Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// load registry credentials from environment variables
username := os.Getenv("DOCKERHUB_USERNAME")
if username == "" {
panic("DOCKERHUB_USERNAME env var must be set")
}
passwordPlaintext := os.Getenv("DOCKERHUB_PASSWORD")
if passwordPlaintext == "" {
panic("DOCKERHUB_PASSWORD env var must be set")
}
password := client.SetSecret("password", passwordPlaintext)

// define multiple image tags
tags := [4]string{"latest", "1.0-alpine", "1.0", "1.0.0"}

// create and publish image with multiple tags
ctr := client.Container().
From("alpine").
WithRegistryAuth("docker.io", username, password)

for _, tag := range tags {
addr, err := ctr.Publish(ctx, fmt.Sprintf("%s/alpine:%s", username, tag))
if err != nil {
panic(err)
}
fmt.Println("Published: ", addr)
}
}

Secrets

Expose secret via environment variable

The following code listing demonstrates how to inject an environment variable in a container as a secret.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
// initialize Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// read secret from host variable
secret := client.SetSecret("gh-secret", os.Getenv("GH_SECRET"))

// use secret in container environment
out, err := client.
Container().
From("alpine:3.17").
WithSecretVariable("GITHUB_API_TOKEN", secret).
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)
if err != nil {
panic(err)
}

fmt.Println(out)
}

Learn more

Expose secret via file

The following code listing demonstrates how to inject a file in a container as a secret.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
)

func main() {
// initialize Dagger client
ctx := context.Background()
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// read file
config, err := os.ReadFile("/home/USER/.config/gh/hosts.yml")
if err != nil {
panic(err)
}

// set secret to file contents
secret := client.SetSecret("ghConfig", string(config))

// mount secret as file in container
out, err := client.
Container().
From("alpine:3.17").
WithExec([]string{"apk", "add", "github-cli"}).
WithMountedSecret("/root/.config/gh/hosts.yml", secret).
WithWorkdir("/root").
WithExec([]string{"gh", "auth", "status"}).
Stdout(ctx)
if err != nil {
panic(err)
}

fmt.Println(out)
}

Learn more

Load secret from Google Cloud Secret Manager

The following code listing reads a secret (a GitHub API token) from Google Cloud Secret Manager and uses it in a Dagger pipeline to interact with the GitHub API.

Set up Application Default Credentials (ADC) and replace the PROJECT-ID and SECRET-ID placeholders with your Google Cloud project and secret identifiers respectively.

package main

import (
"context"
"fmt"
"os"

secretmanager "cloud.google.com/go/secretmanager/apiv1"
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
"dagger.io/dagger"
)

func main() {
ctx := context.Background()

// get secret from Google Cloud Secret Manager
secretPlaintext, err := gcpGetSecretPlaintext(ctx, "PROJECT-ID", "SECRET-ID")
if err != nil {
panic(err)
}

// initialize Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
if err != nil {
panic(err)
}
defer client.Close()

// load secret into Dagger
secret := client.SetSecret("ghApiToken", string(secretPlaintext))

// use secret in container environment
out, err := client.
Container().
From("alpine:3.17").
WithSecretVariable("GITHUB_API_TOKEN", secret).
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)
if err != nil {
panic(err)
}

// print result
fmt.Println(out)
}

func gcpGetSecretPlaintext(ctx context.Context, projectID, secretID string) (string, error) {
secretUri := fmt.Sprintf("projects/%s/secrets/%s/versions/latest", projectID, secretID)

// initialize Google Cloud API client
gcpClient, err := secretmanager.NewClient(ctx)
if err != nil {
panic(err)
}
defer gcpClient.Close()

// retrieve secret
secReq := &secretmanagerpb.AccessSecretVersionRequest{
Name: secretUri,
}

res, err := gcpClient.AccessSecretVersion(ctx, secReq)
if err != nil {
panic(err)
}

secretPlaintext := res.Payload.Data

return string(secretPlaintext), nil
}

Learn more

Load secret from Hashicorp Vault

The following code listing reads a secret (a GitHub API token) from a Hashicorp Vault Key/Value v2 engine and uses it in a Dagger pipeline to interact with the GitHub API.

Set the Hashicorp Vault URI, namespace, role and secret identifiers as host environment variables named VAULT_ADDRESS, VAULT_NAMESPACE, VAULT_ROLE_ID and VAULT_SECRET_ID respectively. Replace the MOUNT-PATH, SECRET-ID and SECRET-KEY placeholders with your Hashicorp Vault mount point, secret identifier and key respectively.

package main

import (
"context"
"fmt"
"os"

"dagger.io/dagger"
"github.com/hashicorp/vault-client-go"
"github.com/hashicorp/vault-client-go/schema"
)

func main() {
ctx := context.Background()

client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stdout))
if err != nil {
panic(err)
}
defer client.Close()

// get secret from Vault
secretPlaintext, err := getVaultSecret("MOUNT-PATH", "SECRET-ID", "SECRET-KEY")
if err != nil {
panic(err)
}

// load secret into Dagger
secret := client.SetSecret("ghApiToken", secretPlaintext)

// use secret in container environment
out, err := client.Container().
From("alpine:3.17").
WithSecretVariable("GITHUB_API_TOKEN", secret).
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)

// print result
fmt.Println(out)
}

func getVaultSecret(mountPath, secretID, secretKey string) (string, error) {
ctx := context.Background()

// check for required variables in host environment
address := os.Getenv("VAULT_ADDRESS")
role_id := os.Getenv("VAULT_ROLE_ID")
secret_id := os.Getenv("VAULT_SECRET_ID")

// create Vault client
client, err := vault.New(
vault.WithAddress(address),
)
if err != nil {
return "", err
}

// log in to Vault
resp, err := client.Auth.AppRoleLogin(
ctx,
schema.AppRoleLoginRequest{
RoleId: role_id,
SecretId: secret_id,
},
vault.WithMountPath(mountPath),
)
if err != nil {
return "", err
}

if err := client.SetToken(resp.Auth.ClientToken); err != nil {
return "", err
}

// read and return secret
secret, err := client.Secrets.KvV2Read(
ctx,
secretID,
vault.WithMountPath(mountPath),
)
if err != nil {
return "", err
}
return fmt.Sprintf("%s", secret.Data.Data[secretKey]), nil
}

Learn more

Optimizations

Cache dependencies

The following code listing uses a cache volume for application dependencies. This enables Dagger to reuse the contents of the cache every time the pipeline runs, and thereby speed up pipeline operations.

package main

import (
"context"
"fmt"
"math"
"math/rand"
"os"

"dagger.io/dagger"
)

func main() {
ctx := context.Background()

// initialize Dagger client
client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))

if err != nil {
panic(err)
}
defer client.Close()

// create a cache volume
nodeCache := client.CacheVolume("node")

// use a node:16-slim container
// mount the source code directory on the host
// at /src in the container
// mount the cache volume to persist dependencies
source := client.Container().
From("node:16-slim").
WithDirectory("/src", client.Host().Directory("."), dagger.ContainerWithDirectoryOpts{
Exclude: []string{"node_modules/", "ci/"},
}).
WithMountedCache("/src/node_modules", nodeCache)

// set the working directory in the container
// install application dependencies
runner := source.WithWorkdir("/src").
WithExec([]string{"npm", "install"})