Dagger — Programmable CI/CD Pipelines
Dagger je open-source nástroj na písanie portable CI/CD pipelines v programovacom jazyku namiesto YAML súborov. Pipelines bežia lokálne aj v CI úplne rovnako, pretože sú zabalené v kontajneroch a riadené Dagger Enginom (postaveným nad BuildKitom).
Dagger vznikol v roku 2022 v tíme pôvodných autorov Dockeru (Solomon Hykes, Sam Alba) a rieši typický problém modernej dobe: YAML konfigurácie CI sa neškálujú, nedajú sa debugovať lokálne a každý vendor (GitHub, GitLab, CircleCI, Jenkins) má vlastný diallekt.
Prečo Dagger?
Klasický CI pipeline v YAML má známe bolesti:
- "Works on my machine" naopak — pipeline funguje v CI, ale na lokále ho neviete reprodukovať. Každá zmena si vyžaduje push a čakanie na runner.
- Vendor lock-in — presun z GitHub Actions na GitLab CI znamená prepísanie všetkých workflow súborov.
- Bez typovej kontroly — YAML chyby sa odhalia až po spustení.
- Duplicita — každý tím kopíruje tie isté build/test kroky do svojho workflow.
Dagger tieto problémy rieši:
- Pipeline je kód — Go, Python, TypeScript alebo Java SDK s plným type-checkingom, IDE autocomplete a debugovaním.
- Lokálne = CI —
dagger call buildrobí to isté na notebooku aj na runneri. - Portabilita — ten istý Dagger kód beží v GitHub Actions, GitLab CI, Jenkinse, CircleCI alebo priamo zo shellu.
- Reproducibility cache — BuildKit cache vrstvy sa zdieľajú medzi behmi, aj medzi vývojármi (cez Dagger Cloud).
Ako Dagger funguje (architektúra)
┌───────────────────────────┐
│ Váš pipeline kód │ Go / Python / TS / Java SDK
│ (Dagger Functions) │
└──────────────┬────────────┘
│ GraphQL API
▼
┌───────────────────────────┐
│ Dagger Engine (daemon) │ container: dagger/engine
│ (BuildKit pod kapotou) │
└──────────────┬────────────┘
│ OCI runtime
▼
┌───────────────────────────┐
│ Containerd / Docker │
└───────────────────────────┘
- Dagger Functions — jednotky pipeline logiky (build, test, publish). Píšete ich v SDK.
- Dagger Engine — daemon bežiaci ako container, ktorý vykonáva operácie cez BuildKit. Každá funkcia dostane prístup k images, volume mountom, envs, secrets.
- Dagger Cloud (voliteľné) — SaaS, ktorý pridáva traces, zdieľanú cache a tímový dashboard.
Inštalácia
# macOS/Linux
curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
sudo mv bin/dagger /usr/local/bin/
# Overenie
dagger version
Pre engine nepotrebujete nič navyše — Dagger si ho stiahne a spustí pri prvom volaní.
Praktický príklad 1 — Python projekt
Vytvoríme pipeline, ktorý zbuilduje Python aplikáciu, spustí testy a vytvorí Docker image.
Najprv inicializácia modulu:
cd my-python-app
dagger init --sdk=python --name=ci
Toto vytvorí adresár ci/ s Python SDK skeletonom. Hlavný súbor ci/src/ci/main.py:
import dagger
from dagger import dag, function, object_type
@object_type
class Ci:
@function
async def test(self, source: dagger.Directory) -> str:
"""Spusti pytest v Python 3.12 kontajneri."""
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_image(self, source: dagger.Directory) -> dagger.Container:
"""Postav production 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", "main.py"])
)
@function
async def publish(self, source: dagger.Directory, tag: str) -> str:
"""Build + push do registry."""
image = await self.build_image(source)
return await image.publish(f"ttl.sh/my-app:{tag}")
Spustenie:
# Lokálne
dagger call test --source=.
# Build image
dagger call build-image --source=. export --path=./app.tar
# Publish
dagger call publish --source=. --tag=24h
To isté volanie dagger call test --source=. beží identicky na vašom notebooku aj v CI.
Praktický príklad 2 — TypeScript SDK
import { dag, Container, Directory, object, func } from "@dagger.io/dagger"
@object()
class Ci {
@func()
async test(source: Directory): Promise<string> {
return await dag
.container()
.from("node:22-alpine")
.withDirectory("/app", source)
.withWorkdir("/app")
.withExec(["npm", "ci"])
.withExec(["npm", "test"])
.stdout()
}
@func()
build(source: Directory): Container {
return dag
.container()
.from("node:22-alpine")
.withDirectory("/src", source)
.withWorkdir("/src")
.withExec(["npm", "ci"])
.withExec(["npm", "run", "build"])
}
}
dagger call test --source=.
dagger call build --source=. directory --path=dist export --path=./dist
Integrácia s GitHub Actions
Dagger v CI nepotrebuje špeciálny runner — beží v klasickom ubuntu-latest runneri:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Dagger
run: |
curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
sudo mv bin/dagger /usr/local/bin/
- name: Run tests
run: dagger call test --source=.
env:
DAGGER_CLOUD_TOKEN: ${{ secrets.DAGGER_CLOUD_TOKEN }}
YAML je triviálny — celú zložitosť drží Python/TS kód v ci/. Prechod z GitHub Actions na GitLab CI je záležitosť skopírovania jedného dagger call riadku.
Caching a performance
Dagger využíva BuildKit cache layer-by-layer. V praxi to znamená:
- Keď zmeníte jeden test súbor, rebuild
pip installsa nespustí — vrstva je cache-hit. - Pri opakovanom
dagger callcache ostáva v engine kontajneri. - Dagger Cloud pridáva zdieľanú cache medzi runnermi a lokálnymi vývojármi → prvý push v CI často trvá sekundy namiesto minút.
Secrets a env premenné
token = dag.set_secret("gh_token", os.environ["GITHUB_TOKEN"])
result = (
dag.container()
.from_("alpine:3.20")
.with_secret_variable("GITHUB_TOKEN", token)
.with_exec(["sh", "-c", "curl -H \"Authorization: token $GITHUB_TOKEN\" ..."])
)
Secrets sa nedajú vypísať cez stdout() ani export() — Dagger engine ich filtruje.
Kedy Dagger použiť a kedy nie
Dobrý fit:
- Monorepá s viacerými službami, kde YAML duplikácia je bolesť.
- Tímy, ktoré chcú lokálne debugovať CI (veľká úspora času).
- Platformové tímy, ktoré pripravujú "zlaté pipelines" pre viacero projektov.
- Projekty migrujúce medzi CI vendormi.
Horší fit:
- Minimálne single-service repá, kde 20-riadkový YAML stačí.
- Tímy bez skúseností s písaním kódu v CI — krivka učenia je reálna.
- Prostredia bez možnosti spustiť container daemon (niektoré shared runneri).
Ekosystém — Daggerverse
Dagger má Daggerverse — register zdieľaných modulov, ktoré si môžete importovať do vlastného pipeline:
# Pridaj helm modul
dagger install github.com/dagger/dagger/sdk/helm
# Použitie v kode
helm_chart = dag.helm().package(source=source)
Týmto spôsobom vznikajú re-použiteľné building blocky (helm, trivy, snyk, go-releaser, ...) bez copy-paste.
Zhrnutie
Dagger prináša do CI/CD typ premyslenia, ktoré softvérový svet dávno aplikuje na aplikačný kód — verziovanie, testovanie, modularita, lokálne debugovanie. Ak váš tím bojuje s YAML peklom, má viacero projektov so zdieľanými build krokmi, alebo potrebuje prenositeľnosť medzi CI vendormi, Dagger je dnes jedna z najvyspelejších možností.
Pre malý projekt je overkill — ale pre platformový tím, ktorý udržiava CI pre 20 mikroslužieb, je to zásadná zmena.