Zum Inhalt springen

Dagger

Programmierbare CI/CD-Engine, die Pipelines in Containern mit Go-, Python- oder TypeScript-SDKs ausführt.

BefehlBeschreibung
curl -fsSL https://dl.dagger.io/dagger/install.sh | shDagger CLI auf Linux/macOS installieren
brew install dagger/tap/daggerAuf macOS mit Homebrew installieren
choco install daggerAuf Windows mit Chocolatey installieren
scoop install daggerAuf Windows mit Scoop installieren
nix profile install nixpkgs#daggerAuf NixOS/mit Nix installieren
dagger versionInstallierte Dagger-Version anzeigen
BefehlBeschreibung
dagger initNeues Dagger-Modul initialisieren
dagger init --sdk=pythonModul mit Python SDK initialisieren
dagger init --sdk=typescriptModul mit TypeScript SDK initialisieren
dagger init --sdk=goModul mit Go SDK initialisieren
dagger init --name=my-pipelineModul mit benutzerdefiniertem Namen initialisieren
dagger init --sdk=go --source=./ciIn einem Unterverzeichnis initialisieren
BefehlBeschreibung
dagger callFunktion aus dem aktuellen Modul aufrufen
dagger call buildBuild-Funktion aufrufen
dagger call test --source=.Test-Funktion mit Source-Argument aufrufen
dagger call build --source=. export --path=./distBauen und Ausgabe lokal exportieren
dagger call lint --source=. stdoutLint ausführen und stdout ausgeben
dagger call publish --source=. --tag=v1.0Publish-Funktion mit Tag aufrufen
dagger functionsVerfügbare Funktionen im Modul auflisten
dagger shellInteraktive Dagger-Shell öffnen
dagger queryRaw-GraphQL-Abfrage gegen Engine ausführen
dagger run cmdBefehl innerhalb einer Dagger-Sitzung ausführen
BefehlBeschreibung
dagger loginBei Dagger Cloud authentifizieren
dagger logoutAuthentifizierung entfernen
dagger configModulkonfiguration anzeigen
dagger cloud tracesPipeline-Traces in Dagger Cloud anzeigen
DAGGER_CLOUD_TOKEN=token dagger call buildMit Dagger Cloud Token ausführen
BefehlBeschreibung
dagger install github.com/org/repoAbhängigkeitsmodul installieren
dagger install github.com/org/repo@v1.0Bestimmte Version installieren
dagger install github.com/org/repo/subpathModul aus Unterverzeichnis installieren
dagger developSDK-Bindings generieren/aktualisieren
dagger develop --sdk=pythonModul auf anderes SDK umstellen
dagger mod syncModulabhängigkeiten synchronisieren
dagger mod updateModulabhängigkeiten aktualisieren
dagger publish ttl.sh/my-moduleModul in Registry veröffentlichen
dagger publish ghcr.io/org/module:v1.0In GitHub Container Registry veröffentlichen
BefehlBeschreibung
func (m *MyModule) Build(ctx context.Context, src *dagger.Directory) *dagger.ContainerBuild-Funktion definieren
dag.Container().From("golang:1.22")Container aus Image erstellen
container.WithDirectory("/src", src)Verzeichnis in Container einbinden
container.WithExec([]string{"go", "build", "."})Befehl im Container ausführen
container.WithEnvVariable("KEY", "value")Umgebungsvariable setzen
container.File("/app/binary")Datei aus Container abrufen
container.Directory("/app/dist")Verzeichnis aus Container abrufen
container.WithServiceBinding("db", dbSvc)Service-Abhängigkeit binden
container.WithWorkdir("/src")Arbeitsverzeichnis setzen
container.WithUser("nonroot")Container-Benutzer setzen
container.Publish(ctx, "ttl.sh/my-image")Container als Image veröffentlichen
container.Export(ctx, "./output.tar")Container als Tarball exportieren
package main

import (
	"context"
	"dagger/my-pipeline/internal/dagger"
)

type MyPipeline struct{}

func (m *MyPipeline) Build(ctx context.Context, src *dagger.Directory) *dagger.Container {
	goCache := dag.CacheVolume("go-mod")
	buildCache := dag.CacheVolume("go-build")

	return dag.Container().
		From("golang:1.22-alpine").
		WithMountedCache("/go/pkg/mod", goCache).
		WithMountedCache("/root/.cache/go-build", buildCache).
		WithDirectory("/src", src).
		WithWorkdir("/src").
		WithExec([]string{"go", "mod", "download"}).
		WithExec([]string{"go", "build", "-o", "/app/server", "./cmd/server"})
}

func (m *MyPipeline) Test(ctx context.Context, src *dagger.Directory) (string, error) {
	return m.Build(ctx, src).
		WithExec([]string{"go", "test", "-v", "./..."}).
		Stdout(ctx)
}

func (m *MyPipeline) Publish(ctx context.Context, src *dagger.Directory, tag string) (string, error) {
	build := m.Build(ctx, src)
	return dag.Container().
		From("cgr.dev/chainguard/static:latest").
		WithFile("/app/server", build.File("/app/server")).
		WithEntrypoint([]string{"/app/server"}).
		Publish(ctx, "ttl.sh/my-app:"+tag)
}
BefehlBeschreibung
@functionFunktion für Dagger dekorieren
async def build(self, src: dagger.Directory) -> dagger.ContainerAsynchrone Build-Funktion definieren
dag.container().from_("python:3.12")Container aus Image erstellen
container.with_directory("/src", src)Verzeichnis einbinden
container.with_exec(["pip", "install", "-r", "requirements.txt"])pip install ausführen
await container.stdout()Befehlsausgabe abrufen
container.with_workdir("/app")Arbeitsverzeichnis setzen
container.with_user("nonroot")Container-Benutzer setzen
await container.publish("ttl.sh/my-app")Container als Image veröffentlichen
import dagger
from dagger import dag, function, object_type

@object_type
class MyPipeline:
    @function
    async def build(self, src: dagger.Directory) -> dagger.Container:
        pip_cache = dag.cache_volume("pip")
        return (
            dag.container()
            .from_("python:3.12-slim")
            .with_mounted_cache("/root/.cache/pip", pip_cache)
            .with_directory("/app", src)
            .with_workdir("/app")
            .with_exec(["pip", "install", "--no-cache-dir", "-r", "requirements.txt"])
            .with_exec(["pip", "install", "."])
        )

    @function
    async def test(self, src: dagger.Directory) -> str:
        return await (
            self.build(src)
            .with_exec(["pytest", "-v", "--tb=short"])
            .stdout()
        )

    @function
    async def lint(self, src: dagger.Directory) -> str:
        return await (
            self.build(src)
            .with_exec(["ruff", "check", "."])
            .stdout()
        )

    @function
    async def publish(self, src: dagger.Directory, tag: str) -> str:
        build = self.build(src)
        return await (
            dag.container()
            .from_("cgr.dev/chainguard/python:latest")
            .with_directory("/app", build.directory("/app"))
            .with_entrypoint(["python", "-m", "myapp"])
            .publish(f"ttl.sh/my-python-app:{tag}")
        )
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"

@object()
class MyPipeline {
  @func()
  build(src: Directory): Container {
    const nodeCache = dag.cacheVolume("node-modules")
    return dag
      .container()
      .from("node:20-slim")
      .withMountedCache("/app/node_modules", nodeCache)
      .withDirectory("/app", src)
      .withWorkdir("/app")
      .withExec(["npm", "ci"])
      .withExec(["npm", "run", "build"])
  }

  @func()
  async test(src: Directory): Promise<string> {
    return this.build(src)
      .withExec(["npm", "test"])
      .stdout()
  }

  @func()
  async lint(src: Directory): Promise<string> {
    return this.build(src)
      .withExec(["npm", "run", "lint"])
      .stdout()
  }

  @func()
  async publish(src: Directory, tag: string): Promise<string> {
    return dag
      .container()
      .from("cgr.dev/chainguard/node:latest")
      .withDirectory("/app", this.build(src).directory("/app/dist"))
      .withEntrypoint(["node", "/app/index.js"])
      .publish(`ttl.sh/my-node-app:${tag}`)
  }
}
BefehlBeschreibung
container.WithMountedCache("/go/pkg", dag.CacheVolume("gomod"))Persistentes Cache-Volume einbinden (Go)
container.with_mounted_cache("/root/.cache/pip", dag.cache_volume("pip"))pip-Cache einbinden (Python)
dag.CacheVolume("node-modules")Benanntes Cache-Volume erstellen
container.WithMountedCache("/root/.cache/go-build", dag.CacheVolume("gobuild"))Go-Build-Artefakte cachen
container.WithMountedCache("/root/.npm", dag.CacheVolume("npm"))npm-Pakete cachen
container.WithMountedCache("/root/.cargo/registry", dag.CacheVolume("cargo"))Rust-Abhängigkeiten cachen
BefehlBeschreibung
container.WithEnvVariable("CACHE_BUSTER", time.Now().String())Cache bei Bedarf invalidieren
dagger call --focus=false buildOhne automatisches Focus/Caching ausführen
container.WithoutEnvVariable("CACHE_BUSTER")Cache-Busting-Variable entfernen
BefehlBeschreibung
dag.SetSecret("token", os.Getenv("API_TOKEN"))Secret aus Umgebungsvariable erstellen
container.WithSecretVariable("API_KEY", secret)Umgebungsvariable aus Secret setzen
container.WithMountedSecret("/run/secrets/key", secret)Secret als Datei einbinden
dagger call deploy --token=env:API_TOKENSecret aus Umgebung per CLI übergeben
dagger call deploy --token=file:./token.txtSecret aus Datei per CLI übergeben
dagger call deploy --token=cmd:"vault read -field=token secret/app"Secret aus Befehl übergeben
BefehlBeschreibung
container.WithServiceBinding("db", dbSvc)Service an Container binden
container.WithExposedPort(8080)Port aus Container freigeben
container.AsService()Container in Service umwandeln
dag.Container().From("postgres:16").WithExposedPort(5432).AsService()Datenbank-Service erstellen
service.Start(ctx)Service explizit starten
service.Stop(ctx)Laufenden Service stoppen
func (m *MyPipeline) IntegrationTest(ctx context.Context, src *dagger.Directory) (string, error) {
	postgres := dag.Container().
		From("postgres:16-alpine").
		WithEnvVariable("POSTGRES_PASSWORD", "test").
		WithEnvVariable("POSTGRES_DB", "testdb").
		WithExposedPort(5432).
		AsService()

	redis := dag.Container().
		From("redis:7-alpine").
		WithExposedPort(6379).
		AsService()

	return m.Build(ctx, src).
		WithServiceBinding("db", postgres).
		WithServiceBinding("cache", redis).
		WithEnvVariable("DATABASE_URL", "postgres://postgres:test@db:5432/testdb").
		WithEnvVariable("REDIS_URL", "redis://cache:6379").
		WithExec([]string{"go", "test", "-v", "-tags=integration", "./..."}).
		Stdout(ctx)
}
func (m *MyPipeline) BuildProduction(ctx context.Context, src *dagger.Directory) *dagger.Container {
	// Phase 1: Abhängigkeiten bauen
	deps := dag.Container().
		From("golang:1.22-alpine").
		WithDirectory("/src", src, dagger.ContainerWithDirectoryOpts{
			Include: []string{"go.mod", "go.sum"},
		}).
		WithWorkdir("/src").
		WithMountedCache("/go/pkg/mod", dag.CacheVolume("gomod")).
		WithExec([]string{"go", "mod", "download"})

	// Phase 2: Binary bauen
	build := deps.
		WithDirectory("/src", src).
		WithExec([]string{"go", "build", "-ldflags=-s -w", "-o", "/app/server", "./cmd/server"})

	// Phase 3: Minimale Laufzeitumgebung
	return dag.Container().
		From("cgr.dev/chainguard/static:latest").
		WithFile("/app/server", build.File("/app/server")).
		WithExposedPort(8080).
		WithEntrypoint([]string{"/app/server"})
}
name: CI with Dagger
on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Dagger
        run: |
          curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
          sudo mv ./bin/dagger /usr/local/bin/
      - name: Run pipeline
        env:
          DAGGER_CLOUD_TOKEN: ${{ secrets.DAGGER_CLOUD_TOKEN }}
        run: |
          dagger call build --source=.
          dagger call test --source=.
          dagger call lint --source=.
      - name: Publish
        if: github.ref == 'refs/heads/main'
        run: |
          dagger call publish --source=. --tag=${{ github.sha }}
stages:
  - build
  - test

variables:
  DAGGER_CLOUD_TOKEN: $DAGGER_CLOUD_TOKEN

build:
  stage: build
  image: alpine:latest
  before_script:
    - apk add curl
    - curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
    - export PATH=$PWD/bin:$PATH
  script:
    - dagger call build --source=.
    - dagger call test --source=.
BefehlBeschreibung
dagger call --debug buildMit Debug-Logging ausführen
dagger call build --interactiveBei Fehler in interaktive Shell wechseln
container.WithExec(["sh", "-c", "cat /etc/os-release"])Container-Inhalte debuggen
container.Terminal()Terminal im Container öffnen (Go)
export DAGGER_LOG_LEVEL=debugAusführliches Logging setzen
dagger query '{ container { from(address:"alpine") { exec(args:["ls"]) { stdout } } } }'Mit Raw-GraphQL debuggen
dagger call build 2>&1 | tee dagger-output.logAusgabe in Log-Datei speichern
container.WithExec(["ls", "-la", "/app"])Dateien zum Debuggen auflisten
container.WithExec(["env"])Umgebungsvariablen ausgeben
  1. Cache-Volumes großzügig verwenden — Caches für Paketmanager (pip, npm, cargo, Go-Module) einbinden, um Build-Zeiten über mehrere Durchläufe drastisch zu reduzieren.

  2. Module klein und komponierbar halten — Große Pipelines in wiederverwendbare Funktionen aufteilen, die jeweils eine Sache gut erledigen, und dann zusammensetzen.

  3. Secrets statt Umgebungsvariablen verwenden — Sensible Werte niemals als Klartext übergeben; immer dag.SetSecret() oder CLI-Secret-Referenzen verwenden.

  4. Base-Image-Versionen festpinnen — Spezifische Tags wie golang:1.22 statt latest für reproduzierbare Builds verwenden.

  5. Multi-Stage-Muster nutzen — Build-Abhängigkeiten von der Laufzeitumgebung trennen, um minimale finale Container zu erzeugen.

  6. Service-Bindings für Integrationstests verwenden — Datenbanken, Caches und andere Abhängigkeiten als Services starten, anstatt sie zu mocken.

  7. Artefakte explizit exportieren.Export() oder .Publish() verwenden, um Ausgaben außerhalb der Dagger-Engine verfügbar zu machen.

  8. Dagger Cloud aktivieren — Mit Dagger Cloud verbinden für Pipeline-Caching, Tracing und kollaboratives Debugging.

  9. --interactive zum Debugging verwenden — Wenn ein Schritt fehlschlägt, öffnet --interactive eine Shell im Container am Fehlerpunkt.

  10. Lokal vor CI testen — Dagger-Pipelines laufen identisch auf Laptops und in CI, daher alles zuerst lokal validieren.