Dagger lets you write CI/CD pipelines as code — TypeScript, Python, or Go — and run them identically on your laptop and in CI. No more 'works in CI but not locally'.
Why Dagger?
- Run locally: Test your entire CI pipeline before pushing
- Any language: TypeScript, Python, Go, PHP
- Any CI: GitHub Actions, GitLab CI, CircleCI, Jenkins
- Caching: Automatic layer caching across runs
- Containers: Everything runs in containers
- Composable: Share and reuse pipeline modules
Install
# macOS/Linux
curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
# Init project
dagger init --sdk=typescript
Define a Pipeline (TypeScript)
// dagger/src/index.ts
import { dag, Container, Directory, object, func } from '@dagger.io/dagger';
@object()
export class MyProject {
@func()
async build(source: Directory): Promise<Container> {
return dag
.container()
.from('node:20-alpine')
.withDirectory('/app', source)
.withWorkdir('/app')
.withExec(['npm', 'install'])
.withExec(['npm', 'run', 'build']);
}
@func()
async test(source: Directory): Promise<string> {
return dag
.container()
.from('node:20-alpine')
.withDirectory('/app', source)
.withWorkdir('/app')
.withExec(['npm', 'install'])
.withExec(['npm', 'test'])
.stdout();
}
@func()
async lint(source: Directory): Promise<string> {
return dag
.container()
.from('node:20-alpine')
.withDirectory('/app', source)
.withWorkdir('/app')
.withExec(['npm', 'install'])
.withExec(['npx', 'eslint', 'src/'])
.stdout();
}
}
Run Locally
# Run tests
dagger call test --source=.
# Run build
dagger call build --source=.
# Run lint
dagger call lint --source=.
Exact same pipeline runs in CI and on your laptop.
Use in GitHub Actions
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dagger/dagger-for-github@v6
with:
verb: call
args: test --source=.
Pipeline with Database (Integration Tests)
@func()
async integrationTest(source: Directory): Promise<string> {
const db = dag
.container()
.from('postgres:16-alpine')
.withEnvVariable('POSTGRES_PASSWORD', 'test')
.withExposedPort(5432)
.asService();
return dag
.container()
.from('node:20-alpine')
.withDirectory('/app', source)
.withWorkdir('/app')
.withServiceBinding('db', db)
.withEnvVariable('DATABASE_URL', 'postgres://postgres:test@db:5432/postgres')
.withExec(['npm', 'install'])
.withExec(['npm', 'run', 'test:integration'])
.stdout();
}
Publish Container
@func()
async publish(source: Directory, tag: string): Promise<string> {
const container = await this.build(source);
return container
.withEntrypoint(['node', 'dist/index.js'])
.publish(`registry.example.com/my-app:${tag}`);
}
Real-World Use Case
A team spent 2 hours per week debugging CI-only failures. After switching to Dagger, they run the exact pipeline locally. CI debug time dropped to near zero because if it works locally, it works in CI.
Need to automate data collection? Check out my Apify actors for ready-made scrapers, or email spinov001@gmail.com for custom solutions.
Top comments (0)