Ir al contenido

Dagger

Motor de CI/CD programable que ejecuta pipelines en contenedores usando SDKs de Go, Python o TypeScript.

ComandoDescripción
curl -fsSL https://dl.dagger.io/dagger/install.sh | shInstalar Dagger CLI en Linux/macOS
brew install dagger/tap/daggerInstalar en macOS con Homebrew
choco install daggerInstalar en Windows con Chocolatey
scoop install daggerInstalar en Windows con Scoop
nix profile install nixpkgs#daggerInstalar en NixOS/con Nix
dagger versionMostrar versión instalada de Dagger
ComandoDescripción
dagger initInicializar un nuevo módulo de Dagger
dagger init --sdk=pythonInicializar módulo con SDK de Python
dagger init --sdk=typescriptInicializar módulo con SDK de TypeScript
dagger init --sdk=goInicializar módulo con SDK de Go
dagger init --name=my-pipelineInicializar módulo con nombre personalizado
dagger init --sdk=go --source=./ciInicializar en un subdirectorio
ComandoDescripción
dagger callLlamar una función del módulo actual
dagger call buildLlamar la función build
dagger call test --source=.Llamar función test con argumento source
dagger call build --source=. export --path=./distCompilar y exportar salida localmente
dagger call lint --source=. stdoutEjecutar lint e imprimir stdout
dagger call publish --source=. --tag=v1.0Llamar función publish con tag
dagger functionsListar funciones disponibles en el módulo
dagger shellAbrir shell interactivo de Dagger
dagger queryEjecutar consulta GraphQL contra el motor
dagger run cmdEjecutar comando dentro de sesión de Dagger
ComandoDescripción
dagger loginAutenticarse con Dagger Cloud
dagger logoutEliminar autenticación
dagger configMostrar configuración del módulo
dagger cloud tracesVer trazas de pipeline en Dagger Cloud
DAGGER_CLOUD_TOKEN=token dagger call buildEjecutar con token de Dagger Cloud
ComandoDescripción
dagger install github.com/org/repoInstalar un módulo de dependencia
dagger install github.com/org/repo@v1.0Instalar versión específica
dagger install github.com/org/repo/subpathInstalar módulo desde subdirectorio
dagger developGenerar/actualizar bindings del SDK
dagger develop --sdk=pythonCambiar módulo a otro SDK
dagger mod syncSincronizar dependencias del módulo
dagger mod updateActualizar dependencias del módulo
dagger publish ttl.sh/my-modulePublicar módulo en registro
dagger publish ghcr.io/org/module:v1.0Publicar en GitHub Container Registry
ComandoDescripción
func (m *MyModule) Build(ctx context.Context, src *dagger.Directory) *dagger.ContainerDefinir función build
dag.Container().From("golang:1.22")Crear contenedor desde imagen
container.WithDirectory("/src", src)Montar directorio en contenedor
container.WithExec([]string{"go", "build", "."})Ejecutar comando en contenedor
container.WithEnvVariable("KEY", "value")Establecer variable de entorno
container.File("/app/binary")Obtener archivo del contenedor
container.Directory("/app/dist")Obtener directorio del contenedor
container.WithServiceBinding("db", dbSvc)Vincular dependencia de servicio
container.WithWorkdir("/src")Establecer directorio de trabajo
container.WithUser("nonroot")Establecer usuario del contenedor
container.Publish(ctx, "ttl.sh/my-image")Publicar contenedor como imagen
container.Export(ctx, "./output.tar")Exportar contenedor como 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)
}
ComandoDescripción
@functionDecorar función para exponerla a Dagger
async def build(self, src: dagger.Directory) -> dagger.ContainerDefinir función build asíncrona
dag.container().from_("python:3.12")Crear contenedor desde imagen
container.with_directory("/src", src)Montar directorio
container.with_exec(["pip", "install", "-r", "requirements.txt"])Ejecutar pip install
await container.stdout()Obtener salida del comando
container.with_workdir("/app")Establecer directorio de trabajo
container.with_user("nonroot")Establecer usuario del contenedor
await container.publish("ttl.sh/my-app")Publicar contenedor como imagen
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}`)
  }
}
ComandoDescripción
container.WithMountedCache("/go/pkg", dag.CacheVolume("gomod"))Montar volumen de caché persistente (Go)
container.with_mounted_cache("/root/.cache/pip", dag.cache_volume("pip"))Montar caché de pip (Python)
dag.CacheVolume("node-modules")Crear volumen de caché nombrado
container.WithMountedCache("/root/.cache/go-build", dag.CacheVolume("gobuild"))Cachear artefactos de compilación de Go
container.WithMountedCache("/root/.npm", dag.CacheVolume("npm"))Cachear paquetes npm
container.WithMountedCache("/root/.cargo/registry", dag.CacheVolume("cargo"))Cachear dependencias de Rust
ComandoDescripción
container.WithEnvVariable("CACHE_BUSTER", time.Now().String())Invalidar caché cuando sea necesario
dagger call --focus=false buildEjecutar sin enfoque/caché automático
container.WithoutEnvVariable("CACHE_BUSTER")Eliminar variable de invalidación de caché
ComandoDescripción
dag.SetSecret("token", os.Getenv("API_TOKEN"))Crear secreto desde variable de entorno
container.WithSecretVariable("API_KEY", secret)Establecer variable de entorno desde secreto
container.WithMountedSecret("/run/secrets/key", secret)Montar secreto como archivo
dagger call deploy --token=env:API_TOKENPasar secreto desde env vía CLI
dagger call deploy --token=file:./token.txtPasar secreto desde archivo vía CLI
dagger call deploy --token=cmd:"vault read -field=token secret/app"Pasar secreto desde comando
ComandoDescripción
container.WithServiceBinding("db", dbSvc)Vincular un servicio a un contenedor
container.WithExposedPort(8080)Exponer un puerto de un contenedor
container.AsService()Convertir contenedor en un servicio
dag.Container().From("postgres:16").WithExposedPort(5432).AsService()Crear un servicio de base de datos
service.Start(ctx)Iniciar un servicio explícitamente
service.Stop(ctx)Detener un servicio en ejecución
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 {
	// Etapa 1: Compilar dependencias
	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"})

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

	// Etapa 3: Runtime mínimo
	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=.
ComandoDescripción
dagger call --debug buildEjecutar con registro de depuración
dagger call build --interactiveAbrir shell interactivo en caso de fallo
container.WithExec(["sh", "-c", "cat /etc/os-release"])Depurar contenido del contenedor
container.Terminal()Abrir terminal en contenedor (Go)
export DAGGER_LOG_LEVEL=debugEstablecer registro detallado
dagger query '{ container { from(address:"alpine") { exec(args:["ls"]) { stdout } } } }'Depurar con GraphQL directo
dagger call build 2>&1 | tee dagger-output.logGuardar salida en archivo de registro
container.WithExec(["ls", "-la", "/app"])Listar archivos para depuración
container.WithExec(["env"])Imprimir variables de entorno
  1. Usa volúmenes de caché generosamente — monta cachés para gestores de paquetes (pip, npm, cargo, módulos de Go) para reducir drásticamente los tiempos de compilación entre ejecuciones.

  2. Mantén los módulos pequeños y componibles — divide pipelines grandes en funciones reutilizables que cada una haga una sola cosa bien, luego compónlas juntas.

  3. Usa secretos en lugar de variables de entorno — nunca pases valores sensibles como cadenas planas; siempre usa dag.SetSecret() o referencias de secretos del CLI.

  4. Fija versiones de imágenes base — usa tags específicos como golang:1.22 en lugar de latest para compilaciones reproducibles.

  5. Aprovecha los patrones multi-etapa — separa las dependencias de compilación del runtime para producir contenedores finales mínimos.

  6. Usa vinculación de servicios para pruebas de integración — levanta bases de datos, cachés y otras dependencias como servicios en lugar de simularlos.

  7. Exporta artefactos explícitamente — usa .Export() o .Publish() para hacer las salidas disponibles fuera del motor de Dagger.

  8. Habilita Dagger Cloud — conéctate a Dagger Cloud para caché de pipelines, trazas y depuración colaborativa.

  9. Usa --interactive para depuración — cuando un paso falla, --interactive te lleva al contenedor en el punto de fallo.

  10. Prueba localmente antes del CI — los pipelines de Dagger se ejecutan idénticamente en laptops y en CI, así que valida todo localmente primero.