本文永久链接 – https://tonybai.com/2026/10/30/dagger-1-0-beta-go-ci-practice
大家好,我是Tony Bai。
【导读】
AI 编程智能体写代码越来越快,软件交付却未必同步提速。
一次修改,仍然可能经历本地测试、提交代码、等待 CI、查看日志、修复问题、再次提交。对于一个由多个智能体并行工作的团队来说,这个循环还可能被放大数十倍。
2026 年 10 月 23 日,Docker 联合创始人 Solomon Hykes 宣布 Dagger 1.0 Beta 发布,并将它定位为继 Jenkins 和 GitHub Actions 之后的第三代 CI。
这次升级值得关注的,不只是速度,而是 CI 的运行方式:同一套构建和测试流程,能否在开发者本地、CI 服务器和远程执行环境中使用同一套定义?能否复用已经计算过的结果?能否直接定位到失败的测试,而不是让开发者或 AI 智能体重新阅读一大段日志?
本文选择一个最普通的 Go 项目,从零搭建 GitHub Actions 和 Dagger 两套 CI,通过实际操作理解第三代 CI 的优势究竟在哪里。
【文章要点】
- 第一代与第二代 CI 的问题:执行环境、流水线定义和开发者本地环境容易脱节。
- Dagger 的核心变化:把流水线变成可编程、可复用、可在本地执行的依赖图。
- 动手实战:用同一个 Go 项目分别实现 GitHub Actions 和 Dagger CI。
- 验证关键亮点:本地与 CI 一致性、增量缓存、测试级反馈和失败现场调试。
- 讨论适用边界:Dagger 不会自动让所有流水线更快,也不意味着 GitHub Actions 必须立即退役。

CI 的第三代,究竟改变了什么?
先看一个熟悉的开发场景。
你在本地执行:
go test ./...
所有测试通过。提交代码后,GitHub Actions 却下载依赖、重新编译、再次运行测试。测试失败时,你还得从日志中寻找错误原因。
如果只是一个小项目,这些开销或许可以接受。但如果代码由 AI 智能体持续生成,开发者每次修复都可能触发新一轮检查,反馈延迟就会成为生产力瓶颈。
从 Jenkins 到 GitHub Actions,再到 Dagger
可以把三代 CI 的主要差异概括为:
| 维度 | 第一代:Jenkins | 第二代:GitHub Actions | 第三代:Dagger |
|---|---|---|---|
| 典型执行方式 | 持久化或自建执行节点 | 托管或自建 Runner | 本地或远程的容器化执行引擎 |
| 流水线定义 | 插件、任务、脚本等 | YAML、Actions、脚本 | 可编程模块与执行图 |
| 本地复用 | 通常需要自行组织 | 通常需要额外组织 | 直接运行同一套 Dagger 检查 |
| 缓存思路 | 依赖插件和自行配置 | 依赖缓存 Action 等机制 | 依赖执行图和内容缓存 |
| 测试反馈 | 取决于测试报告插件 | 通常先看到 Job 或 Step 状态 | 官方模块可提供测试级结果 |
| 运行环境 | 取决于节点配置 | 取决于 Runner 和工作流配置 | 可复用容器环境与执行定义 |
这张表并不意味着前两代 CI 无法实现缓存、测试报告或本地调试。Jenkins 和 GitHub Actions 都可以通过插件、脚本及其他工具实现这些能力。
Dagger 的区别在于:它试图把执行、缓存、容器环境、调度和可观测性整合进同一套模型,而不是让每个环节独立配置、再拼接起来。

Dagger 1.0 Beta 的新能力还包括官方模块(dagger module)、workspace 配置、测试级可观测性、交互式开发 Shell,以及 Dagger Cloud 的远程执行和 GitHub 原生集成。
接下来,我们不急着下结论,直接用代码验证。
准备实验:一个普通的 Go 项目
实验环境:
- macOS 或 Linux 开发机。
- Go 工具链。
- Docker Desktop 或其他受支持的容器运行环境。
- GitHub 仓库,用于运行 GitHub Actions。
- Dagger CLI 1.0 Beta。
建议先确认 Go 和 Docker 都能正常使用:
go version
docker version
安装 Dagger 时,优先使用官方安装文档。
macOS 和 Linux 用户可以使用:
curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=1.0.0-beta.21 BIN_DIR=$HOME/.local/bin sh
当然,macOS 用户也可以使用下面的命令安装:
brew install dagger/tap/dagger
确认 CLI 可用:
dagger version
version: v1.0.0-beta.21
commit: a7a2e64e
dirty: no
platform: linux/amd64
runner-host: image://registry.dagger.io/engine:v1.0.0-beta.21
创建项目
mkdir dagger-go-demo
cd dagger-go-demo
go mod init example.com/dagger-go-demo
mkdir -p mathutil .github/workflows
创建 mathutil/math.go:
package mathutil
func Add(a, b int) int {
return a + b
}
创建 mathutil/math_test.go:
package mathutil
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
if got != 5 {
t.Fatalf("Add(2, 3) = %d, want 5", got)
}
}
func TestAddNegative(t *testing.T) {
got := Add(-2, -3)
if got != -5 {
t.Fatalf("Add(-2, -3) = %d, want -5", got)
}
}
项目结构如下:
dagger-go-demo/
├── go.mod
├── mathutil/
│ ├── math.go
│ └── math_test.go
└── .github/
└── workflows/
先在本地运行:
go test ./...
go vet ./...
两条命令都应该通过。这是后续两套 CI 共用的检查基线。
基线实验:使用 GitHub Actions
创建 .github/workflows/ci.yml:
name: Go CI
on:
push:
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.27.3'
cache: true
- name: Test
run: go test ./...
- name: Vet
run: go vet ./...
这里的 1.27.3 只是示例版本,实验时应选择当前受支持且符合项目要求的 Go 版本,并在两套 CI 中保持一致。
提交并推送:
git init
git add .
git commit -m "Add Go CI baseline"
git branch -M main
git remote add origin <你的 GitHub 仓库地址>
git push -u origin main
打开 GitHub 仓库的 Actions 页面,等待流水线完成。

这套 CI 已经具备几个基本能力:
- 推送和 Pull Request 自动触发检查。
- 通过
setup-go配置 Go 环境。 - 缓存 Go 模块和构建缓存。
- 通过 Job 和 Step 的日志查看执行结果。
这不是一个糟糕的方案。对于简单的 Go 项目,它可能已经足够好。
但它有一个值得注意的边界:GitHub Actions 的工作流定义是远程执行的入口,并不会自动让本地开发环境与 CI 使用同一套执行定义。
注:目前已经有一些项目支持在本地(local)运行 GitHub Action,比如 nektos/act。
开发者仍然需要知道本地应该执行哪些命令、使用什么环境,以及如何复现 CI 中的失败。
现在看看 Dagger 怎样处理同一个问题。
实战:使用 Dagger 重建 CI
Dagger 1.0 Beta 提供官方模块,用来发现和执行项目中的检查。对于 Go 项目,我们先采用官方模块(dagger module),而不是一开始就编写自定义 SDK 模块。
初始化 Dagger workspace
在项目根目录执行:
dagger init
按照交互提示,选择适用于 Go 项目的官方模块。

# dagger init
✔ Workspace configuration initialized at ./dagger.toml
✔ Installed go.
∅ Skipped. Run later: dagger cloud checks on
dagger.io/go ➡️ https://github.com/dagger/go#v0.3.1
如果当前项目没有显示合适的推荐项,可以尝试:
dagger module recommend
dagger module search go
初始化后,检查工作区的模块配置文件 dagger.toml 和 dagger.lock:
# cat dagger.toml
[modules.go]
source = "dagger.io/go"
# cat dagger.lock
# Generated by Dagger. Pins the images and git refs this workspace uses.
# Any Dagger command may update this file; always commit it with your changes.
# Run `dagger lock update` to refresh the pinned versions.
[["version","2"]]
["","git-latest",["github.com/dagger/go",["tagPrefix","gobuild"],["version","v0.3.1"]],"refs/tags/v0.3.1"]
["","git-latest",["github.com/dagger/go",["tagPrefix","gomod"],["version","v0.3.1"]],"refs/tags/v0.3.1"]
["","git-latest",["github.com/dagger/go"],"refs/tags/v0.3.1"]
["","git-sha",["github.com/dagger/go","refs/tags/v0.3.1"],"c25c457c7567a426133d536523913988c09ffb1e"]
["","vanity-url",["https://dagger.io/go"],"https://github.com/dagger/go"]
查看 Dagger 发现了哪些检查
执行:
# dagger check -l
如果 Go 模块已经正确安装,输出中应该能看到它发现的检查项:
MODULE GO-PACKAGE GO-TEST CHECK DESCRIPTION
go * stale staleness check: run go generate in this package
go * * test Run this test.
# Use --all to expand collections and list each item.
具体发现的检查项取决于模块版本和仓库内容。
需要展开每个检查时,可以执行:
dagger check -l --all
MODULE GO-PACKAGE GO-TEST CHECK DESCRIPTION
go mathutil stale staleness check: run go generate in this package
go mathutil TestAdd test Run this test.
go mathutil TestAddNegative test Run this test.
这一步很重要:Dagger 不再只是让你手动写一个包含多条命令的 CI Job,而是尝试把项目中的包、测试和其他检查识别为可以单独选择的执行单元。
如果列表中没有测试项,先检查 Go 模块是否安装成功,以及当前模块是否识别这个项目。不要在没有确认模块发现结果的情况下,就把一次空跑当作成功的实验。
执行检查
dagger check
这个命令会运行 workspace 中启用的检查,并在失败时返回非零退出码。多个检查可以并行执行。下面是首次 check 的结果以及耗时(54.8s):
# dagger check
TRACE ✔ PASSED
dagger check 54.8s
CHECKS ✔ 2 passed
✔ dag://?check=go/packages/generate/stale 1.6s OK
✔ dag://?check=test&go-package=mathutil 52.8s OK
TESTS
✔ example.com/dagger-go-demo/mathutil 2 passed
✔ 2 passed
完成后,立即再次执行:
# dagger check
TRACE ✔ PASSED
dagger check 3.3s
CHECKS ✔ 2 passed
✔ dag://?check=go/packages/generate/stale 1.1s OK
✔ dag://?check=test&go-package=mathutil 0.9s OK
观察第二次执行的耗时和缓存相关输出,我们看到只用了 3.3s。
这就是第一个关键实验:同一套检查可以直接在开发者机器上执行,且重复执行有机会复用之前的计算结果。
注意,第一次运行可能需要拉取模块、下载基础镜像和 Go 依赖,第二次才更容易体现缓存效果。不能把首次安装开销和后续执行耗时混为一谈。
只运行一个失败的测试
在传统 CI 中,如果一个 Job 执行了几十个测试,最直接的重试方式可能是重新运行整个 Job。
Dagger 1.0 的官方 Go 模块可以把测试作为独立的检查项。例如,在模块发现 TestAdd 后,可以尝试:
dagger check --go-test=TestAdd
具体过滤参数以 dagger check -l --all 和 dagger check --help 输出为准。
如果想查看某个包下的测试,也可以使用相应的包过滤参数。
现在故意把测试中的期望值从 5 改为 6,再次运行对应检查。观察 Dagger 返回的失败信息,再把代码恢复。
# dagger check --go-test=TestAdd
TRACE ✘ FAILED
dagger check --go-test=TestAdd 2.8s
! exit code 1
! dag://?check=test&go-package=mathutil&go-test=TestAdd: Go tests in mathutil: exit code: 1
CHECKS ✘ 1 failed
✘ dag://?check=test&go-package=mathutil&go-test=TestAdd 0.9s ERROR
TESTS
✘ example.com/dagger-go-demo/mathutil FAIL
✘ example.com/dagger-go-demo/mathutil › TestAdd FAIL
math_test.go:8: Add(2, 3) = 5, want 5
✘ 1 failed
RUN LOCALLY
dagger check "dag://?check=test&go-package=mathutil&go-test=TestAdd"
改回后:
# dagger check --go-test=TestAdd
TRACE ✔ PASSED
dagger check --go-test=TestAdd 2.4s
CHECKS ✔ 1 passed
✔ dag://?check=test&go-package=mathutil&go-test=TestAdd 0.6s OK
在官方模块支持的情况下,Dagger 能显示单个测试的状态、执行时间和日志,并允许针对失败的测试再次执行。这比只知道“test 这个 Step 失败了”更有利于开发者和 AI 智能体快速定位问题。
Dagger 的优势:不仅仅是运行测试
前面的实验说明了基本用法,但真正值得关注的是这些能力如何组合起来。
一套执行定义,多处运行

传统 GitHub Actions 方案中,本地执行命令和 CI 工作流之间的对应关系需要由团队维护。Dagger 则让本地与 CI 直接调用同一套检查定义。
这并不意味着运行环境完全不需要管理。Go 版本、模块版本、系统依赖和环境变量依然需要明确配置。Dagger 的价值是让这些配置更容易被复用和追踪。
缓存不再只是一个额外步骤
GitHub Actions 可以通过 actions/cache 或 setup-go 的缓存功能复用 Go 模块和构建缓存。Dagger 也不是唯一拥有缓存的 CI 系统。
区别在于,Dagger 将缓存纳入自己的执行模型:函数的输入、依赖关系以及已有结果共同决定哪些工作需要重新计算。
概念上,可以把流水线理解为:

当输入没有发生影响结果的变化时,Dagger 有机会复用已缓存的结果;发生变化时,则重新计算受影响的部分。
不过,缓存并不保证每次都命中。输入变化、缓存失效、环境差异以及模块自身的实现,都可能影响最终效果。
从 Job 级反馈走向测试级反馈
GitHub Actions 的基础视图以 Job 和 Step 为中心。如果想获得更细粒度的测试结果,通常需要额外生成并上传测试报告,或接入其他工具。
Dagger 1.0 的官方模块则尝试直接发现测试并报告独立结果。对 AI 智能体来说,这种结构化反馈尤其有价值:它可以知道哪个测试失败、应该重跑什么,而不是从冗长的文本日志中重新推断整个执行过程。
失败后进入相同的环境
Dagger 还提供 dagger shell,用于进入 workspace 所提供的容器环境;dagger check -i 则可以在检查失败时交互式地进入对应环境:
# dagger check -i
✘ Exec failed, attaching terminal:
container(platform: "linux/amd64"): Container!
.from(address: "docker.io/library/golang:1.27-alpine@sha256:d200d2dd76b03a99b52651fd1325aff7dcd35422c35c01bac56571b0507f3deb"): Container!
.withoutEntrypoint: Container!
.withMountedCache(
┆ ┆ path: "/go/pkg/mod"
┆ ┆ cache: cacheVolume(key: "go-mod", namespace: "mod(gomodhttps://github.com/dagger/go/gomod)"): CacheVolume!
┆ ): Container!
withEnvVariable GOMODCACHE=/go/pkg/mod
.withMountedCache(
┆ ┆ path: "/root/.cache/go-build"
┆ ┆ cache: cacheVolume(key: "go-build", namespace: "mod(gomodhttps://github.com/dagger/go/gomod)"): CacheVolume!
┆ ): Container!
withEnvVariable GOCACHE=/root/.cache/go-build
withEnvVariable GOPROXY=https://proxy.golang.org|https://proxy.golang.org|direct
withWorkdir /src/dagger-go-demo
.withFile(
┆ ┆ path: "/usr/local/bin/otelgotest"
┆ ┆ source: Container.file(path: "/usr/local/bin/otelgotest"): File!
┆ ┆ permissions: 493
┆ ): Container!
.withDirectory(
┆ ┆ path: "."
┆ ┆ source: Host.directory(path: "/root/test/dagger-go-demo", include: ["**/*.go", "**/go.mod"]): Directory!
┆ ): Container!
.withDirectory(
┆ ┆ path: "."
┆ ┆ source: Host.directory(path: "/root/test/dagger-go-demo", include: ["dagger.toml", "dagger.json"]): Directory!
┆ ): Container!
withNewFile .git/HEAD (contents: "ref: refs/heads/main\n")
withWorkdir .
withExec otelgotest ./mathutil (experimentalPrivilegedNesting: true)
! exit code: 1
/src/dagger-go-demo # ls
go.mod mathutil
这解决的是一个经常被低估的问题:开发者不仅要看到错误,还要能够在尽可能相同的环境里重现错误。
需要注意,这些能力的具体行为取决于所使用的模块和执行环境。它们不是对所有外部服务状态、网络故障或非确定性测试的自动修复。
让 Dagger 进入 GitHub Actions:用官方 Action 接入自建 Engine
前面我们已经在本地 Go 项目中体验了 Dagger 的检查流程。接下来,要解决一个更实际的问题:如何让 GitHub Actions 触发 Dagger,同时把真正的执行任务交给独立的 Dagger Engine?
Dagger 为此提供了专门的 GitHub Actions 项目:dagger/dagger-for-github。与在工作流中手动安装 Dagger CLI、再执行命令相比,使用官方 Action 可以让工作流配置更清晰,也更方便统一管理 Dagger 的接入方式。
使用官方 GitHub Action
在仓库中创建或修改 .github/workflows/ci.yml,将 Dagger 接入现有 CI 流程。下面是一个简化的配置示例,展示了集成思路:
name: Go CI with Dagger
on:
push:
pull_request:
jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Run checks with Dagger
uses: dagger/dagger-for-github@v8.4.0
with:
check: "**"
# v1.0.0-beta.21:beta 版不在 dl.dagger.io 的 releases 通道,
# version: "latest" 只会装到稳定版 0.21.x(读不懂新版 dagger.toml,
# 会发现 0 个 check 并静默通过),必须用 commit 走 dev build 通道
commit: a7a2e64e853be92728f05aad9dc07bebe63c7448
用故意改错的 test 验证 GitHub Action 中 dagger check 的有效性,结果如下:

这里的关键是 dagger/dagger-for-github:它负责在 GitHub Actions 中接入 Dagger,并调用指定的 Dagger 命令,比如示例中的 check。注意,目前 dagger-for-github 等项目与 Dagger 1.0 Beta 还未完全同步,所以才有了上面 ci.yml 中的注释。
不购买 Dagger Cloud,也可以使用远程 Engine
Dagger Engine 与 Dagger Cloud 是两个不同的概念。Engine 是实际执行容器任务、调度 DAG 并管理缓存的引擎;Dagger Cloud 则提供托管执行及相关可观测性能力。
对于希望控制基础设施和成本的团队,可以按照官方的自托管文档,在自己的远程机器上运行 Dagger Engine,再让 CI 环境连接到它。比如,可以在一台拥有 Docker 环境的 Linux 服务器上启动 Engine,将这台机器作为专用的 CI 执行节点。
这种部署方式的价值不只是把计算从 GitHub Actions Runner 移出去。它还为团队提供了集中管理执行环境、复用缓存,以及隔离 CI 任务的基础。具体能否获得明显的性能收益,取决于网络、缓存命中率、并发配置和项目负载,需要通过实际运行数据验证。
自建时也要认真处理安全问题:远程 Engine 不应直接暴露给公网;应配置适当的网络访问控制和身份认证,并限制执行任务可访问的资源。不要将连接凭据直接写进仓库。
Cloud Checks:可选的补充能力
如果使用 Dagger Cloud,还可以配置 Cloud Checks,将 Dagger 检查结果与 GitHub 的提交状态和 PR 检查流程关联起来。
不过,Cloud Checks 并不是使用 Dagger 的前提。团队完全可以先通过官方 GitHub Action(就像上面的示例)将 dagger check 接入现有工作流,再根据自身对托管服务、可观测性和运维成本的需求,决定是否使用 Dagger Cloud,或者采用自建 Engine 的方案。
怎样做一次有说服力的对照实验?
不要只运行一次,然后凭终端上的时间判断谁更快。
建议至少完成以下实验,并记录实际结果,建议对Dagger感兴趣的读者在自己的环境中做一个详细的对照验证。
| 实验 | GitHub Actions 基线 | Dagger 方案 | 需要记录什么 |
|---|---|---|---|
| 首次运行 | 运行测试与 vet | 首次 dagger check |
总耗时、下载与初始化开销 |
| 原样重复 | 再次推送相同工作负载 | 再次运行 dagger check |
缓存命中情况、执行耗时 |
| 修改一个测试 | 重新运行工作流 | 选择对应测试执行 | 能否缩小反馈范围 |
| 制造测试失败 | 查看 Job / Step 日志 | 查看对应测试结果 | 定位错误所需时间 |
| 本地复现 | 手动运行 Go 命令 | 运行相同 Dagger 检查 | 环境和操作步骤的差异 |
为了让结论可信,还需要控制几个变量:
- 使用相同的 Go 版本和相同的源码。
- 分开记录首次执行与缓存预热后的执行。
- 多次运行,避免把偶然的网络抖动当成性能差异。
- 如果比较费用,记录实际 CPU、内存、执行时间和远程缓存使用情况。
- 不要把 GitHub Actions 的空闲等待时间与 Dagger 的纯执行时间直接比较。
如果实验发现 Dagger 的首次运行更慢,也不意味着实验失败。它可能说明小项目不值得承担额外的引入成本;而重复构建、复杂依赖、并行测试和多智能体工作负载,才更可能体现其架构优势。
小结:Dagger 值得替代 GitHub Actions 吗?
我的判断是:Dagger 最值得关注的,不是取代某个 CI 服务,而是让 CI 逻辑摆脱特定执行平台,成为开发流程中可复用、可观察、可增量执行的一部分。
对于简单的 Go 服务,如果现有 Jenkins或GitHub Actions 等已经运行稳定、缓存有效、测试反馈清楚,那么没有必要仅仅因为 Dagger 进入 1.0 Beta 就迁移。
但如果你正在构建 AI 软件工厂,或者需要让大量智能体反复修改、构建和验证代码,Dagger 值得认真评估。它让本地检查和远程 CI 使用同一套执行定义,也为缓存、测试级反馈、失败现场调试和远程执行提供了统一的基础。
建议从一个耗时最长、失败重试最频繁的 Go 仓库开始:保留现有 GitHub Actions 作为基线,增加 Dagger 检查,测量重复执行的实际收益,再决定是否扩大范围。
第三代 CI 的价值,最终不应该由产品名称来证明,而应该由一个更具体的指标来衡量:从代码发生变化,到开发者或 AI 智能体获得可信反馈,究竟需要多少时间和资源?
这才是 Dagger 1.0 Beta 真正值得动手验证的问题。
参考资料:
- Dagger 1.0 Beta 官方发布说明 - https://dagger.io/blog/dagger-10-beta/
- Dagger 官方文档 - https://docs.dagger.io/
- Dagger GitHub 仓库 - https://github.com/dagger/dagger
- Dagger for GitHub 仓库 - https://github.com/dagger/dagger-for-github
还在为“复制粘贴喂AI”而烦恼?我的新专栏 《AI原生开发工作流实战》 将带你:
- 告别低效,重塑开发范式
- 驾驭AI Agent(Claude Code),实现工作流自动化
- 从“AI使用者”进化为规范驱动开发的“工作流指挥家”
扫描下方二维码,开启你的AI原生开发之旅。

你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?
- 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
- 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
- 想打造生产级的Go服务,却在工程化实践中屡屡受挫?
继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!
我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。
目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。
