Skip to main content
Version: 1.0-beta

Go

The Go module tests, builds, and runs go generate on the Go code in a workspace. It finds every Go module from its go.mod, and exposes the packages inside them as a collection keyed by directory. Each package's tests are a collection keyed by test name, so a check can run one package, one test, or everything. Each Go module runs on the Go toolchain its own go.mod asks for.

Add it to your workspace​

dagger module install dagger.io/go

Checks and generators​

AddressKindWhat it does
go/packages/tests/testCheckRuns the selected tests, one go test per package
go/packages/generateGeneratorRuns go generate in the selected packages
go/packages/generate/staleCheckFails when go generate would change a committed file
dagger check --go                     # run every Go check
dagger check --go --check=test # run every Go test
dagger generate --go # run go generate in every package

Tests run through an OpenTelemetry-aware runner, so individual Go tests appear as spans in the Dagger TUI and Dagger Cloud.

Collections​

FlagCollectionKey
--go-packagepackagesPackage directory, relative to the workspace root, such as sdk/go/cmd/dagger. The root package is .
--go-testtests, on a packageTest, fuzz, or example function name, such as TestConnect
--go-platformplatforms, on a packageGOOS/GOARCH pair, such as linux/arm64

List the keys with dagger list go-packages -a, dagger list go-tests -a, and dagger list go-platforms -a. Combine the flags to narrow a selection:

dagger check --go-package=sdk/go                          # every check in one package
dagger check --go-test=TestConnect # TestConnect in every package that has one
dagger check --go-package=sdk/go --go-test=TestConnect # one test
dagger generate --go-package=sdk/go/internal/gen # one generator

A test name in two packages is two keys, so --go-test alone selects both and --go-package picks one. Select collection items describes how to select items.

Packages​

A directory is a package key when it has work to do:

  • It has tests, and its Go module is selected by the test setting.
  • It is a main package selected by the build setting.
  • It has a //go:generate line and is selected by the generate setting.

Go modules are not a collection of their own. They decide how a package is mounted and which Go toolchain it gets.

Tests​

Test names come from the package's _test.go files: every func TestXxx(t *testing.T), func FuzzXxx(f *testing.F), and func ExampleXxx(). Listing them runs no container. TestMain, testdata directories, and nested Go modules are left out. Build constraints are not evaluated, so a test excluded by one is still listed, and selecting it runs nothing.

The selected tests of one package run together. With every test in the package selected, the check runs go test on the package. With some selected, it runs go test -run with their names. Each package runs as a separate command, never one go test ./... per Go module.

Platforms​

Every pair the Go toolchain supports is a platform key. Platforms select which binaries to cross-compile, and do not affect checks.

Module discovery​

The module finds Go modules by looking for go.mod files:

  • Every directory at or below the directory dagger runs from that holds a go.mod is a Go module. Run dagger from the workspace root to cover the whole repository, or from a subdirectory to cover only the Go modules below it.
  • When dagger runs from a directory inside a Go module, that enclosing module is included as well.
  • Every go.mod counts, including those under vendor/, testdata/, examples/, hidden directories, and directories listed in .gitignore.
  • A directory is a module when its go.mod declares a module path and the directory holds at least one Go file of its own.

Exclude a Go module from testing with the test setting, which selects module roots by path from the workspace root:

PatternSelects
"**" or "*"Every module
"services/api" or "services/api/**"The module at services/api and any module below it
"."The root module only
"!vendor"Excludes the module at vendor and any module below it

Exclusions always win, whatever the order. A list with only exclusions starts from every module, so ["!vendor", "!testdata"] tests everything except those two trees, and ["!**"] turns testing off. A module outside the selection offers no tests.

The generate setting selects directories instead, as Dagger globs from the workspace root. A literal path selects only that directory, "internal/**" selects internal and everything below it, and a ! prefix excludes.

The build setting selects main packages by directory relative to each Go module, with the same rules as test, and applies in every module: ["cmd/**", "!cmd/internal"] keeps cmd and its subdirectories except cmd/internal.

Generate code​

go/packages/generate runs go generate . in each selected package, and returns the result as a changeset to review before applying.

The selected packages of one Go module run together, in lexical path order, and share a container, so a later command can use an earlier one's output. With every package selected, that is go generate ./... for each Go module. A package generated on its own runs against the committed source of the others. Go modules run independently.

A directory can pick the container its generators run in with //go:generate:container, naming a workspace container such as generate-env, or an image reference such as docker.io/library/golang:1.26.1-alpine. Without the directive, the directory uses the module's own base container.

Build binaries​

binary is a function, not a check. It returns the binary for a main package selected by the build setting, built for the engine's platform. On the platforms collection, it cross-compiles for the selected platforms and returns one subdirectory per platform, such as linux-amd64/ and windows-arm64/. With no platform selected, it builds for the engine's platform. Cross-compiling sets GOOS and GOARCH in the native container, and cgo is off unless base brings a C cross-toolchain.

dagger -c 'go | packages | get cmd/app | binary | export ./app'
dagger -c 'go | packages | get cmd/app | platforms | subset linux/amd64 darwin/arm64 | batch | binary | export ./dist'

A binary is named the way go build names it.

Configure it​

List the current settings with dagger module settings go, then change one with dagger module settings go <key> <value>. They live in dagger.toml under [modules.go.settings]:

  • version sets one Go toolchain for every module, overriding the go directive in each module's go.mod. Without it, each module is tested, built, and generated in golang:<version>-alpine for the version its own go.mod asks for, and a module with no go directive uses 1.26.
  • base is an optional container to use instead of that image, for tests and generators that need system packages or bound services. It must carry a Go toolchain and any C/C++ dependencies the modules need, and the test runner is installed into it unless it already has one. It supplies its own toolchain, so version is ignored alongside it and the run reports the ignored value. Wire a function of your own into it with module wiring.
  • includeExtraFiles (default empty) are extra workspace-root path patterns mounted alongside each module's Go source. Go source, go.mod/go.sum/go.work, and testdata/ directories are already included automatically; use this for inputs those patterns miss, such as embedded non-Go assets, generator inputs, or fixtures kept outside testdata/.
  • test (default ["**"]) selects the module roots to test. See Module discovery for the pattern rules.
  • generate (default ["**"]) selects the directories to run go generate in, as paths and globs from the workspace root.
  • build (default ["**"]) selects the main packages to build, as paths relative to each Go module.
  • buildFlags (default empty) are extra arguments for go build only, such as ["-trimpath", "-ldflags=-s -w"]. Each list item is one argument, so a value may contain spaces. Tests and go generate do not receive them.
  • goflags sets the GOFLAGS environment variable in every Go container, which is how build tags are passed, as in -tags=extended. Flags that take a value use the -flag=value form. On the command line, put -- before the value so the CLI does not read its leading dash as flags of its own.
  • mountPath is the absolute container path where the workspace is mounted. It defaults to /src/<workspace name>, where the name is the last segment of the workspace address, usually the repository name.
# Use one Go version for the whole workspace, whatever each go.mod says
dagger module settings go version 1.26

# Pass build tags to every Go command. The value starts with a dash, so it
# goes after "--"; quoting it is not enough.
dagger module settings go goflags -- -tags=extended

List-valued settings are edited directly in dagger.toml:

[modules.go.settings]
version = "1.26"
includeExtraFiles = ["Makefile", "tools/**"]
test = ["**", "!legacy-service"] # Test all modules except legacy-service and modules below it
build = ["cmd/**"]
buildFlags = ["-trimpath"]

Working with other modules​

For Go lint checks, install golangci-lint or Staticcheck alongside this module.

When the tests need a service, such as a database, wire a container with that service bound into the base setting.

source code