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)
}

Learn more

Get host directory with filters

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"

"dagger.io/dagger"
)

func main() {
os.WriteFile("foo.txt", []byte("1"), 0600)
os.WriteFile("bar.txt", []byte("2"), 0600)
os.WriteFile("baz.rar", []byte("3"), 0600)

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(".", dagger.HostDirectoryOpts{
Exclude: []string{"*.txt"},
}).Entries(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(entries)
}

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

package main

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

"dagger.io/dagger"
)

func main() {
os.WriteFile("foo.txt", []byte("1"), 0600)
os.WriteFile("bar.txt", []byte("2"), 0600)
os.WriteFile("baz.rar", []byte("3"), 0600)

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(".", dagger.HostDirectoryOpts{
Include: []string{"*.rar"},
}).Entries(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(entries)
}

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"

"dagger.io/dagger"
)

func main() {
os.WriteFile("foo.txt", []byte("1"), 0600)
os.WriteFile("bar.txt", []byte("2"), 0600)
os.WriteFile("baz.rar", []byte("3"), 0600)

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(".", dagger.HostDirectoryOpts{
Include: []string{"*.*"},
Exclude: []string{"*.rar"},
}).Entries(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(entries)
}

Learn more

Transfer and read host directory in container

The following code listing writes a host directory to a container at the /host container path and then reads the contents of the 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)
}

Transfer and write to host directory from container

The following code listing writes a host directory to a container at the /host container path, adds a file to it, and then exports the modified directory back to the host:

note

Modifications made to a host directory written to a container filesystem path do not appear on the host. Data flows only one way between Dagger operations, because they are connected in a DAG. To write modifications back to the host directory, you must explicitly export the directory back to the host filesystem.

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("/tmp/sandbox")).
WithExec([]string{"/bin/sh", "-c", `echo foo > /host/bar`}).
Directory("/host").
Export(ctx, "/tmp/sandbox")
if err != nil {
log.Println(err)
return
}

fmt.Println(contents)
}

Learn more

Add Git repository as directory to container

The following code listing adds a remote Git repository branch to a container as a directory at the /src container path and then executes a command in the container to list the directory contents.

package main

import (
"context"
"fmt"
"log"
"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 repository at specified branch
project := client.
Git("https://github.com/dagger/dagger").
Branch("main").
Tree()

// return container with repository
// at /src path
contents, err := client.Container().
From("alpine:latest").
WithDirectory("/src", project).
WithWorkdir("/src").
WithExec([]string{"ls", "/src"}).
Stdout(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(contents)
}

Add Git repository as directory to container with filters

The following code listing adds a remote Git repository branch as a directory at the /src container path, excluding *.md files.

package main

import (
"context"
"fmt"
"log"
"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 repository at specified branch
project := client.
Git("https://github.com/dagger/dagger").
Branch("main").
Tree()

// return container with repository
// at /src path
// excluding *.md files
contents, err := client.Container().
From("alpine:latest").
WithDirectory("/src", project, dagger.ContainerWithDirectoryOpts{
Exclude: []string{"*.md"},
}).
WithWorkdir("/src").
WithExec([]string{"ls", "/src"}).
Stdout(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(contents)
}

The following code listing adds a remote Git repository branch as a directory at the /src container path, including only *.md files.

package main

import (
"context"
"fmt"
"log"
"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 repository at specified branch
project := client.
Git("https://github.com/dagger/dagger").
Branch("main").
Tree()

// return container with repository
// at /src path
// including only *.md files
contents, err := client.Container().
From("alpine:latest").
WithDirectory("/src", project, dagger.ContainerWithDirectoryOpts{
Include: []string{"*.md"},
}).
WithWorkdir("/src").
WithExec([]string{"ls", "/src"}).
Stdout(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(contents)
}

The following code listing adds a remote Git repository branch as a directory at the /src container path, including all *.md files except README.md.

package main

import (
"context"
"fmt"
"log"
"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 repository at specified branch
project := client.
Git("https://github.com/dagger/dagger").
Branch("main").
Tree()

// return container with repository
// at /src path
// include all *.md files except README.md
contents, err := client.Container().
From("alpine:latest").
WithDirectory("/src", project, dagger.ContainerWithDirectoryOpts{
Include: []string{"*.md"},
Exclude: []string{"README.md"},
}).
WithWorkdir("/src").
WithExec([]string{"ls", "/src"}).
Stdout(ctx)
if err != nil {
log.Println(err)
return
}

fmt.Println(contents)
}

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,
// Some registries may require explicit use of docker mediatypes
// rather than the default OCI mediatypes
// MediaTypes: dagger.Dockermediatypes,
})
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,
// Some registries may require explicit use of docker mediatypes
// rather than the default OCI mediatypes
// MediaTypes: dagger.Dockermediatypes,
})
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 from a Dockerfile in the current working directory on the host.

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

Build image from Dockerfile using different build context

The following code listing builds an image from a Dockerfile using a build context directory in a different location than the current working directory.

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()

// get build context directory
contextDir := client.Host().Directory("/projects/myapp")

// get Dockerfile in different filesystem location
dockerfilePath := "/data/myapp/custom.Dockerfile"
dockerfile := client.Host().File(dockerfilePath)

// add Dockerfile to build context directory
workspace := contextDir.WithFile("custom.Dockerfile", dockerfile)

// build using Dockerfile
// publish the resulting container to a registry
ref, err := client.
Container().
Build(workspace, dagger.ContainerBuildOpts{
Dockerfile: "custom.Dockerfile",
}).
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")

// note: some registries (e.g. ghcr.io) may require explicit use
// of Docker mediatypes rather than the default OCI mediatypes
// addr, err := ctr.Publish(ctx, "ttl.sh/my-alpine", dagger.ContainerPublishOpts{
// MediaTypes: dagger.Dockermediatypes,
// })

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", dagger.GitOpts{
SSHAuthSocket: client.Host().UnixSocket(sshAgentPath),
}).
Branch("main").
Tree().
File("README.md").
Contents(ctx)

if err != nil {
panic(err)
}

fmt.Println("readme", readme)
}

Invalidate cache

The following code listing demonstrates how to invalidate the Dagger pipeline operations 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.

note

Changes in mounted cache volumes or secrets do not invalidate the Dagger pipeline operations cache.

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)
}

Services

Expose service containers to host

The following code listing makes HTTP requests from the host to an HTTP service running in a Dagger pipeline.

package main

import (
"context"
"fmt"
"io"
"net/http"
"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()

// create HTTP service container with exposed port 8080
httpSrv := client.Container().
From("python").
WithDirectory("/srv", client.Directory().WithNewFile("index.html", "Hello, world!")).
WithWorkdir("/srv").
WithExec([]string{"python", "-m", "http.server", "8080"}).
WithExposedPort(8080).
AsService()

// expose HTTP service to host
tunnel, err := client.Host().Tunnel(httpSrv).Start(ctx)
if err != nil {
panic(err)
}
defer tunnel.Stop(ctx)

// get HTTP service address
srvAddr, err := tunnel.Endpoint(ctx)
if err != nil {
panic(err)
}

// access HTTP service from host
res, err := http.Get("http://" + srvAddr)
if err != nil {
panic(err)
}
defer res.Body.Close()

// print response
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}

Learn more

Expose host services to containers

The following code listing shows how a database client in a Dagger pipeline can access a database service running on the host.

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()

// expose host service on port 3306
hostSrv := client.Host().Service([]dagger.PortForward{
{Frontend: 3306, Backend: 3306},
})

// create MariaDB container
// with host service binding
// execute SQL query on host service
out, err := client.Container().
From("mariadb:10.11.2").
WithServiceBinding("db", hostSrv).
WithExec([]string{"/bin/sh", "-c", "/usr/bin/mysql --user=root --password=secret --host=db -e 'SELECT * FROM mysql.user'"}).
Stdout(ctx)
if err != nil {
panic(err)
}
fmt.Println(out)
}

Learn more

Use transient database service 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).
AsService()

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

Start and stop services

The following code listing demonstrates explicitly starting a Docker daemon for use in a test suite.

package main_test

import (
"context"
"testing"

"dagger.io/dagger"
"github.com/stretchr/testify/require"
)

func TestFoo(t *testing.T) {
ctx := context.Background()

c, err := dagger.Connect(ctx)
require.NoError(t, err)

dockerd, err := c.Container().From("docker:dind").AsService().Start(ctx)
require.NoError(t, err)

// dockerd is now running, and will stay running
// so you don't have to worry about it restarting after a 10 second gap

// then in all of your tests, continue to use an explicit binding:
_, err = c.Container().From("golang").
WithServiceBinding("docker", dockerd).
WithEnvVariable("DOCKER_HOST", "tcp://docker:2375").
WithExec([]string{"go", "test", "./..."}).
Sync(ctx)
require.NoError(t, err)

// or, if you prefer
// trust `Endpoint()` to construct the address
//
// note that this has the exact same non-cache-busting semantics as WithServiceBinding,
// since hostnames are stable and content-addressed
//
// this could be part of the global test suite setup.
dockerHost, err := dockerd.Endpoint(ctx, dagger.ServiceEndpointOpts{
Scheme: "tcp",
})
require.NoError(t, err)

_, err = c.Container().From("golang").
WithEnvVariable("DOCKER_HOST", dockerHost).
WithExec([]string{"go", "test", "./..."}).
Sync(ctx)
require.NoError(t, err)

// Service.Stop() is available to explicitly stop the service if needed
}