Skip to main content

Cookbook

Filesystem

Copy a directory or remote repository to a container

The following Dagger Function accepts a Directory argument, which could reference either a directory from the local filesystem or from a remote Git repository. It copies the specified directory to the /src path in a container and returns the modified container.

note

When working with private Git repositories, ensure that SSH authentication is properly configured on your Dagger host.

package main

import (
"context"

"dagger/my-module/internal/dagger"
)

type MyModule struct{}

// Return a container with a specified directory
func (m *MyModule) CopyDirectory(
ctx context.Context,
// Source directory
source *dagger.Directory,
) *dagger.Container {
return dag.Container().
From("alpine:latest").
WithDirectory("/src", source)
}

Examples

  • Copy the /myapp host directory to /src in the container and return the modified container:

    dagger call copy-directory --source=./myapp/
  • Copy the public dagger/dagger GitHub repository to /src in the container and return the modified container:

    dagger call copy-directory --source=github.com/dagger/dagger#main
  • Copy the private user/foo GitHub repository to /src in the container and return the modified container:

    dagger call copy-directory --source=ssh://git@github.com/user/foo#main
  • Copy the public dagger/dagger GitHub repository to /src in the container and list the contents of the directory:

    dagger call copy-directory --source=https://github.com/dagger/dagger#main  directory --path=/src entries

Modify a copied directory or remote repository in a container

The following Dagger Function accepts a Directory argument, which could reference either a directory from the local filesystem or from a remote Git repository. It copies the specified directory to the /src path in a container, adds a file to it, and returns the modified container.

note

Modifications made to a directory's contents after it is written to a container filesystem do not appear on the source. Data flows only one way between Dagger operations, because they are connected in a DAG. To transfer modifications back to the local host, you must explicitly export the directory back to the host filesystem.

note

When working with private Git repositories, ensure that SSH authentication is properly configured on your Dagger host.

package main

import (
"context"

"dagger/my-module/internal/dagger"
)

type MyModule struct{}

// Return a container with a specified directory and an additional file
func (m *MyModule) CopyAndModifyDirectory(
ctx context.Context,
// Source directory
source *dagger.Directory,
) *dagger.Container {
return dag.Container().
From("alpine:latest").
WithDirectory("/src", source).
WithExec([]string{"/bin/sh", "-c", `echo foo > /src/foo`})
}

Examples

  • Copy the /myapp host directory to /src in the container, add a file to it, and return the modified container:

    dagger call copy-and-modify-directory --source=./myapp/
  • Copy the public dagger/dagger GitHub repository to /src in the container, add a file to it, and return the modified container:

    dagger call copy-and-modify-directory --source=github.com/dagger/dagger#main
  • Copy the private user/foo GitHub repository to /src in the container, add a file to it, and return the modified container:

    dagger call copy-and-modify-directory --source=ssh://git@github.com/user/foo#main
  • Copy the public dagger/dagger GitHub repository to /src in the container, add a file to it, and list the contents of the directory:

    dagger call copy-and-modify-directory --source=https://github.com/dagger/dagger#main directory --path=/src entries

Copy a file to a container

The following Dagger Function accepts a File argument representing the host file to be copied. It writes the specified file to a container in the /src/ directory and returns the modified container.

package main

import (
"context"
"fmt"

"dagger/my-module/internal/dagger"
)

type MyModule struct{}

// Return a container with a specified file
func (m *MyModule) CopyFile(
ctx context.Context,
// Source file
f *dagger.File,
) *dagger.Container {
name, _ := f.Name(ctx)
return dag.Container().
From("alpine:latest").
WithFile(fmt.Sprintf("/src/%s", name), f)
}

Example

  • Copy the /home/admin/archives.zip file on the host to the /src directory in the container and return the modified container:

    dagger call copy-file --f=/home/admin/archives.zip
  • Copy the /home/admin/archives.zip file on the host to the /src directory in the container and list the contents of the directory:

    dagger call copy-file --f=/home/admin/archives.zip directory --path=/src entries

Copy a subset of a directory or remote repository to a container

The following Dagger Function accepts a Directory argument, which could reference either a directory from the local filesystem or from a remote Git repository. It copies the specified directory to the /src path in a container, using wildcard patterns to exclude specified sub-directories and files, and returns the modified container.

note

When working with private Git repositories, ensure that SSH authentication is properly configured on your Dagger host.

package main

import (
"context"

"dagger/my-module/internal/dagger"
)

type MyModule struct{}

// Return a container with a filtered directory
func (m *MyModule) CopyDirectoryWithExclusions(
ctx context.Context,
// Source directory
source *dagger.Directory,
// Directory exclusion pattern
// +optional
excludeDirectoryPattern string,
// +optional
// File exclusion pattern
excludeFilePattern string,
) *dagger.Container {
filteredSource := source.
WithoutDirectory(excludeDirectoryPattern).
WithoutFile(excludeFilePattern)
return dag.Container().
From("alpine:latest").
WithDirectory("/src", filteredSource)
}

Examples

  • Copy the current host directory to /src in the container, excluding the dagger sub-directory and the dagger.json file, and return the modified container:

    dagger call copy-directory-with-exclusions --source=. --exclude-directory=dagger --exclude-file=dagger.json
  • Copy the public dagger/dagger GitHub repository to /src in the container, excluding all Markdown files, and list the contents of the directory:

    dagger call copy-directory-with-exclusions --source=https://github.com/dagger/dagger#main --exclude-file='*.md' directory --path=/src entries
  • Copy the private user/foo GitHub repository to /src in the container, excluding all Markdown files, and list the contents of the directory:

    dagger call copy-directory-with-exclusions --source=ssh://git@github.com/user/foo#main --exclude-file='*.md' directory --path=/src entries

Copy a file to the Dagger module runtime container for custom processing

The following Dagger Function accepts a File argument and copies the specified file to the Dagger module runtime container. This makes it possible to add one or more files to the runtime container and manipulate them using custom logic.

package main

import (
"context"
"dagger/my-module/internal/dagger"
"fmt"
"os"
)

type MyModule struct{}

// Copy a file to the Dagger module runtime container for custom processing
func (m *MyModule) CopyFile(ctx context.Context, source *dagger.File) {
source.Export(ctx, "foo.txt")
// your custom logic here
// for example, read and print the file in the Dagger Engine container
fmt.Println(os.ReadFile("foo.txt"))
}

Example

Copy the data.json host file to the runtime container and process it:

dagger call copy-file --source=../data.json

Builds

Perform a multi-stage build

The following Dagger Function performs a multi-stage build.

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
}

Example

Perform a multi-stage build of the source code in the golang/example/hello repository and publish the resulting image:

dagger call build --src="https://github.com/golang/example#master:hello"

Perform a matrix build

The following Dagger Function performs a matrix build.

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
}

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.

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.

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
}

Example

Build and publish a multi-platform image:

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.

info

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

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
}

Example

Build and publish a multi-platform image with cross compliation:

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.

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 := dag.Container().
WithDirectory("/src", src).
WithWorkdir("/src").
Directory("/src").
DockerBuild(). // build from Dockerfile
Publish(ctx, "ttl.sh/hello-dagger")

if err != nil {
return "", err
}

return ref, nil
}

Example

Build and publish an image from an existing Dockerfile

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.

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
}

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:

dagger call build \
--src "https://github.com/dockersamples/python-flask-redis" \
--dockerfile https://github.com/vimagick/dockerfiles#master:registry-cli/Dockerfile

Add OCI labels to image

The following Dagger Function adds OpenContainer Initiative (OCI) labels to an image.

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
}

Example

Build and publish an image with OCI labels:

dagger call build

Invalidate cache

The following function demonstrates how to invalidate the Dagger layer cache and 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.
  • Changes in mounted cache volumes or secrets do not invalidate the Dagger layer cache.
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
}

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.

dagger call build

Secrets

Use secret variables

The following Dagger Function accepts a GitHub personal access token as a Secret, and uses the Secret to authorize a request to the GitHub API. The secret may be sourced from a host environment variable, host file, or host command execution.

package main

import (
"context"

"main/internal/dagger"
)

type MyModule struct{}

// Query the GitHub API
func (m *MyModule) GithubApi(
ctx context.Context,
// GitHub API token
token *dagger.Secret,
) (string, error) {
return dag.Container().
From("alpine:3.17").
WithSecretVariable("GITHUB_API_TOKEN", token).
WithExec([]string{"apk", "add", "curl"}).
WithExec([]string{"sh", "-c", `curl "https://api.github.com/repos/dagger/dagger/issues" --header "Accept: application/vnd.github+json" --header "Authorization: Bearer $GITHUB_API_TOKEN"`}).
Stdout(ctx)
}

Examples

  • Query the API using a secret sourced from an environment variable:

    dagger call github-api --token=env:GITHUB_API_TOKEN
  • Query the API using a secret sourced from a file:

    dagger call github-api --token=file:./github.txt
  • Query the API using a secret sourced from a command:

    dagger call github-api --token=cmd:"gh auth token"

Mount files as secrets

The following Dagger Function accepts a GitHub hosts configuration file as a Secret, and mounts the file as a Secret to a container to authorize a request to GitHub.

package main

import (
"context"

"main/internal/dagger"
)

type MyModule struct{}

// Query the GitHub API
func (m *MyModule) GithubAuth(
ctx context.Context,
// GitHub Hosts configuration file
ghCreds *dagger.Secret,
) (string, error) {
return dag.Container().
From("alpine:3.17").
WithExec([]string{"apk", "add", "github-cli"}).
WithMountedSecret("/root/.config/gh/hosts.yml", ghCreds).
WithWorkdir("/root").
WithExec([]string{"gh", "auth", "status"}).
Stdout(ctx)
}

Example

Query the GitHub API using a mounted secret file:

dagger call github-auth \
--gh-creds=file:$HOME/.config/gh/hosts.yml

Use secret in Dockerfile build

The following code listing demonstrates how to inject a secret into a Dockerfile build. The secret is automatically mounted in the build container at /run/secrets/SECRET-ID.

package main

import (
"context"

"main/internal/dagger"
)

type MyModule struct{}

// Build a Container from a Dockerfile
func (m *MyModule) Build(
ctx context.Context,
// The source code to build
source *dagger.Directory,
// The secret to use in the Dockerfile
secret *dagger.Secret,
) (*dagger.Container, error) {
// Ensure the Dagger secret's name matches what the Dockerfile
// expects as the id for the secret mount.
secretVal, err := secret.Plaintext(ctx)
if err != nil {
return nil, err
}
buildSecret := dag.SetSecret("gh-secret", secretVal)

return source.
DockerBuild(dagger.DirectoryDockerBuildOpts{
Secrets: []*dagger.Secret{buildSecret},
}), nil
}

The sample Dockerfile below demonstrates the process of mounting the secret using a secret filesystem mount type and using it in the Dockerfile build process:

FROM alpine:3.17
RUN apk add curl
RUN --mount=type=secret,id=gh-secret \
curl "https://api.github.com/repos/dagger/dagger/issues" \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer $(cat /run/secrets/gh-secret)"

Example

Build from a Dockerfile with a mounted secret:

dagger call build --source=. --secret=env:GITHUB_API_TOKEN

Services

Bind and use services in Dagger Functions

The first Dagger Function below creates and returns an HTTP service. This service is bound and used from a different Dagger Function, via a service binding using an alias like www.

package main

import (
"context"
"main/internal/dagger"
)

type MyModule struct{}

// Start and return an HTTP service
func (m *MyModule) HttpService() *dagger.Service {
return dag.Container().
From("python").
WithWorkdir("/srv").
WithNewFile("index.html", "Hello, world!").
WithExec([]string{"python", "-m", "http.server", "8080"}).
WithExposedPort(8080).
AsService()
}

// Send a request to an HTTP service and return the response
func (m *MyModule) Get(ctx context.Context) (string, error) {
return dag.Container().
From("alpine").
WithServiceBinding("www", m.HttpService()).
WithExec([]string{"wget", "-O-", "http://www:8080"}).
Stdout(ctx)
}

Example

Send a request from one Dagger Function to a bound HTTP service instantiated by a different Dagger Function:

dagger call get

Expose services in Dagger Functions to the host

The Dagger Function below creates and returns an HTTP service. This service can be used from the host.

package main

import "main/internal/dagger"

type MyModule struct{}

// Start and return an HTTP service
func (m *MyModule) HttpService() *dagger.Service {
return dag.Container().
From("python").
WithWorkdir("/srv").
WithNewFile("index.html", "Hello, world!").
WithExec([]string{"python", "-m", "http.server", "8080"}).
WithExposedPort(8080).
AsService()
}

Examples

  • Expose the HTTP service instantiated by a Dagger Function to the host on the default port:

    dagger call http-service up

    # access the service from the host
    curl localhost:8080
  • Expose the HTTP service instantiated by a Dagger Function to the host on a different host port:

    dagger call  http-service up --ports 9000:8080

    # access the service from the host
    curl localhost:9000

Expose host services to Dagger Functions

The following Dagger Function accepts a Service running on the host, binds it using an alias, and creates a client to access it via the service binding. This example uses a MariaDB database service running on host port 3306, aliased as db in the Dagger Function.

note

This implies that a service is already listening on a port on the host, out-of-band of Dagger.

package main

import (
"context"
"main/internal/dagger"
)

type MyModule struct{}

// Send a query to a MariaDB service and return the response
func (m *MyModule) UserList(
ctx context.Context,
// Host service
svc *dagger.Service,
) (string, error) {
return dag.Container().
From("mariadb:10.11.2").
WithServiceBinding("db", svc).
WithExec([]string{"/usr/bin/mysql", "--user=root", "--password=secret", "--host=db", "-e", "SELECT Host, User FROM mysql.user"}).
Stdout(ctx)
}

Example

Send a query to the database service listening on host port 3306 and return the result as a string:

# assumes a service is listening on host port 3306
dagger call user-list --svc=tcp://localhost:3306

Create a transient service for unit tests

The following Dagger Function creates a service and binds it to an application container for unit testing. In this example, the application being tested is Drupal. Drupal includes a large number of unit tests, including tests which depend on an active database service. This database service is created on-the-fly by the Dagger Function.

package main

import (
"context"
)

type MyModule struct{}

// Run unit tests against a database service
func (m *MyModule) Test(ctx context.Context) (string, error) {
mariadb := dag.Container().
From("mariadb:10.11.2").
WithEnvVariable("MARIADB_USER", "user").
WithEnvVariable("MARIADB_PASSWORD", "password").
WithEnvVariable("MARIADB_DATABASE", "drupal").
WithEnvVariable("MARIADB_ROOT_PASSWORD", "root").
WithExposedPort(3306).
AsService()

// get Drupal base image
// install additional dependencies
drupal := dag.Container().
From("drupal:10.0.7-php8.2-fpm").
WithExec([]string{"composer", "require", "drupal/core-dev", "--dev", "--update-with-all-dependencies"})

// add service binding for MariaDB
// run kernel tests using PHPUnit
return drupal.
WithServiceBinding("db", mariadb).
WithEnvVariable("SIMPLETEST_DB", "mysql://user:password@db/drupal").
WithEnvVariable("SYMFONY_DEPRECATIONS_HELPER", "disabled").
WithWorkdir("/opt/drupal/web/core").
WithExec([]string{"../../vendor/bin/phpunit", "-v", "--group", "KernelTests"}).
Stdout(ctx)
}

Example

Run Drupal's unit tests, instantiating a database service during the process:

dagger call test

Start and stop services

The following Dagger Function demonstrates how to control a service's lifecycle by explicitly starting and stopping a service. This example uses a Redis service.

package main

import (
"context"
)

type MyModule struct{}

// Explicitly start and stop a Redis service
func (m *MyModule) RedisService(ctx context.Context) (string, error) {
redisSrv := dag.Container().
From("redis").
WithExposedPort(6379).
AsService()

// start Redis ahead of time so it stays up for the duration of the test
redisSrv, err := redisSrv.Start(ctx)
if err != nil {
return "", err
}

// stop the service when done
defer redisSrv.Stop(ctx)

// create Redis client container
redisCLI := dag.Container().
From("redis").
WithServiceBinding("redis-srv", redisSrv)

args := []string{"redis-cli", "-h", "redis-srv"}

// set value
setter, err := redisCLI.
WithExec(append(args, "set", "foo", "abc")).
Stdout(ctx)
if err != nil {
return "", err
}

// get value
getter, err := redisCLI.
WithExec(append(args, "get", "foo")).
Stdout(ctx)
if err != nil {
return "", err
}

return setter + getter, nil
}

Example

Start and stop a Redis service:

dagger call redis-service

Create interdependent services

The following Dagger Function runs two services, service A and service B, that depend on each other. The services are set up with custom hostnames, svca and svcb, allowing each service to communicate with the other by hostname.

package main

import (
"context"
"main/internal/dagger"
)

type MyModule struct{}

// Run two services which are dependent on each other
func (m *MyModule) Services(ctx context.Context) (*dagger.Service, error) {

svcA := dag.Container().From("nginx").
WithExposedPort(80).
WithExec([]string{"sh", "-c", `
nginx & while true; do curl svcb:80 && sleep 1; done
`}).
AsService().WithHostname("svca")

_, err := svcA.Start(ctx)
if err != nil {
return nil, err
}

svcB := dag.Container().From("nginx").
WithExposedPort(80).
WithExec([]string{"sh", "-c", `
nginx & while true; do curl svca:80 && sleep 1; done
`}).
AsService().WithHostname("svcb")

svcB, err = svcB.Start(ctx)
if err != nil {
return nil, err
}

return svcB, nil
}

Example

Start two inter-dependent services:

dagger call services up --ports 8080:80

Just-in-time artifacts

Publish a container image to a private registry

The following Dagger Function publishes a just-in-time container image to a private registry.

package main

import (
"context"
"fmt"

"dagger/my-module/internal/dagger"
)

type MyModule struct{}

// Publish a container image to a private registry
func (m *MyModule) Publish(
ctx context.Context,
// Registry address
registry string,
// Registry username
username string,
// Registry password
password *dagger.Secret,
) (string, error) {
return dag.Container().
From("nginx:1.23-alpine").
WithNewFile(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
dagger.ContainerWithNewFileOpts{Permissions: 0o400},
).
WithRegistryAuth(registry, username, password).
Publish(ctx, fmt.Sprintf("%s/%s/my-nginx", registry, username))
}

Examples

  • Publish a just-in-time container image to Docker Hub, using the account username user and the password set in the PASSWORD environment variable:

    dagger call publish --registry=docker.io --username=user --password=env:PASSWORD
  • Publish a just-in-time container image to GitHub Container Registry, using the account username user and the GitHub personal access token set in the PASSWORD environment variable:

    dagger call publish --registry=ghcr.io --username=user --password=env:PASSWORD

Publish a container image to a private registry with multiple tags

The following Dagger Function tags a just-in-time container image multiple times and publishes it to a private registry.

package main

import (
"context"
"fmt"

"dagger/my-module/internal/dagger"
)

type MyModule struct{}

// Tag a container image multiple times and publish it to a private registry
func (m *MyModule) Publish(
ctx context.Context,
// Registry address
registry string,
// Registry username
username string,
// Registry password
password *dagger.Secret,
) ([]string, error) {
tags := [4]string{"latest", "1.0-alpine", "1.0", "1.0.0"}
addr := []string{}
ctr := dag.Container().
From("nginx:1.23-alpine").
WithNewFile(
"/usr/share/nginx/html/index.html",
"Hello from Dagger!",
dagger.ContainerWithNewFileOpts