Cookbook
Filesystem
List host directory contents
The following code listing obtains a reference to the host working directory and lists the directory's contents.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
connect(
async (client: Client) => {
const entries = await client.host().directory(".").entries()
console.log(entries)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
entries = await client.host().directory(".").entries()
print(entries)
anyio.run(main)
Get host directory with filters
The following code listing obtains a reference to the host working directory containing all files except *.txt
files.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
import * as fs from "fs"
const files = ["foo.txt", "bar.txt", "baz.rar"]
let count = 1
for (const file of files) {
fs.writeFileSync(file, count.toString())
count = count + 1
}
connect(
async (client: Client) => {
const entries = await client
.host()
.directory(".", { exclude: ["*.txt"] })
.entries()
console.log(entries)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
for i, file in enumerate(["foo.txt", "bar.txt", "baz.rar"]):
await (anyio.Path(".") / file).write_text(str(i + 1))
cfg = dagger.Config(log_output=sys.stderr)
async with dagger.Connection(cfg) as client:
entries = await client.host().directory(".", exclude=["*.txt"]).entries()
print(entries)
anyio.run(main)
The following code listing obtains a reference to the host working directory containing only *.rar
files.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
import * as fs from "fs"
const files = ["foo.txt", "bar.txt", "baz.rar"]
let count = 1
for (const file of files) {
fs.writeFileSync(file, count.toString())
count = count + 1
}
connect(
async (client: Client) => {
const entries = await client
.host()
.directory(".", { include: ["*.rar"] })
.entries()
console.log(entries)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
for i, file in enumerate(["foo.txt", "bar.txt", "baz.rar"]):
await (anyio.Path(".") / file).write_text(str(i + 1))
cfg = dagger.Config(log_output=sys.stderr)
async with dagger.Connection(cfg) as client:
entries = await client.host().directory(".", include=["*.rar"]).entries()
print(entries)
anyio.run(main)
The following code listing obtains a reference to the host working directory containing all files except *.rar
files.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
import * as fs from "fs"
const files = ["foo.txt", "bar.txt", "baz.rar"]
let count = 1
for (const file of files) {
fs.writeFileSync(file, count.toString())
count = count + 1
}
connect(
async (client: Client) => {
const entries = await client
.host()
.directory(".", { include: ["*.*"], exclude: ["*.rar"] })
.entries()
console.log(entries)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
for i, file in enumerate(["foo.txt", "bar.txt", "baz.rar"]):
await (anyio.Path(".") / file).write_text(str(i + 1))
cfg = dagger.Config(log_output=sys.stderr)
async with dagger.Connection(cfg) as client:
entries = (
await client.host()
.directory(".", exclude=["*.rar"], include=["*.*"])
.entries()
)
print(entries)
anyio.run(main)
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.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
connect(
async (client: Client) => {
const contents = await client
.container()
.from("alpine:latest")
.withDirectory("/host", client.host().directory("."))
.withExec(["ls", "/host"])
.stdout()
console.log(contents)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
out = await (
client.container()
.from_("alpine:latest")
.with_directory("/host", client.host().directory("."))
.with_exec(["ls", "/host"])
.stdout()
)
print(out)
anyio.run(main)
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:
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.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
connect(
async (client: Client) => {
const contents = await client
.container()
.from("alpine:latest")
.withDirectory("/host", client.host().directory("/tmp/sandbox"))
.withExec(["/bin/sh", "-c", `echo foo > /host/bar`])
.directory("/host")
.export("/tmp/sandbox")
console.log(contents)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
out = await (
client.container()
.from_("alpine:latest")
.with_directory("/host", client.host().directory("/tmp/sandbox"))
.with_exec(["/bin/sh", "-c", "`echo foo > /host/bar`"])
.directory("/host")
.export("/tmp/sandbox")
)
print(out)
anyio.run(main)
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.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
// create Dagger client
connect(
async (client) => {
// get repository at specified branch
const project = client
.git("https://github.com/dagger/dagger")
.branch("main")
.tree()
// return container with repository
// at /src path
const contents = await client
.container()
.from("alpine:latest")
.withDirectory("/src", project)
.withWorkdir("/src")
.withExec(["ls", "/src"])
.stdout()
console.log(contents)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# get repository at specified branch
project = client.git("https://github.com/dagger/dagger").branch("main").tree()
# return container with repository
# at /src path
out = await (
client.container()
.from_("alpine:latest")
.with_directory("/src", project)
.with_workdir("/src")
.with_exec(["ls", "/src"])
.stdout()
)
print(out)
anyio.run(main)
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.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
// create Dagger client
connect(
async (client) => {
// get repository at specified branch
const project = client
.git("https://github.com/dagger/dagger")
.branch("main")
.tree()
// return container with repository
// at /src path
// excluding *.md files
const contents = await client
.container()
.from("alpine:latest")
.withDirectory("/src", project, { exclude: ["*.md"] })
.withWorkdir("/src")
.withExec(["ls", "/src"])
.stdout()
console.log(contents)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# 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
out = await (
client.container()
.from_("alpine:latest")
.with_directory("/src", project, exclude=["*.md"])
.with_workdir("/src")
.with_exec(["ls", "/src"])
.stdout()
)
print(out)
anyio.run(main)
The following code listing adds a remote Git repository branch as a directory at the /src
container path, including only *.md
files.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
// create Dagger client
connect(
async (client) => {
// get repository at specified branch
const project = client
.git("https://github.com/dagger/dagger")
.branch("main")
.tree()
// return container with repository
// at /src path
// including only *.md files
const contents = await client
.container()
.from("alpine:latest")
.withDirectory("/src", project, { include: ["*.md"] })
.withWorkdir("/src")
.withExec(["ls", "/src"])
.stdout()
console.log(contents)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# 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
out = await (
client.container()
.from_("alpine:latest")
.with_directory("/src", project, include=["*.md"])
.with_workdir("/src")
.with_exec(["ls", "/src"])
.stdout()
)
print(out)
anyio.run(main)
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
.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
// create Dagger client
connect(
async (client) => {
// get repository at specified branch
const 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
const contents = await client
.container()
.from("alpine:latest")
.withDirectory("/src", project, {
include: ["*.md"],
exclude: ["README.md"],
})
.withWorkdir("/src")
.withExec(["ls", "/src"])
.stdout()
console.log(contents)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# 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
out = await (
client.container()
.from_("alpine:latest")
.with_directory("/src", project, include=["*.md"], exclude=["README.md"])
.with_workdir("/src")
.with_exec(["ls", "/src"])
.stdout()
)
print(out)
anyio.run(main)
Builds
Perform multi-stage build
The following code listing performs a multi-stage build.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
connect(
async (client: Client) => {
// get host directory
const project = client.host().directory(".")
// build app
const builder = client
.container()
.from("golang:latest")
.withDirectory("/src", project)
.withWorkdir("/src")
.withEnvVariable("CGO_ENABLED", "0")
.withExec(["go", "build", "-o", "myapp"])
// publish binary on alpine base
const prod = client
.container()
.from("alpine")
.withFile("/bin/myapp", builder.file("/src/myapp"))
.withEntrypoint(["/bin/myapp"])
const addr = await prod.publish("localhost:5000/multistage")
console.log(addr)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# get host directory
project = client.host().directory(".")
# build app
builder = (
client.container()
.from_("golang:latest")
.with_directory("/src", project)
.with_workdir("/src")
.with_env_variable("CGO_ENABLED", "0")
.with_exec(["go", "build", "-o", "myapp"])
)
# publish binary on alpine base
prod = (
client.container()
.from_("alpine")
.with_file("/bin/myapp", builder.file("/src/myapp"))
.with_entrypoint(["/bin/myapp"])
)
addr = await prod.publish("localhost:5000/multistage")
print(addr)
anyio.run(main)
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")
}
}
import { connect } from "@dagger.io/dagger"
// Create a multi-build pipeline for a Go application.
// define build matrix
const oses = ["linux", "darwin"]
const arches = ["amd64", "arm64"]
// initialize dagger client
connect(
async (client) => {
console.log("Building with Dagger")
// get reference to the local project
const src = client.host().directory(".")
// create empty directory to put build outputs
var outputs = client.directory()
const golang = client
.container()
// get golang image
.from("golang:latest")
// mount source code into golang image
.withDirectory("/src", src)
.withWorkdir("/src")
for (const os of oses) {
for (const arch of arches) {
// create a directory for each OS and architecture
const path = `build/${os}/${arch}/`
const build = golang
// set GOARCH and GOOS in the build environment
.withEnvVariable("GOOS", os)
.withEnvVariable("GOARCH", arch)
.withExec(["go", "build", "-o", path])
// add build to outputs
outputs = outputs.withDirectory(path, build.directory(path))
}
}
// write build artifacts to host
await outputs.export(".")
},
{ LogOutput: process.stderr }
)
"""Create a multi-build pipeline for a Go application."""
import itertools
import sys
import anyio
import dagger
async def main():
print("Building with Dagger")
# define build matrix
oses = ["linux", "darwin"]
arches = ["amd64", "arm64"]
# initialize dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# get reference to the local project
src = client.host().directory(".")
# create empty directory to put build outputs
outputs = client.directory()
golang = (
# get `golang` image
client.container()
.from_("golang:latest")
# mount source code into `golang` image
.with_directory("/src", src)
.with_workdir("/src")
)
for goos, goarch in itertools.product(oses, arches):
# create a directory for each OS and architecture
path = f"build/{goos}/{goarch}/"
build = (
golang
# set GOARCH and GOOS in the build environment
.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))
# write build artifacts to host
await outputs.export(".")
anyio.run(main)
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)
}
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)
}
Build image from Dockerfile
The following code listing builds an image from a Dockerfile in the current working directory on the host.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
connect(
async (client) => {
// set build context
const contextDir = client.host().directory(".")
// build using Dockerfile
// publish the resulting container to a registry
const imageRef = await contextDir
.dockerBuild()
.publish("ttl.sh/hello-dagger-" + Math.floor(Math.random() * 10000000))
console.log(`Published image to: ${imageRef}`)
},
{ LogOutput: process.stderr }
)
import random
import sys
import anyio
import dagger
async def main():
config = dagger.Config(log_output=sys.stdout)
async with dagger.Connection(config) as client:
# set build context
context_dir = client.host().directory(".")
# build using Dockerfile
# publish the resulting container to a registry
image_ref = await context_dir.docker_build().publish(
f"ttl.sh/hello-dagger-{random.randrange(10 ** 8)}"
)
print(f"Published image to: {image_ref}")
anyio.run(main)
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.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
connect(
async (client) => {
// get build context directory
const contextDir = client.host().directory("/projects/myapp")
// get Dockerfile in different filesystem location
const dockerfilePath = "/data/myapp/custom.Dockerfile"
const dockerfile = client.host().file(dockerfilePath)
// add Dockerfile to build context directory
const workspace = contextDir.withFile("custom.Dockerfile", dockerfile)
// build using Dockerfile
// publish the resulting container to a registry
const imageRef = await client
.container()
.build(workspace, { dockerfile: "custom.Dockerfile" })
.publish("ttl.sh/hello-dagger-" + Math.floor(Math.random() * 10000000))
console.log(`Published image to: ${imageRef}`)
},
{ LogOutput: process.stderr }
)
import random
import sys
import anyio
import dagger
async def main():
config = dagger.Config(log_output=sys.stdout)
async with dagger.Connection(config) as client:
# get build context directory
context_dir = client.host().directory("/projects/myapp")
# get Dockerfile in different filesystem location
dockerfile_path = "/data/myapp/custom.Dockerfile"
dockerfile = client.host().file(dockerfile_path)
# add Dockerfile to build context directory
workspace = context_dir.with_file("custom.Dockerfile", dockerfile)
# build using Dockerfile
# publish the resulting container to a registry
image_ref = await (
client.container()
.build(context=workspace, dockerfile="custom.Dockerfile")
.publish(f"ttl.sh/hello-dagger-{random.SystemRandom().randint(1,10000000)}")
)
print(f"Published image to: {image_ref}")
anyio.run(main)
Add OCI annotations to image
The following code listing adds OpenContainer Initiative (OCI) annotations to an image.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
// create Dagger client
connect(
async (client) => {
// create and publish image with annotations
const container = client
.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")
const addr = await container.publish("ttl.sh/my-alpine")
console.log(addr)
},
{ LogOutput: process.stderr }
)
import sys
from datetime import datetime, timezone
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# publish app on alpine base
addr = await (
client.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")
)
print(addr)
anyio.run(main)
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")
}
}
import { connect } from "@dagger.io/dagger"
// Create a multi-build pipeline for a Go application.
// define build matrix
const oses = ["linux", "darwin"]
const arches = ["amd64", "arm64"]
// initialize dagger client
connect(
async (client) => {
console.log("Building with Dagger")
// get reference to the local project
const src = client.host().directory(".")
// create empty directory to put build outputs
var outputs = client.directory()
const golang = client
.container()
// get golang image
.from("golang:latest")
// mount source code into golang image
.withDirectory("/src", src)
.withWorkdir("/src")
for (const os of oses) {
for (const arch of arches) {
// create a directory for each OS and architecture
const path = `build/${os}/${arch}/`
const build = golang
// set GOARCH and GOOS in the build environment
.withEnvVariable("GOOS", os)
.withEnvVariable("GOARCH", arch)
.withExec(["go", "build", "-o", path])
// add build to outputs
outputs = outputs.withDirectory(path, build.directory(path))
}
}
// write build artifacts to host
await outputs.export(".")
},
{ LogOutput: process.stderr }
)
"""Create a multi-build pipeline for a Go application."""
import itertools
import sys
import anyio
import dagger
async def main():
print("Building with Dagger")
# define build matrix
oses = ["linux", "darwin"]
arches = ["amd64", "arm64"]
# initialize dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# get reference to the local project
src = client.host().directory(".")
# create empty directory to put build outputs
outputs = client.directory()
golang = (
# get `golang` image
client.container()
.from_("golang:latest")
# mount source code into `golang` image
.with_directory("/src", src)
.with_workdir("/src")
)
for goos, goarch in itertools.product(oses, arches):
# create a directory for each OS and architecture
path = f"build/{goos}/{goarch}/"
build = (
golang
# set GOARCH and GOOS in the build environment
.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))
# write build artifacts to host
await outputs.export(".")
anyio.run(main)
Access private Git repository
The following code listing demonstrates how to access a private Git repository using SSH.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
import process from "process"
// initialize Dagger client
connect(async (client: Client) => {
// Collect value of SSH_AUTH_SOCK env var, to retrieve authentication socket path
const sshAuthSockPath = process.env.SSH_AUTH_SOCK?.toString() || ""
// Retrieve authentication socket ID from host
const sshAgentSocketID = await client.host().unixSocket(sshAuthSockPath).id()
const repo = client
// Retrieve the repository
.git("git@private-repository.git")
// Select the main branch, and the filesystem tree associated
.branch("main")
.tree({
sshAuthSocket: sshAgentSocketID,
})
// Select the README.md file
.file("README.md")
// Retrieve the content of the README file
const file = await repo.contents()
console.log(file)
})
"""Clone a Private Git Repository and print the content of the README.md file."""
import os
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# Collect value of SSH_AUTH_SOCK env var, to retrieve auth socket path
ssh_auth_path = os.environ.get("SSH_AUTH_SOCK", "")
# Retrieve authentication socket from host
ssh_agent_socket = client.host().unix_socket(ssh_auth_path)
repo = (
client
# Retrieve the repository
.git("git@private-repository.git")
# Select the main branch, and the filesystem tree associated
.branch("main").tree(ssh_auth_socket=ssh_agent_socket)
# Select the README.md file
.file("README.md")
)
# Retrieve the content of the README file
file = await repo.contents()
print(file)
anyio.run(main)
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.
This is a temporary workaround until cache invalidation support is officially added to Dagger.
Changes in mounted cache volumes do not invalidate the Dagger pipeline operations cache.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
// create Dagger client
connect(
async (client) => {
// invalidate cache to force execution
// of second withExec() operation
const output = await client
.pipeline("test")
.container()
.from("alpine")
.withExec(["apk", "add", "curl"])
.withEnvVariable("CACHEBUSTER", Date.now().toString())
.withExec(["apk", "add", "zip"])
.stdout()
console.log(output)
},
{ LogOutput: process.stderr }
)
import sys
from datetime import datetime
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# invalidate cache to force execution
# of second with_exec() operation
output = await (
client.pipeline("test")
.container()
.from_("alpine")
.with_exec(["apk", "add", "curl"])
.with_env_variable("CACHEBUSTER", str(datetime.now()))
.with_exec(["apk", "add", "zip"])
.stdout()
)
print(output)
anyio.run(main)
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.
- Go
- Node.js
- Python
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))
}
import { connect, Client } from "@dagger.io/dagger"
import fetch from "node-fetch"
connect(
async (client: Client) => {
// create HTTP service container with exposed port 8080
const httpSrv = client
.container()
.from("python")
.withDirectory(
"/srv",
client.directory().withNewFile("index.html", "Hello, world!")
)
.withWorkdir("/srv")
.withExec(["python", "-m", "http.server", "8080"])
.withExposedPort(8080)
.asService()
// expose HTTP service to host
const tunnel = await client.host().tunnel(httpSrv).start()
// get HTTP service address
const srvAddr = await tunnel.endpoint()
// access HTTP service from host
// print response
await fetch("http://" + srvAddr)
.then((res) => res.text())
.then((body) => console.log(body))
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import httpx
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# create HTTP service container with exposed port 8080
http_srv = (
client.container()
.from_("python")
.with_directory(
"/srv",
client.directory().with_new_file("index.html", "Hello, world!"),
)
.with_workdir("/srv")
.with_exec(["python", "-m", "http.server", "8080"])
.with_exposed_port(8080)
.as_service()
)
# expose HTTP service to host
tunnel = await client.host().tunnel(http_srv).start()
# get HTTP service address
endpoint = await tunnel.endpoint()
# access HTTP service from host
async with httpx.AsyncClient() as http:
r = await http.get(f"http://{endpoint}")
print(r.status_code)
print(r.text)
anyio.run(main)
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.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
connect(
async (client: Client) => {
// expose host service on port 3306
const hostSrv = client.host().service([{ frontend: 3306, backend: 3306 }])
// create MariaDB container
// with host service binding
// execute SQL query on host service
const out = await client
.container()
.from("mariadb:10.11.2")
.withServiceBinding("db", hostSrv)
.withExec([
"/bin/sh",
"-c",
"/usr/bin/mysql --user=root --password=secret --host=db -e 'SELECT * FROM mysql.user'",
])
.stdout()
console.log(out)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# expose host service on port 3306
host_srv = client.host().service(
[
dagger.PortForward(
backend=3306, frontend=3306, protocol=dagger.NetworkProtocol.TCP
)
]
)
# create MariaDB container
# with host service binding
# execute SQL query on host service
out = await (
client.container()
.from_("mariadb:10.11.2")
.with_service_binding("db", host_srv)
.with_exec(
[
"/bin/sh",
"-c",
"/usr/bin/mysql --user=root --password=secret --host=db -e 'SELECT * FROM mysql.user'",
]
)
.stdout()
)
print(out)
anyio.run(main)
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.
- Go
- Node.js
- Python
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)
}
import { connect, Client } from "@dagger.io/dagger"
connect(
async (client: Client) => {
// get MariaDB base image
const 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
const drupal = client
.container()
.from("drupal:10.0.7-php8.2-fpm")
.withExec([
"composer",
"require",
"drupal/core-dev",
"--dev",
"--update-with-all-dependencies",
])
// add service binding for MariaDB
// run unit tests using PHPUnit
const test = await drupal
.withServiceBinding("db", mariadb)
.withEnvVariable("SIMPLETEST_DB", "mysql://user:password@db/drupal")
.withEnvVariable("SYMFONY_DEPRECATIONS_HELPER", "disabled")
.withWorkdir("/opt/drupal/web/core")
.withExec(["../../vendor/bin/phpunit", "-v", "--group", "KernelTests"])
.stdout()
// print ref
console.log(test)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# get MariaDB base image
mariadb = (
client.container()
.from_("mariadb:10.11.2")
.with_env_variable("MARIADB_USER", "user")
.with_env_variable("MARIADB_PASSWORD", "password")
.with_env_variable("MARIADB_DATABASE", "drupal")
.with_env_variable("MARIADB_ROOT_PASSWORD", "root")
.with_exposed_port(3306)
.as_service()
)
# get Drupal base image
# install additional dependencies
drupal = (
client.container()
.from_("drupal:10.0.7-php8.2-fpm")
.with_exec(
[
"composer",
"require",
"drupal/core-dev",
"--dev",
"--update-with-all-dependencies",
]
)
)
# add service binding for MariaDB
# run unit tests using PHPUnit
test = await (
drupal.with_service_binding("db", mariadb)
.with_env_variable("SIMPLETEST_DB", "mysql://user:password@db/drupal")
.with_env_variable("SYMFONY_DEPRECATIONS_HELPER", "disabled")
.with_workdir("/opt/drupal/web/core")
.with_exec(["../../vendor/bin/phpunit", "-v", "--group", "KernelTests"])
.stdout()
)
print(test)
anyio.run(main)
Start and stop services
The following code listing demonstrates explicitly starting a Docker daemon for use in a test suite.
- Go
- Node.js
- Python
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
}
import { connect, Client } from "@dagger.io/dagger"
connect(
async (client: Client) => {
const dockerd = await client
.container()
.from("docker:dind")
.asService()
.start()
// 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:
const test = await client
.container()
.from("golang")
.withServiceBinding("docker", dockerd)
.withEnvVariable("DOCKER_HOST", "tcp://docker:2375")
.withExec(["go", "test", "./..."])
.sync()
console.log("test: ", test)
// 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.
const dockerHost = await dockerd.endpoint({ scheme: "tcp" })
const testWithEndpoint = await client
.container()
.from("golang")
.withEnvVariable("DOCKER_HOST", dockerHost)
.withExec(["go", "test", "./..."])
.sync()
console.log("testWithEndpoint: ", testWithEndpoint)
// service.stop() is available to explicitly stop the service if needed
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
# create Dagger client
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
dockerd = await client.container().from_("docker:dind").as_service().start()
# dockerd is now running, and will stay running
# so you don't have to worry about it restarting after a 10 second gap
test = await (
client.container()
.from_("golang")
.with_service_binding("docker", dockerd)
.with_env_variable("DOCKER_HOST", "tcp://docker:2375")
.with_exec(["go", "test", "./..."])
.sync()
)
print("test: " + test)
# or, if you prefer
# trust `endpoint()` to construct the address
#
# note that this has the exact same non-cache-busting semantics as with_service_binding,
# since hostnames are stable and content-addressed
#
# this could be part of the global test suite setup.
docker_host = await dockerd.endpoint(scheme="tcp")
test_with_endpoint = await (
client.container()
.from_("golang")
.with_env_variable("DOCKER_HOST", docker_host)
.with_exec(["go", "test", "./..."])
.sync()
)
print("test_with_endpoint: " + test_with_endpoint)
# service.stop() is available to explicitly stop the service if needed
anyio.run(main)
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.
- Go
- Node.js
- Python
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)
}
import { connect } from "@dagger.io/dagger"
// initialize Dagger client
connect(
async (client) => {
// set secret as string value
const secret = client.setSecret("password", "DOCKER-HUB-PASSWORD")
// create container
const c = client
.container()
.from("nginx:1.23-alpine")
.withNewFile("/usr/share/nginx/html/index.html", {
contents: "Hello from Dagger!",
permissions: 0o400,
})
// use secret for registry authentication
const addr = await c
.withRegistryAuth("docker.io", "DOCKER-HUB-USERNAME", secret)
.publish("DOCKER-HUB-USERNAME/my-nginx")
// print result
console.log(`Published at: ${addr}`)
},
{ LogOutput: process.stderr }
)
import sys
import anyio
import dagger
async def main():
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
# set secret as string value
secret = client.set_secret("password", "DOCKER-HUB-PASSWORD")
# create container
ctr = (
client.container(platform=dagger.Platform("linux/amd64"))
.from_("nginx:1.23-alpine")
.with_new_file(
"/usr/share/nginx/html/index.html",
contents="Hello from Dagger!",
permissions=0o400,
)
)
# use secret for registry authentication
addr = await ctr.with_registry_auth(
"docker.io", "DOCKER-HUB-USERNAME", secret
).publish("DOCKER-HUB-USERNAME/my-nginx")
print(f"Published at: {addr}")
anyio.run(main)
Export image to host
The following code listing exports a container image from a Dagger pipeline to the host.
- Go
- Node.js
- Python
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.