Dagger란?
Dagger는 CI/CD 파이프라인을 프로그래밍 언어(Go, Python, TypeScript 등)로 작성할 수 있게 해주는 오픈소스 도구입니다. 핵심 가치는 **"Write once, run anywhere"** — 로컬 노트북에서 실행한 파이프라인이 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
"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
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초 (의존성 다운로드 포함)
두 번째 빌드: 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
**Q1. Dagger가 기존 CI/CD 도구 대비 가장 큰 장점은?**
로컬과 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 옵션을 사용합니다.
현재 단락 (1/257)
Dagger는 CI/CD 파이프라인을 프로그래밍 언어(Go, Python, TypeScript 등)로 작성할 수 있게 해주는 오픈소스 도구입니다. 핵심 가치는 **"Write on...