DEV Community

daniel jeong
daniel jeong

Posted on • Originally published at manoit.co.kr

Complete Guide to Dagger v0.20 — Beyond YAML Pipelines with CI/CD as Code, Dang Scripting, and Production Deployment

Complete Guide to Dagger v0.20 — Beyond YAML Pipelines with CI/CD as Code: Dang Scripting, Daggerverse Modules, and Production Deployment Strategies

In 2026, CI/CD pipelines are still trapped in YAML hell. GitHub Actions' .github/workflows/*.yml, GitLab CI's .gitlab-ci.yml, Jenkins' Jenkinsfile — different syntax per platform, no local testing, and vendor lock-in. Dagger, created by Docker founder Solomon Hykes, is a container-native automation engine that lets you write CI/CD pipelines in programming languages and run them identically anywhere inside containers.

v0.20, released in February 2026, introduced the dedicated Dang scripting language, a completely redesigned terminal UI, and a logs-based progress mode. In OpenMeter's real-world case, a pipeline that took 25 minutes on GitHub Actions alone was reduced to 5 minutes (5x faster, 50% cost reduction) with Dagger Cloud caching. This guide covers Dagger's architecture, SDK ecosystem, Daggerverse modules, CI integration patterns, Kubernetes production deployment, and comparison with existing CI/CD tools.

Dagger Architecture — DAG-Based Container Execution Engine

Core Structure: Engine → GraphQL API → SDK

Understanding Dagger's architecture requires distinguishing three layers. The Dagger Engine is a core runtime combining an execution engine, universal type system, data layer, and module system. It runs on any OCI-compatible system. Each language SDK (Go, Python, TypeScript, etc.) doesn't execute pipelines directly — instead, it sends pipeline definitions to the Dagger GraphQL API, which triggers the Engine.

┌─────────────────────────────────────────────────────────────┐
│  Your Code (Go / Python / TypeScript / Dang)                │
│  └── SDK: Converts pipeline definitions to GraphQL requests │
├─────────────────────────────────────────────────────────────┤
│  Dagger GraphQL API                                         │
│  └── Parses pipeline definitions into a DAG                 │
├─────────────────────────────────────────────────────────────┤
│  Dagger Engine                                              │
│  ├── Executes DAG operations concurrently                   │
│  ├── Intermediate artifacts: JIT build + incremental cache  │
│  ├── Container sandbox: all ops run inside containers       │
│  └── Auto-generates OpenTelemetry traces                    │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

When the Engine receives an API request, it computes a DAG (Directed Acyclic Graph) of low-level operations and processes them concurrently. Intermediate artifacts are built just-in-time, and every operation is incremental by default. Once all operations resolve, the Engine returns results to your program.

CI-Agnostic Design — "Write Once, Run Anywhere"

Dagger's core design principle is CI-agnostic. Your pipelines aren't tied to any platform or provider. GitHub Actions, GitLab CI, Jenkins, CircleCI, Azure Pipelines, or your local terminal — the same pipeline logic works everywhere.

Key Value: This fundamentally solves "works on my machine" problems. A pipeline tested locally with dagger call build runs identically on CI runners. Local debugging — impossible with YAML-based CI — is now a reality.

v0.20 Key Features — Dang, Native TUI, Cache Management

Dang: Dagger's Dedicated Scripting Language

Introduced in v0.20.2, Dang is a scripting language designed specifically for Dagger. It runs on a native runtime built into the Engine and introspects the Engine schema at runtime, making every Dagger type a native language type. No codegen, near-instant startup, and concise syntax optimized for AI-assisted development.

# Build pipeline in Dang — much more concise than Go SDK
container |
  from "golang:1.22-alpine" |
  with-exec ["apk", "add", "git"] |
  with-directory "/src" (host | directory ".") |
  with-workdir "/src" |
  with-exec ["go", "test", "./..."] |
  with-exec ["go", "build", "-o", "/app", "."]
Enter fullscreen mode Exit fullscreen mode

Dang modules run directly inside the Engine, eliminating the usual module loading overhead (container startup, process coordination). This is particularly valuable for workflows where AI agents generate and modify pipelines.

Redesigned Native TUI

v0.20.2 completely rebuilt the interactive TUI. Instead of the previous fixed viewport, it uses the terminal's native scrollback — free scrolling, link clicking, and text selection. A separate logs-based progress mode (--progress=logs) was also added for easier CI log scanning.

Improved Manual Cache Management

For teams with automatic GC disabled, explicit space threshold-based cache pruning was introduced. Per-call overrides are available for max used space, reserved space, minimum free space, and target space.

# Cache pruning options
dagger call build \
  --cache-max-used-space=20GB \
  --cache-reserved-space=5GB \
  --cache-min-free-space=10GB
Enter fullscreen mode Exit fullscreen mode

SDK Ecosystem — Write Pipelines in 8 Languages

Dagger auto-generates SDKs from its API schema, providing full type safety and editor support (autocomplete, linting) for each language.

SDK Maturity Latest Version Highlights
Go GA v0.20.3 Most mature, official reference implementation
Python GA v0.20.3 async/await, typing support
TypeScript GA v0.20.3 Deno/Bun support, ESM default
Rust GA v0.20.3 Ideal for system-level pipelines
PHP Beta - Laravel/Symfony ecosystem integration
Java Beta - For Maven/Gradle projects
.NET Beta - C# pipeline authoring
Elixir Beta - For Phoenix projects

TypeScript SDK Example — Build → Test → Publish

// dagger/src/index.ts — Full-stack CI pipeline with TypeScript SDK
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"

@object()
class CiPipeline {

  @func()
  async build(source: Directory): Promise<Container> {
    return dag
      .container()
      .from("node:22-alpine")
      .withDirectory("/app", source)
      .withWorkdir("/app")
      .withExec(["npm", "ci"])
      .withExec(["npm", "run", "build"])
  }

  @func()
  async test(source: Directory): Promise<string> {
    const ctr = await this.build(source)
    return ctr
      .withExec(["npm", "run", "test", "--", "--coverage"])
      .stdout()
  }

  @func()
  async publish(
    source: Directory,
    registry: string,
    tag: string
  ): Promise<string> {
    const built = await this.build(source)
    return built
      .withEntrypoint(["node", "dist/main.js"])
      .publish(`${registry}:${tag}`)
  }
}
Enter fullscreen mode Exit fullscreen mode
# Run locally — identical results to CI
dagger call test --source=.

# Run in GitHub Actions — same command
dagger call publish --source=. --registry=ghcr.io/myorg/myapp --tag=v1.2.3
Enter fullscreen mode Exit fullscreen mode

Go SDK Example — Multi-Architecture Build

// dagger/main.go — Multi-arch container build with Go SDK
package main

import (
    "context"
    "dagger/ci/internal/dagger"
)

type Ci struct{}

func (m *Ci) BuildMultiArch(
    ctx context.Context,
    source *dagger.Directory,
) (*dagger.Container, 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").
            WithExec([]string{"go", "build", "-o", "/app", "."})
    }

    return dag.Container().
        Publish(ctx, "ghcr.io/myorg/app:latest",
            dagger.ContainerPublishOpts{
                PlatformVariants: platformVariants,
            })
}
Enter fullscreen mode Exit fullscreen mode

Daggerverse — 1,500+ Reusable Modules

Daggerverse serves as the central index for Dagger modules, similar to DockerHub's role for container images. Over 1,500 public modules are registered, hosted on GitHub public repositories. Dagger indexes modules — it doesn't host the code.

Category Key Modules Purpose
Build golang, node, python, rust Standardized language build steps
Test pytest, vitest, go-test Test runner integration
Security trivy, grype, cosign Image scanning + signing
Deploy helm, kubectl, terraform Infrastructure provisioning
AI daggie, openai, ollama AI agent pipelines
Notifications slack, discord, github-comment Pipeline result notifications
# Using Daggerverse modules — direct remote module calls
dagger call -m github.com/purpleclay/daggerverse/trivy@v0.5.0 \
  scan --source=. --severity=HIGH,CRITICAL

# Production tip: Always pin versions for remote modules
# ✅ github.com/user/module@v1.2.3
# ❌ github.com/user/module (latest auto — not recommended)
Enter fullscreen mode Exit fullscreen mode

Production Warning: Always pin specific versions when using Daggerverse modules in production to prevent unexpected changes. Manage internal modules in a monorepo subdirectory.

CI Integration Patterns — GitHub Actions, GitLab CI, Jenkins

GitHub Actions + Dagger

Dagger replaces GitHub Actions' YAML definitions while keeping the runner infrastructure. YAML serves only as a minimal wrapper for Dagger calls.

# .github/workflows/ci.yml — Dagger + GitHub Actions
name: CI
on:
  push:
    branches: [main]
  pull_request:

jobs:
  dagger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Dagger Pipeline
        uses: dagger/dagger-for-github@v8
        with:
          version: "0.20.3"
          verb: call
          args: test --source=.
      - name: Build and Push
        if: github.ref == 'refs/heads/main'
        uses: dagger/dagger-for-github@v8
        with:
          version: "0.20.3"
          verb: call
          args: publish --source=. --registry=ghcr.io/${{ github.repository }} --tag=${{ github.sha }}
Enter fullscreen mode Exit fullscreen mode

GitLab CI + Dagger

# .gitlab-ci.yml — Dagger + GitLab CI
stages:
  - ci

dagger:
  stage: ci
  image: alpine:latest
  services:
    - docker:dind
  variables:
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - apk add --no-cache curl
    - curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
    - export PATH=$HOME/.dagger/bin:$PATH
  script:
    - dagger call test --source=.
    - dagger call publish --source=. --registry=$CI_REGISTRY_IMAGE --tag=$CI_COMMIT_SHA
Enter fullscreen mode Exit fullscreen mode

Dagger Cloud Checks — Managed CI

Dagger Cloud connects to your Git provider and automatically runs dagger check on every change. Auto-scaled on cloud engines — no YAML, no vendor syntax, no orchestration layer. Through a partnership with Depot, managed Dagger Powered GitHub Actions runners are also available with pre-installed Dagger, automatic persistent layer caching, and multi-architecture support.

Kubernetes Production Deployment

DaemonSet Pattern

In Kubernetes, the Dagger Engine deploys as a DaemonSet — one Engine instance per node for maximum resource efficiency and local cache reuse.

# dagger-engine-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: dagger-engine
  namespace: ci
spec:
  selector:
    matchLabels:
      app: dagger-engine
  template:
    metadata:
      labels:
        app: dagger-engine
    spec:
      containers:
        - name: dagger-engine
          image: registry.dagger.io/engine:v0.20.3
          securityContext:
            privileged: true
          ports:
            - containerPort: 8080
              name: api
          volumeMounts:
            - name: dagger-cache
              mountPath: /var/lib/dagger
          resources:
            requests:
              cpu: "2"
              memory: "4Gi"
            limits:
              cpu: "4"
              memory: "8Gi"
      volumes:
        - name: dagger-cache
          hostPath:
            path: /var/lib/dagger
            type: DirectoryOrCreate
Enter fullscreen mode Exit fullscreen mode

Actions Runner Controller (ARC) Integration

For GitHub Actions with Kubernetes runners, combine ARC with Dagger. ARC usage grew 45% year-over-year in 2026, making it the standard for ephemeral, auto-scaling runners.

Comparison with Existing CI/CD Tools

Aspect Dagger GitHub Actions Jenkins GitLab CI
Pipeline Definition Code (Go/TS/Py) YAML Groovy DSL YAML
Local Execution Full support act (limited) No No
Vendor Lock-in None GitHub None GitLab
Caching Auto incremental Manual config Plugins Manual config
Debugging OTel traces Logs only Blue Ocean Logs only
Module Ecosystem 1,500+ 20K+ Marketplace 1,800+ plugins Limited built-in
K8s Native DaemonSet ARC Agent Pod Runner
AI Integration Dang + Daggie Copilot Limited Duo

Important: Dagger is not a replacement for GitHub Actions — it's complementary. Keep your CI runner infrastructure (GitHub Actions, GitLab CI, etc.) and replace the YAML logic with Dagger functions. "Dagger replaces YAML, not your CI."

OpenTelemetry Integration — Pipeline Observability

Every Dagger operation automatically generates OpenTelemetry traces with granular logs and metrics, viewable in terminal or web interfaces.

# Configure OpenTelemetry export
export OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318

# Run pipeline — traces sent automatically
dagger call build --source=.

# Visualize every pipeline step in Jaeger UI
# See execution time, cache hits/misses, dependency graphs
Enter fullscreen mode Exit fullscreen mode

Combined with Prometheus + Grafana, you can monitor pipeline execution time trends, cache hit rates, and failure rates on dashboards — deep trace-based analysis that was impossible with log-only YAML CI.

Performance Benchmarks — Real-World Data

OpenMeter Case Study (Published Data)

Configuration Build Time Cost Improvement
GitHub Actions alone 25 min Baseline -
Dagger + Cloud caching 10 min -30% 2.5x
Dagger + Cloud + Fast Runner 5 min -50% 5x

The key factor is incremental caching. The Dagger Engine caches each DAG node independently, re-executing only changed portions — fundamentally different from traditional CI that runs entire workflows from scratch.

GitHub Actions Runner Price Reduction

In January 2026, GitHub reduced runner pricing by up to 39%. The new 4-vCPU "Standard" runner costs the same as the 2024 2-vCPU runner. Combining these runners with Dagger enables 5x performance at half the cost.

Production Adoption Checklist

Phase Check Item Recommendation
1. Pilot Apply Dagger to a single project Run parallel with existing YAML, compare results
2. SDK Selection Use your team's primary language SDK Recommend GA SDKs: Go/TS/Python
3. Module Management Pin external module versions, monorepo for internal Supply chain security: Cosign signature verification
4. Caching Dagger Cloud caching or self-hosted cache Per-node hostPath or PVC
5. Observability Configure OTel trace collection Jaeger/Tempo + Grafana dashboard
6. Security Minimize privileged containers Evaluate Sysbox or rootless mode
7. Rollout Team-wide adoption after pilot success Build internal module library

Conclusion — The Next Step for CI/CD

Dagger's proposition is clear: write CI/CD pipelines in code (not YAML), run them identically locally and in CI, and maximize speed with container-based incremental caching. At cdCon 2026, AI-powered pipeline optimization, platform engineering, and software supply chain security are key themes — and Dagger shows strength in all three areas.

The emergence of the Dang scripting language signals a future where AI agents create and modify pipelines. Its design — introspecting the Engine schema without codegen — is an interface optimized for LLMs to understand and manipulate pipelines. In the evolution from "humans writing YAML" to "AI agents orchestrating pipelines as code," Dagger is the most compelling runtime candidate.

You don't need to abandon GitHub Actions or GitLab CI today. Keep the YAML wrapper and gradually migrate pipeline logic to Dagger functions — that's the most practical adoption strategy. Start with a pilot on one project, and share the build time reduction and local debugging experience with your team. Once "works on my machine" problems disappear, going back to YAML becomes very difficult.


This article was written with AI assistance (Claude Opus 4.6). Technical accuracy was cross-verified against official documentation, release notes, and published case study data. For the latest version changes, check the Dagger Official Changelog.

© 2026 ManoIT · www.manoit.co.kr


Originally published at ManoIT Tech Blog.

Top comments (0)