DEV Community

Alex Spinov
Alex Spinov

Posted on

Dagger Has a Free API — Write CI/CD Pipelines as Code in Any Language

TL;DR

Dagger lets you write CI/CD pipelines as code in TypeScript, Python, or Go — then run them anywhere: locally, in GitHub Actions, GitLab CI, or any container runtime. No more YAML.

What Is Dagger?

Dagger replaces CI/CD YAML with real code:

  • Code, not YAML — TypeScript, Python, Go SDKs
  • Run anywhere — local, GitHub Actions, GitLab CI, Jenkins
  • Container-native — every step runs in a container
  • Caching — automatic layer caching for fast builds
  • Portable — same pipeline works on every CI platform
  • Local debugging — run your CI pipeline on your laptop
  • Free — Apache 2.0

Quick Start

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

# Initialize a Dagger module
dagger init --sdk=typescript
Enter fullscreen mode Exit fullscreen mode

TypeScript Pipeline

// dagger/src/index.ts
import { dag, Container, Directory, object, func } from "@dagger.io/dagger";

@object()
class MyPipeline {
  @func()
  async test(source: Directory): Promise<string> {
    return dag
      .container()
      .from("node:20")
      .withDirectory("/app", source)
      .withWorkdir("/app")
      .withExec(["npm", "install"])
      .withExec(["npm", "test"])
      .stdout();
  }

  @func()
  async build(source: Directory): Promise<Container> {
    return dag
      .container()
      .from("node:20-alpine")
      .withDirectory("/app", source)
      .withWorkdir("/app")
      .withExec(["npm", "install", "--production"])
      .withExec(["npm", "run", "build"])
      .withEntrypoint(["node", "dist/index.js"]);
  }

  @func()
  async publish(source: Directory, registry: string, tag: string): Promise<string> {
    const container = await this.build(source);
    return container.publish(`${registry}:${tag}`);
  }

  @func()
  async ci(source: Directory): Promise<string> {
    // Run tests
    await this.test(source);
    // Build and publish
    const image = await this.publish(source, "ghcr.io/myorg/myapp", "latest");
    return `Published: ${image}`;
  }
}
Enter fullscreen mode Exit fullscreen mode

Run Locally

# Run tests locally (same as CI!)
dagger call test --source=.

# Build container
dagger call build --source=.

# Full CI pipeline
dagger call ci --source=.
Enter fullscreen mode Exit fullscreen mode

Use in GitHub Actions

# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dagger/dagger-for-github@v6
        with:
          verb: call
          args: ci --source=.
Enter fullscreen mode Exit fullscreen mode

Multi-Stage with Caching

@func()
async buildOptimized(source: Directory): Promise<Container> {
  // Install deps with cache
  const deps = dag
    .container()
    .from("node:20")
    .withDirectory("/app", source, { include: ["package*.json"] })
    .withWorkdir("/app")
    .withMountedCache("/root/.npm", dag.cacheVolume("npm"))
    .withExec(["npm", "ci"]);

  // Build with full source
  const built = deps
    .withDirectory("/app", source)
    .withExec(["npm", "run", "build"]);

  // Production image (small)
  return dag
    .container()
    .from("node:20-alpine")
    .withDirectory("/app/dist", built.directory("/app/dist"))
    .withDirectory("/app/node_modules", deps.directory("/app/node_modules"))
    .withWorkdir("/app")
    .withEntrypoint(["node", "dist/index.js"]);
}
Enter fullscreen mode Exit fullscreen mode

Dagger vs Alternatives

Feature Dagger GitHub Actions GitLab CI Jenkins
Config Code (TS/Py/Go) YAML YAML Groovy/YAML
Local run Yes act (limited) No No
Portable Any CI GitHub only GitLab only Jenkins only
Caching Automatic Manual Manual Manual
Debugging Full local Commit-push-wait Commit-push-wait Console
Container-native Yes Yes Yes Plugin

Resources


Building CI/CD for scraping pipelines? My Apify tools extract web data — automate deployments with Dagger for reproducible, testable pipelines. Questions? Email spinov001@gmail.com

Top comments (0)