DEV Community

Alex Spinov
Alex Spinov

Posted on

Dagger Has a Free API: Write CI/CD Pipelines in Code Instead of YAML

Dagger lets you write CI/CD pipelines in TypeScript, Python, or Go — run them locally, debug them in your IDE, then execute the same pipeline on any CI system.

Why Dagger Matters

Every CI system has its own YAML dialect. Moving from GitHub Actions to GitLab CI means rewriting everything. Dagger pipelines are code that runs anywhere — your laptop, GitHub Actions, GitLab, CircleCI, or bare metal.

What you get for free:

  • Write CI/CD in TypeScript, Python, or Go — not YAML
  • Run pipelines locally with dagger call — test before pushing
  • Same pipeline runs on ANY CI system (GitHub Actions, GitLab, Jenkins)
  • Built on containers — every step is reproducible and cacheable
  • GraphQL API for composing pipeline modules
  • Dagger Cloud: real-time pipeline visualization and caching

Quick Start

# Install Dagger
curl -fsSL https://dl.dagger.io/dagger/install.sh | sh

# Initialize module
dagger init --sdk=typescript

# Run a function
dagger call build --source=.

# Run tests
dagger call test --source=.
Enter fullscreen mode Exit fullscreen mode

Build + Test + Deploy Pipeline

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:20-slim")
      .withDirectory("/app", source)
      .withWorkdir("/app")
      .withExec(["npm", "ci"])
      .withExec(["npm", "run", "build"]);
  }

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

  @func()
  async lint(source: Directory): Promise<string> {
    const built = await this.build(source);
    return built
      .withExec(["npx", "eslint", "src/"])
      .stdout();
  }

  @func()
  async deploy(source: Directory, token: string): Promise<string> {
    // Run tests first
    await this.test(source);

    // Build production image
    const prod = await this.build(source);

    // Push to registry
    const ref = await prod
      .withLabel("org.opencontainers.image.source", "https://github.com/myapp")
      .publish(`ttl.sh/myapp-${Date.now()}:1h`);

    return `Deployed: ${ref}`;
  }
}
Enter fullscreen mode Exit fullscreen mode

Multi-Language Matrix

@func()
async testMatrix(source: Directory): Promise<string[]> {
  const versions = ["18", "20", "22"];
  const results: string[] = [];

  // Run tests in parallel across Node versions
  for (const version of versions) {
    const result = await dag
      .container()
      .from(`node:${version}-slim`)
      .withDirectory("/app", source)
      .withWorkdir("/app")
      .withExec(["npm", "ci"])
      .withExec(["npm", "test"])
      .stdout();
    results.push(`Node ${version}: ${result}`);
  }

  return results;
}
Enter fullscreen mode Exit fullscreen mode

Use in GitHub Actions (2 lines)

- name: Run Dagger Pipeline
  uses: dagger/dagger-for-github@v6
  with:
    verb: call
    args: deploy --source=. --token=env:DEPLOY_TOKEN
Enter fullscreen mode Exit fullscreen mode

Useful Links


Automating data collection pipelines? Check out my developer tools on Apify for ready-made web scrapers, or email spinov001@gmail.com for custom solutions.

Top comments (0)