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
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\")
# Run locally
dagger call test --source .
dagger call build --source .
dagger call publish --source . --registry ttl.sh
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"})
}
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 .
# .gitlab-ci.yml — GitLab CI
test:
image: docker:latest
script:
- curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
- dagger call test --source .
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)