Dagger : CI as code portable

À la fin de ce tutoriel, vous saurez écrire un pipeline CI en Python avec Dagger, le rejouer à l’identique sur laptop et dans GitHub Actions, cacher les couches (BuildKit), et esquisser un publish ECR en ca-central-1. Angle DEH : un module versionné, runners = wrappers minces, cleanup lab obligatoire.

Niveau : Intermédiaire · Temps estimé : 55–75 min · Versions cibles : Dagger CLI 0.18+ · SDK Python 3.12 · Docker Engine · GitHub Actions (dagger/dagger-for-github) · AWS CLI v2 · Dernière vérification : 2026-09-11 · Region : ca-central-1

Slug : wow-dagger-ci · Série : WOW (39/50) · Mot-clé SEO : dagger ci · Publish : READY (feu vert Maître — push centralisé)

← Précédent : Buildkite agents · → Suivant : Nix flakes / devshells · Aussi : GitHub Actions reusable · DevSecOps pipeline · Trivy en CI · GHA OIDC AWS

Prérequis

  • Docker Engine local (l’Engine Dagger orchestre des conteneurs)
  • Python 3.12+ pour lire le module (SDK via dagger init)
  • Dépôt GitHub pour le wrapper Actions — Git
  • Compte AWS lab (AWS_PROFILE=lab) si publish ECR — Démarrer avec AWS
  • Bases CI — Buildkite · Jenkins

Coût estimé : 0 € en local. ECR lab : < 1 € (repo vide quelques heures en ca-central-1 puis delete). Pas de NAT, EKS, runners payants.

export AWS_PROFILE=lab
export AWS_DEFAULT_REGION=ca-central-1
docker info >/dev/null
aws sts get-caller-identity

Ce que nous allons construire

Dagger CI as code (WOW 39/50) — ca-central-1
  ├── Pourquoi Dagger (code > YAML, local = CI)
  ├── Architecture : CLI → Engine → Functions
  ├── Install CLI + dagger init --sdk=python
  ├── Mini-app + functions lint / test / build
  ├── dagger call en local (fail fast)
  ├── Wrapper GitHub Actions mince
  ├── Sketch ECR ca-central-1 + OIDC
  └── Anti-patterns + quiz + FAQ

(Schéma — alt : « Module Dagger Python : lint/test/build en conteneurs ; laptop et GitHub Actions appellent les mêmes functions ; publish optionnel ECR ca-central-1 ».)

Étape 1 — Pourquoi Dagger plutôt qu’un YAML CI

Le push-and-pray (commit, 8 min, typo YAML, recommencer) coûte cher. GitHub Actions, GitLab, Jenkins, Buildkite décrivent surtout ça tourne. La logique (lint, pytest, image, scan) dérive entre laptop et runners.

Dagger inverse ça : pipeline = module (Go, Python, TypeScript) + functions typées. L’Engine (BuildKit) exécute chaque step en conteneur. dagger call test --source=. sur votre machine est le job CI.

Critère YAML CI Dagger
Langage YAML + scripts Python / Go / TS
Repro locale Approximative Identique (dagger call)
Cache Artefacts / actions Content-addressed
Portabilité Syntaxe vendor Un module, N wrappers

DEH : GitHub/GitLab/Buildkite pour triggers, PR, secrets d’org. La recette vit dans Dagger — même esprit que Conftest en CI : code reviewable.

Étape 2 — Architecture : CLI, Engine, module

[ Laptop / GitHub Actions / GitLab ]
        │  dagger call <fn> --source=.
        ▼
[ Dagger CLI ]  → GraphQL →  [ Engine / BuildKit ]
        ├── lint (python:3.12-slim + ruff)
        ├── test (pytest)
        ├── build (image app)
        └── publish → ECR ca-central-1 (option)
  • CLI : client vers l’Engine.
  • Engine : conteneurs + dédup des couches.
  • Module : dagger.json + SDK ; dagger call --help liste les functions.
  • Wrapper CI : checkout + dagger/dagger-for-github. Zéro logique métier dans le YAML.

Dagger Cloud (traces) est optionnel. Lab : Engine local, pas d’abonnement.

Étape 3 — Installer la CLI

curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR="${HOME}/.local/bin" sh
export PATH="${HOME}/.local/bin:${PATH}"
dagger version

Attendu : 0.18+. Docker doit répondre (docker ps). Sans Engine, dagger call échoue — voulu.

En CI, pinnez la version de l’action. En local, dagger version doit matcher.

Étape 4 — Mini-app + dagger init

mkdir -p ~/deh-dagger-lab/tests && cd ~/deh-dagger-lab
git init -q

cat > app.py << 'EOF'
"""Mini-app lab DEH — ne pas copier en prod."""

def ping() -> str:
    return "ok"

if __name__ == "__main__":
    print(ping())
EOF

cat > tests/test_app.py << 'EOF'
from app import ping

def test_ping():
    assert ping() == "ok"
EOF

printf 'pytestn' > requirements.txt
dagger init --sdk=python --name=ci
dagger develop
dagger call --help

init pose dagger.json + package Python. develop synchronise le SDK. Les functions utiles arrivent à l’étape 5.

Étape 5 — Functions lint / test / build

Remplacez le squelette (src/ci/__init__.py ou src/main.py selon le scaffold — suivez init) :

import dagger
from dagger import dag, function, object_type


@object_type
class Ci:
    def _py(self, source: dagger.Directory) -> dagger.Container:
        return (
            dag.container()
            .from_("python:3.12-slim")
            .with_directory("/src", source)
            .with_workdir("/src")
        )

    @function
    async def lint(self, source: dagger.Directory) -> str:
        return await (
            self._py(source)
            .with_exec(["pip", "install", "--quiet", "ruff"])
            .with_exec(["ruff", "check", "."])
            .stdout()
        )

    @function
    async def test(self, source: dagger.Directory) -> str:
        return await (
            self._py(source)
            .with_exec(["pip", "install", "--quiet", "-r", "requirements.txt"])
            .with_exec(["pytest", "-q"])
            .stdout()
        )

    @function
    def build(self, source: dagger.Directory) -> dagger.Container:
        return (
            self._py(source)
            .with_exec(["pip", "install", "--quiet", "-r", "requirements.txt"])
            .with_default_args(["python", "app.py"])
        )
  1. source: Directory : le repo monté, pas un checkout magique vendor.
  2. Pin d’image en prod (python:3.12-slim@sha256:…).
  3. Pas de secrets dans le module : type dagger.Secret à l’étape 7.

Étape 6 — Exécuter en local (le vrai CI)

dagger call lint --source=.
dagger call test --source=.
dagger call build --source=.
dagger call build --source=. export --path=./hello.tar

Cassez ping() (return "ko"), relancez test : rouge en ~20 s, sans push. Revert. 2ᵉ run : cache Engine, plus vite.

Contrat WOW : le laptop est un runner. Vert ici ⇒ le wrapper Actions n’appelle que la même function.

Étape 7 — Wrapper GitHub Actions (mince)

.github/workflows/ci.yml :

name: ci
on: [push, pull_request]
jobs:
  dagger:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: dagger/dagger-for-github@v8
        with:
          version: "0.18.12"
          verb: call
          args: test --source=.

Dupliquez le step pour lint puis build (même version). GitLab : script: dagger call test --source=.. Jenkins / Buildkite : un step dagger call. La recette ne bouge pas.

Sketch publish ECR ca-central-1

aws ecr create-repository --repository-name deh-lab/dagger-hello 
  --region ca-central-1 --image-scanning-configuration scanOnPush=true
aws ecr get-login-password --region ca-central-1

OIDC : GitHub Actions OIDC AWS — rôle court, pas de clés longues. Passez le password comme Secret (--password=env:ECR_PASSWORD). Tag : ACCOUNT.dkr.ecr.ca-central-1.amazonaws.com/deh-lab/dagger-hello:${GITHUB_SHA}.

aws ecr delete-repository --repository-name deh-lab/dagger-hello 
  --region ca-central-1 --force
rm -f hello.tar

Étape 8 — Cache, secrets, modules

  • Cache : l’Engine hash with_exec + fichiers. pip install inchangé = gratuit au 2ᵉ call. Ne prunez pas « pour voir ».
  • Secrets : dagger.Secret ; pas d’echo / set -x. Tokens, AWS_* : org/SSM, hors Git.
  • Modules distants : dagger -m github.com/org/mod@vX call … — pinnez le tag.
  • Scan : Trivy après buildDevSecOps.

Étape 9 — Anti-patterns

Double source YAML et Dagger ; latest partout ; clés IAM dans env: ; region us-east-1 ; Engine partagé dirty ; image root en prod ; ECR / hello.tar oubliés.

Erreurs fréquentes

Erreur Impact Correction
Docker daemon down dagger call KO docker info d’abord
Function introuvable CLI confuse dagger develop + --name=ci
Vert local, rouge CI Source mal monté --source=. après checkout
Cache « cassé » Minutes + $ Pas de docker system prune -a
Password en args Fuite logs dagger.Secret / env:ECR_PASSWORD
Region us-east-1 Drift DEH Forcer ca-central-1
YAML Actions 200 lignes Logique hors module Wrapper mince

Check-list DEH

  • [ ] dagger version 0.18+ ; Docker OK
  • [ ] Module ci : lint / test / build
  • [ ] dagger call test --source=. rouge puis vert sans push
  • [ ] Workflow = wrappers dagger call pinés
  • [ ] Secrets hors Git ; OIDC si ECR
  • [ ] ECR / tar détruits ; region ca-central-1

Quiz (5 questions)

1. Dagger sert surtout à :
– A. Remplacer Git par un daemon
– B. Écrire le pipeline en code, local = CI
– C. Imposer un SaaS payant

2. YAML GitHub Actions idéal :
– A. 400 lignes de run:
– B. Checkout + dagger call (wrapper mince)
– C. Trois docker build copiés

3. Qui exécute les steps ?
– A. Un runner Windows obligatoire
– B. Le Dagger Engine (conteneurs / BuildKit)
– C. SSH vers prod

4. Region lab ECR DEH :
– A. us-east-1
– B. ca-central-1
– C. eu-west-3

5. Mot de passe registry :
– A. En clair dans args:
– B. dagger.Secret / env:ECR_PASSWORD
– C. Dans le README

Réponses : 1‑B · 2‑B · 3‑B · 4‑B · 5‑B

FAQ

Dagger remplace GitHub Actions ?

Non. Actions / GitLab / Buildkite / Jenkins déclenchent. Dagger exécute la recette. Vous gardez PR checks, protections de branche, OIDC d’org.

Python, Go ou TypeScript ?

Celui que l’équipe review. Python = lisible DevOps. Go = types stricts. Un SDK par module.

Faut-il Dagger Cloud ?

Non pour ce lab. Cloud = traces d’équipe. Engine local suffit.

Self-hosted runners ?

Oui : Buildkite ou GHA self-hosted avec Docker. Dagger = portabilité ; l’agent = VPC ca-central-1.

Cache laptop ↔ CI ?

Pas partagé (machines différentes). Chaque Engine cache chez lui. Gain : même graphe, hits dès le 2ᵉ run ici.

Nix vs Dagger ?

Nix flakes fige le devshell. Dagger fige le pipeline. Complémentaires.

Par où commencer en équipe ?

Un module lint+test, wrapper Actions, pin de version. Puis build / publish / Trivy. Pas 12 functions jour 1.

Pour aller plus loin

Maillage série WOW

← Précédent Buildkite : agents self-hosted
→ Suivant Nix flakes : dev shells reproductibles
Aussi GHA reusable · OIDC AWS · DevSecOps · Trivy · Conftest · Jenkins

Meta publication (SEO)

  • Title SEO : Dagger CI as code : pipelines portables Python (guide FR)
  • Meta description : Dagger : CI as code portable. Module Python, dagger call local = CI, wrapper GitHub Actions, publish ECR ca-central-1, cache, secrets, FAQ et quiz DEH.
  • Focus keyword : dagger ci
  • Secondary : Dagger CI as code, dagger call, Dagger Python SDK, GitHub Actions Dagger, ECR ca-central-1
  • Image : assets/web/devopelastichayway/cover-wow-dagger-ci-1200x630.webp (à générer)
  • Catégorie : WOW / CI-CD · Niveau : Intermédiaire
  • URL cible : https://devopelastichayway.com/tutoriels/wow-dagger-ci/
  • Post live : N/A (nouveau) · slug wow-dagger-ci · Publish : READY (feu vert Maître — push WP-CLI centralisé, parent /tutoriels/ 5463)

← Catalogue Tutoriels

Pour aller plus loin — hubs live

Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.