Skip to main content

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.

Learn more about multi-stage builds in the Docker documentation.

This guide explains how to perform multi-stage builds with the Go SDK.

Requirements

This guide assumes that:

Example

The following code snippet demonstrates a multi-stage build with the Go SDK.

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.Stdout))
if err != nil {
panic(err)
}
defer client.Close()

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

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

// Publish binary on Alpine base
prodImage := client.Container().
From("alpine")
prodImage = prodImage.WithRootfs(
prodImage.Rootfs().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)
}

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, in the highlighted section, 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, using From("alpine").
  • Transfer the build artifact from the builder image to the new container image by replacing the container image's filesystem with the original filesystem plus the build artifact.
  • 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!