- Authors
- Name
- Daggerとは?
- インストールとプロジェクト初期化
- 最初のパイプライン:Goプロジェクトのビルド
- Python SDKでパイプラインを作成
- キャッシュ戦略
- シークレット管理
- CI統合
- マルチプラットフォームビルド
- 統合パイプライン:フルCI/CD
- Dagger vs 従来のCIツール比較
- トラブルシューティング
Daggerとは?
Daggerは、CI/CDパイプラインをプログラミング言語(Go、Python、TypeScriptなど)で記述できるオープンソースツールです。コアバリューは 「Write once, run anywhere」 — ローカルのノートPCで実行したパイプラインが、GitHub Actions、GitLab CI、Jenkinsのどこでも同一に動作します。
従来のCI/CDの問題点
- YAML地獄: 複雑なパイプラインをYAMLで表現すると、可読性と再利用性が低下する
- ローカルテスト不可: CIでのみ実行可能なため、フィードバックループが長くなる
- ベンダーロックイン: GitHub ActionsとGitLab CIの文法が完全に異なる
- デバッグの困難さ: CI環境をローカルに再現することが難しい
Daggerの解決策
ローカル開発 ──→ Dagger Engine ──→ 同一の結果
GitHub Actions ──→ Dagger Engine ──→ 同一の結果
GitLab CI ──→ Dagger Engine ──→ 同一の結果
Dagger Engineはすべてのタスクをコンテナ内で実行するため、環境の差異がありません。
インストールとプロジェクト初期化
Dagger CLIのインストール
# macOS
brew install dagger/tap/dagger
# Linux
curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
# バージョン確認
dagger version
プロジェクト初期化
# Go SDKを使用
mkdir my-pipeline && cd my-pipeline
dagger init --sdk=go --name=my-pipeline
# 生成されるファイル構造
# ├── dagger.json
# ├── main.go
# └── go.mod
最初のパイプライン:Goプロジェクトのビルド
main.goの作成
package main
import (
"context"
"dagger/my-pipeline/internal/dagger"
)
type MyPipeline struct{}
// Build関数:Goプロジェクトをビルドします
func (m *MyPipeline) Build(ctx context.Context, source *dagger.Directory) *dagger.Container {
// Goビルド環境の設定
builder := dag.Container().
From("golang:1.22-alpine").
WithDirectory("/src", source).
WithWorkdir("/src").
WithEnvVariable("CGO_ENABLED", "0").
WithExec([]string{"go", "mod", "download"}).
WithExec([]string{"go", "build", "-o", "/app", "./cmd/server"})
// 軽量ランタイムイメージ
return dag.Container().
From("alpine:3.19").
WithFile("/app", builder.File("/app")).
WithEntrypoint([]string{"/app"})
}
// Test関数:テスト実行
func (m *MyPipeline) Test(ctx context.Context, source *dagger.Directory) (string, error) {
return dag.Container().
From("golang:1.22-alpine").
WithDirectory("/src", source).
WithWorkdir("/src").
WithExec([]string{"go", "mod", "download"}).
WithExec([]string{"go", "test", "-v", "-race", "./..."}).
Stdout(ctx)
}
// Lint関数:golangci-lintを実行
func (m *MyPipeline) Lint(ctx context.Context, source *dagger.Directory) (string, error) {
return dag.Container().
From("golangci/golangci-lint:v1.57").
WithDirectory("/src", source).
WithWorkdir("/src").
WithExec([]string{"golangci-lint", "run", "--timeout", "5m"}).
Stdout(ctx)
}
ローカル実行
# ビルド
dagger call build --source=.
# テスト
dagger call test --source=.
# リント
dagger call lint --source=.
# ビルド結果をDockerイメージとしてエクスポート
dagger call build --source=. export --path=./image.tar
Python SDKでパイプラインを作成
# dagger/src/main/__init__.py
import dagger
from dagger import dag, function, object_type
@object_type
class MyPipeline:
@function
async def test(self, source: dagger.Directory) -> str:
"""Pythonプロジェクトのテスト"""
return await (
dag.container()
.from_("python:3.12-slim")
.with_directory("/src", source)
.with_workdir("/src")
.with_exec(["pip", "install", "-r", "requirements.txt"])
.with_exec(["pip", "install", "pytest", "pytest-cov"])
.with_exec(["pytest", "-v", "--cov=app", "--cov-report=term-missing"])
.stdout()
)
@function
async def build(self, source: dagger.Directory) -> dagger.Container:
"""Dockerイメージのビルド"""
# 依存関係のキャッシュ
pip_cache = dag.cache_volume("pip-cache")
return (
dag.container()
.from_("python:3.12-slim")
.with_mounted_cache("/root/.cache/pip", pip_cache)
.with_directory("/app", source)
.with_workdir("/app")
.with_exec(["pip", "install", "--no-cache-dir", "-r", "requirements.txt"])
.with_entrypoint(["python", "main.py"])
)
@function
async def publish(
self,
source: dagger.Directory,
registry: str = "ghcr.io",
image_name: str = "my-app",
tag: str = "latest",
registry_user: dagger.Secret | None = None,
registry_pass: dagger.Secret | None = None,
) -> str:
"""イメージのビルドとレジストリへのプッシュ"""
container = await self.build(source)
if registry_user and registry_pass:
container = container.with_registry_auth(
registry,
await registry_user.plaintext(),
registry_pass,
)
ref = f"{registry}/{image_name}:{tag}"
digest = await container.publish(ref)
return digest
キャッシュ戦略
Daggerのキャッシュはビルド速度を劇的に改善します:
func (m *MyPipeline) BuildWithCache(ctx context.Context, source *dagger.Directory) *dagger.Container {
// Goモジュールキャッシュ
goModCache := dag.CacheVolume("go-mod-cache")
goBuildCache := dag.CacheVolume("go-build-cache")
return dag.Container().
From("golang:1.22-alpine").
WithMountedCache("/go/pkg/mod", goModCache).
WithMountedCache("/root/.cache/go-build", goBuildCache).
WithDirectory("/src", source).
WithWorkdir("/src").
WithExec([]string{"go", "build", "-o", "/app", "./cmd/server"})
}
キャッシュ効果の比較
初回ビルド: 2分30秒(依存関係のダウンロードを含む)
2回目のビルド: 15秒(キャッシュヒット)
ソースのみ変更: 20秒(依存関係キャッシュを再利用)
シークレット管理
func (m *MyPipeline) Deploy(
ctx context.Context,
source *dagger.Directory,
kubeconfig *dagger.Secret,
registryToken *dagger.Secret,
) (string, error) {
// シークレットはログに露出されない
return dag.Container().
From("bitnami/kubectl:latest").
WithMountedSecret("/root/.kube/config", kubeconfig).
WithSecretVariable("REGISTRY_TOKEN", registryToken).
WithDirectory("/manifests", source.Directory("k8s")).
WithExec([]string{"kubectl", "apply", "-f", "/manifests/"}).
Stdout(ctx)
}
# ローカル実行時のシークレット受け渡し
dagger call deploy \
--source=. \
--kubeconfig=file:$HOME/.kube/config \
--registry-token=env:REGISTRY_TOKEN
CI統合
GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dagger/dagger-for-github@v6
with:
version: 'latest'
verb: call
args: test --source=.
- uses: dagger/dagger-for-github@v6
with:
version: 'latest'
verb: call
args: lint --source=.
- uses: dagger/dagger-for-github@v6
with:
version: 'latest'
verb: call
args: build --source=.
GitLab CI
# .gitlab-ci.yml
stages:
- test
- build
test:
stage: test
image: registry.dagger.io/engine:latest
services:
- docker:dind
script:
- dagger call test --source=.
build:
stage: build
image: registry.dagger.io/engine:latest
services:
- docker:dind
script:
- dagger call build --source=. export --path=image.tar
artifacts:
paths:
- image.tar
マルチプラットフォームビルド
func (m *MyPipeline) BuildMultiPlatform(
ctx context.Context,
source *dagger.Directory,
) (string, error) {
platforms := []dagger.Platform{
"linux/amd64",
"linux/arm64",
}
platformVariants := make([]*dagger.Container, len(platforms))
for i, platform := range platforms {
platformVariants[i] = dag.Container(dagger.ContainerOpts{Platform: platform}).
From("golang:1.22-alpine").
WithDirectory("/src", source).
WithWorkdir("/src").
WithEnvVariable("CGO_ENABLED", "0").
WithExec([]string{"go", "build", "-o", "/app", "./cmd/server"})
}
// マルチプラットフォームイメージのプッシュ
digest, err := dag.Container().
Publish(ctx, "ghcr.io/my-org/my-app:latest",
dagger.ContainerPublishOpts{
PlatformVariants: platformVariants,
})
return digest, err
}
統合パイプライン:フルCI/CD
func (m *MyPipeline) CI(ctx context.Context, source *dagger.Directory) error {
// 1. リント
_, err := m.Lint(ctx, source)
if err != nil {
return fmt.Errorf("lint failed: %w", err)
}
fmt.Println("✅ Lint passed")
// 2. テスト
_, err = m.Test(ctx, source)
if err != nil {
return fmt.Errorf("test failed: %w", err)
}
fmt.Println("✅ Tests passed")
// 3. ビルド
container := m.Build(ctx, source)
_, err = container.Sync(ctx)
if err != nil {
return fmt.Errorf("build failed: %w", err)
}
fmt.Println("✅ Build succeeded")
return nil
}
Dagger vs 従来のCIツール比較
| 特性 | GitHub Actions | GitLab CI | Dagger |
|---|---|---|---|
| パイプライン定義 | YAML | YAML | Go/Python/TS |
| ローカル実行 | act(制限あり) | 不可 | 完全サポート |
| デバッグ | 困難 | 困難 | IDEデバッガを使用可能 |
| ベンダーロックイン | GitHub | GitLab | なし |
| キャッシュ | 手動設定 | 手動設定 | 自動(コンテンツベース) |
| 再利用性 | マーケットプレイス | テンプレート | パッケージ/モジュール |
トラブルシューティング
よくある問題と解決方法
# Dagger Engineの状態確認
docker ps | grep dagger-engine
# キャッシュの初期化
dagger query <<< '{ defaultPlatform }'
# 詳細ログ出力
dagger call --debug test --source=.
# 特定のステップでシェルにアクセス(デバッグ用)
dagger call build --source=. terminal
確認クイズ(5問)
Q1. 従来のCI/CDツールに対するDaggerの最大の利点は?
ローカルとCI環境で同一のパイプラインを実行でき、プログラミング言語でパイプラインを記述するため、IDEサポート、型安全性、デバッグが可能です。
Q2. Daggerでキャッシュを実装する方法は?
dag.CacheVolume()でキャッシュボリュームを作成し、WithMountedCache()でコンテナにマウントします。コンテンツベースで自動キャッシュされます。
Q3. Daggerでシークレットを安全に受け渡す方法は?
関数パラメータとしてdagger.Secret型を受け取り、WithMountedSecretやWithSecretVariableでコンテナに渡します。ログには露出されません。
Q4. GitHub ActionsでDaggerを使用するにはどのアクションを使いますか?
dagger/dagger-for-github@v6アクションを使用します。
Q5. Daggerでマルチプラットフォームビルドを行うにはどのオプションを使いますか?
dagger.ContainerOptsのPlatformフィールドとPublishのPlatformVariantsオプションを使用します。