Skip to content

Dagger

Programmable CI/CD engine that runs pipelines in containers using Go, Python, or TypeScript SDKs.

CommandDescription
curl -fsSL https://dl.dagger.io/dagger/install.sh | shInstall Dagger CLI on Linux/macOS
brew install dagger/tap/daggerInstall on macOS with Homebrew
choco install daggerInstall on Windows with Chocolatey
scoop install daggerInstall on Windows with Scoop
nix profile install nixpkgs#daggerInstall on NixOS/with Nix
dagger versionShow installed Dagger version
CommandDescription
dagger initInitialize a new Dagger module
dagger init --sdk=pythonInitialize module with Python SDK
dagger init --sdk=typescriptInitialize module with TypeScript SDK
dagger init --sdk=goInitialize module with Go SDK
dagger init --name=my-pipelineInitialize module with custom name
dagger init --sdk=go --source=./ciInitialize in a subdirectory
CommandDescription
dagger callCall a function from the current module
dagger call buildCall the build function
dagger call test --source=.Call test function with source argument
dagger call build --source=. export --path=./distBuild and export output locally
dagger call lint --source=. stdoutRun lint and print stdout
dagger call publish --source=. --tag=v1.0Call publish function with tag
dagger functionsList available functions in module
dagger shellOpen interactive Dagger shell
dagger queryExecute raw GraphQL query against engine
dagger run cmdRun command inside Dagger session
CommandDescription
dagger loginAuthenticate with Dagger Cloud
dagger logoutRemove authentication
dagger configDisplay module configuration
dagger cloud tracesView pipeline traces in Dagger Cloud
DAGGER_CLOUD_TOKEN=token dagger call buildRun with Dagger Cloud token
CommandDescription
dagger install github.com/org/repoInstall a dependency module
dagger install github.com/org/repo@v1.0Install specific version
dagger install github.com/org/repo/subpathInstall module from subdirectory
dagger developGenerate/update SDK bindings
dagger develop --sdk=pythonSwitch module to different SDK
dagger mod syncSynchronize module dependencies
dagger mod updateUpdate module dependencies
dagger publish ttl.sh/my-modulePublish module to registry
dagger publish ghcr.io/org/module:v1.0Publish to GitHub Container Registry
CommandDescription
func (m *MyModule) Build(ctx context.Context, src *dagger.Directory) *dagger.ContainerDefine build function
dag.Container().From("golang:1.22")Create container from image
container.WithDirectory("/src", src)Mount directory into container
container.WithExec([]string{"go", "build", "."})Execute command in container
container.WithEnvVariable("KEY", "value")Set environment variable
container.File("/app/binary")Get file from container
container.Directory("/app/dist")Get directory from container
container.WithServiceBinding("db", dbSvc)Bind service dependency
container.WithWorkdir("/src")Set working directory
container.WithUser("nonroot")Set container user
container.Publish(ctx, "ttl.sh/my-image")Publish container as image
container.Export(ctx, "./output.tar")Export container as tarball
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)
}
CommandDescription
@functionDecorate function to expose to Dagger
async def build(self, src: dagger.Directory) -> dagger.ContainerDefine async build function
dag.container().from_("python:3.12")Create container from image
container.with_directory("/src", src)Mount directory
container.with_exec(["pip", "install", "-r", "requirements.txt"])Run pip install
await container.stdout()Get command output
container.with_workdir("/app")Set working directory
container.with_user("nonroot")Set container user
await container.publish("ttl.sh/my-app")Publish container as image
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}`)
  }
}
CommandDescription
container.WithMountedCache("/go/pkg", dag.CacheVolume("gomod"))Mount persistent cache volume (Go)
container.with_mounted_cache("/root/.cache/pip", dag.cache_volume("pip"))Mount pip cache (Python)
dag.CacheVolume("node-modules")Create named cache volume
container.WithMountedCache("/root/.cache/go-build", dag.CacheVolume("gobuild"))Cache Go build artifacts
container.WithMountedCache("/root/.npm", dag.CacheVolume("npm"))Cache npm packages
container.WithMountedCache("/root/.cargo/registry", dag.CacheVolume("cargo"))Cache Rust dependencies
CommandDescription
container.WithEnvVariable("CACHE_BUSTER", time.Now().String())Bust cache when needed
dagger call --focus=false buildRun without automatic focus/caching
container.WithoutEnvVariable("CACHE_BUSTER")Remove cache-busting variable
CommandDescription
dag.SetSecret("token", os.Getenv("API_TOKEN"))Create secret from env variable
container.WithSecretVariable("API_KEY", secret)Set env var from secret
container.WithMountedSecret("/run/secrets/key", secret)Mount secret as file
dagger call deploy --token=env:API_TOKENPass secret from env via CLI
dagger call deploy --token=file:./token.txtPass secret from file via CLI
dagger call deploy --token=cmd:"vault read -field=token secret/app"Pass secret from command
CommandDescription
container.WithServiceBinding("db", dbSvc)Bind a service to a container
container.WithExposedPort(8080)Expose a port from a container
container.AsService()Convert container into a service
dag.Container().From("postgres:16").WithExposedPort(5432).AsService()Create a database service
service.Start(ctx)Start a service explicitly
service.Stop(ctx)Stop a running service
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 {
	// Stage 1: Build dependencies
	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"})

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

	// Stage 3: Minimal runtime
	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=.
CommandDescription
dagger call --debug buildRun with debug logging
dagger call build --interactiveDrop into interactive shell on failure
container.WithExec(["sh", "-c", "cat /etc/os-release"])Debug container contents
container.Terminal()Open terminal in container (Go)
export DAGGER_LOG_LEVEL=debugSet verbose logging
dagger query '{ container { from(address:"alpine") { exec(args:["ls"]) { stdout } } } }'Debug with raw GraphQL
dagger call build 2>&1 | tee dagger-output.logSave output to log file
container.WithExec(["ls", "-la", "/app"])List files for debugging
container.WithExec(["env"])Print environment variables
  1. Use cache volumes generously — mount caches for package managers (pip, npm, cargo, Go modules) to dramatically reduce build times across runs.

  2. Keep modules small and composable — break large pipelines into reusable functions that each do one thing well, then compose them together.

  3. Use secrets instead of environment variables — never pass sensitive values as plain strings; always use dag.SetSecret() or CLI secret references.

  4. Pin base image versions — use specific tags like golang:1.22 instead of latest for reproducible builds.

  5. Leverage multi-stage patterns — separate build dependencies from runtime to produce minimal final containers.

  6. Use service bindings for integration tests — spin up databases, caches, and other dependencies as services instead of mocking them.

  7. Export artifacts explicitly — use .Export() or .Publish() to make outputs available outside the Dagger engine.

  8. Enable Dagger Cloud — connect to Dagger Cloud for pipeline caching, tracing, and collaborative debugging.

  9. Use --interactive for debugging — when a step fails, --interactive drops you into the container at the failure point.

  10. Test locally before CI — Dagger pipelines run identically on laptops and in CI, so validate everything locally first.