DEV Community

Alex Spinov
Alex Spinov

Posted on

Dagger Has a Free API: Write CI/CD Pipelines in Go, Python, or TypeScript Instead of YAML

Dagger lets you write CI/CD pipelines as code in Go, Python, or TypeScript instead of YAML. Every step runs in a container, so pipelines work identically on your laptop and in any CI system.

Why Dagger?

  • Code, not YAML — write pipelines in real programming languages
  • Portable — same pipeline runs on GitHub Actions, GitLab, Jenkins, locally
  • Cacheable — automatic content-addressed caching
  • Composable — reuse pipeline modules from the community
  • GraphQL API — query and control pipelines programmatically

Install

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

# Initialize in your project
dagger init --sdk=python
# or: --sdk=go, --sdk=typescript
Enter fullscreen mode Exit fullscreen mode

Python Pipeline

# dagger/src/main.py
import dagger
from dagger import dag, function, object_type

@object_type
class MyPipeline:
    @function
    async def test(self, source: dagger.Directory) -> str:
        \"\"\"Run tests\"\"\"  
        return await (
            dag.container()
            .from_(\"python:3.12-slim\")
            .with_directory(\"/app\", source)
            .with_workdir(\"/app\")
            .with_exec([\"pip\", \"install\", \"-r\", \"requirements.txt\"])
            .with_exec([\"pytest\", \"-v\"])
            .stdout()
        )

    @function
    async def build(self, source: dagger.Directory) -> dagger.Container:
        \"\"\"Build Docker image\"\"\"  
        return (
            dag.container()
            .from_(\"python:3.12-slim\")
            .with_directory(\"/app\", source)
            .with_workdir(\"/app\")
            .with_exec([\"pip\", \"install\", \"-r\", \"requirements.txt\"])
            .with_entrypoint([\"python\", \"app.py\"])
        )

    @function
    async def publish(self, source: dagger.Directory, registry: str) -> str:
        \"\"\"Build and publish to registry\"\"\"  
        container = await self.build(source)
        return await container.publish(f\"{registry}/myapp:latest\")
Enter fullscreen mode Exit fullscreen mode
# Run locally
dagger call test --source .
dagger call build --source .
dagger call publish --source . --registry ttl.sh
Enter fullscreen mode Exit fullscreen mode

Go Pipeline

package main

import (
    "context"
    "dagger/mymodule/internal/dagger"
)

type MyModule struct{}

func (m *MyModule) Test(ctx context.Context, source *dagger.Directory) (string, error) {
    return dag.Container().
        From("golang:1.22-alpine").
        WithDirectory("/app", source).
        WithWorkdir("/app").
        WithExec([]string{"go", "test", "./..."}).
        Stdout(ctx)
}

func (m *MyModule) Build(source *dagger.Directory) *dagger.Container {
    return dag.Container().
        From("golang:1.22-alpine").
        WithDirectory("/app", source).
        WithWorkdir("/app").
        WithExec([]string{"go", "build", "-o", "server", "./cmd/server"}).
        WithEntrypoint([]string{"./server"})
}
Enter fullscreen mode Exit fullscreen mode

CI Integration (Portable!)

# .github/workflows/ci.yml — GitHub Actions
name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dagger/dagger-for-github@v6
        with:
          verb: call
          args: test --source .
Enter fullscreen mode Exit fullscreen mode
# .gitlab-ci.yml — GitLab CI
test:
  image: docker:latest
  script:
    - curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
    - dagger call test --source .
Enter fullscreen mode Exit fullscreen mode

Key Features

Feature Details
Languages Go, Python, TypeScript
Caching Content-addressed, automatic
Portability Any CI + local
API GraphQL under the hood
Modules Reusable community modules
Runtime BuildKit containers

Resources


Need CI/CD or automation tools? Check my Apify actors or email spinov001@gmail.com.

Top comments (0)