Use Dagger with Multi-stage Container Builds
Multi-stage builds are a common practice when building containers with Docker.
- First, your application is compiled in a context which has tools that are required for building the application, but not necessarily required for running it.
- Next, to reduce the number of dependencies and hence the size of the image, the compiled application is copied to a different base image which only has the required components to run the application.
This guide explains how to perform multi-stage builds with the Dagger SDKs.
Requirements
This guide assumes that:
- You have a Go, Python or Node.js development environment. If not, install Go, Python or Node.js.
- You have a Dagger SDK installed for one of the above languages. If not, follow the installation instructions for the Dagger Go, Python or Node.js SDK.
- You have Docker installed and running on the host system. If not, install Docker.
- You have an application that you wish to build. This guide assumes a Go application, but you can use an application of your choice.
Example
The following code snippet demonstrates 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)
}
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)
This code listing starts by creating a Dagger client and loading the project to be built. It obtains a reference to the project and then builds the application by using the golang:latest
image to mount the source directory, sets CGO_ENABLED=
since the binary will be published on alpine
, and executes go build
.
Next, the multi-stage build is achieved by transferring the build artifact from the builder image to a runtime image based on alpine
. The steps are:
- Create a new container image which will be used as the runtime image.
- Transfer the build artifact from the builder image to the new container image.
- Set the container entrypoint to the application so that it is executed by default when the container runs.
The final optimized image can now be pushed to a registry and deployed!
Conclusion
This tutorial walked you through the process of performing a multi-stage build with Docker.
Use the API Key Concepts page and the Go, Node.js and Python SDK References to learn more about Dagger.