> ## Content Index
> Fetch the complete content index at: https://www.idriss-benbassou.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# CI/CD et Slim CI dbt : tester chaque changement automatiquement
- URL: https://www.idriss-benbassou.com/dbt-cicd-slim-ci-github-actions/
- Published: 2026-06-11T09:35:30.000Z
- Updated: 2026-06-11T09:35:30.000Z
- Author: Idriss BENBASSOU
- Tags: dbt, #rating-2

Dans [l'article précédent](https://www.idriss-benbassou.com/dbt-unit-tests-tester-logique/), on a écrit des unit tests pour protéger notre logique. Et à [l'article sur le déploiement](https://www.idriss-benbassou.com/dbt-deploiement-git-build-production/), on a mis le projet sur Git et lancé `dbt build` en production. Il manque une pièce et c'est automatisé tout ça.

On va d'abord mettre en place une CI qui marche de bout en bout, puis on l'optimisera avec le Slim CI.

## CI/CD, c'est quoi ici

Deux idées derrière ces lettres :

- **CI (intégration continue)** : à chaque pull request, une machine lance `dbt build` et les tests sur tes changements. Si quelque chose casse, la PR est bloquée. Tu ne merges jamais du code cassé, parce que c'est vérifié avant.
- **CD (déploiement continu)** : une fois la PR mergée, le projet est déployé en production automatiquement, sans intervention manuelle.

L'esprit, c'est de déplacer la vérification du moment où ça casse en prod vers le moment où tu proposes ton changement. La machine relit ton travail à ta place.

## Mettre en place sa première CI

On commence simple. Donc à chaque pull request, la CI installe dbt et lance un `dbt build` complet. On l'allègera juste après avec le Slim CI. 

Quatre étapes : mettre en place les codes secrets, le `profiles.yml`, le workflow, puis le test avec une vraie pull request.

### Étape 1 : les codes secrets dans GitHub

Les identifiants ne vont jamais dans le code. On les stocke dans les secrets du dépôt, où ils sont chiffrés.

1. Sur la page de dépôt GitHub, clique sur l'onglet Settings.
2. Dans le menu de gauche : **Secrets and variables**, puis **Actions**.
3. Onglet **Secrets**, bouton **New repository secret**.
4. Crée trois secrets, un par un (champ *Name* puis champ *Secret*), avec des noms exactement identiques à ceux qu'on utilisera ensuite :

| Name                | Valeur                                                                             |
| ------------------- | ---------------------------------------------------------------------------------- |
| SNOWFLAKE\_ACCOUNT  | ton identifiant de compte, par exemple ABCD-EF12345 (sans .snowflakecomputing.com) |
| SNOWFLAKE\_USER     | ton utilisateur Snowflake                                                          |
| SNOWFLAKE\_PASSWORD | ton mot de passe                                                                   |

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-24.png)

Une fois enregistré, tu ne peux plus afficher la valeur d'un secret, seulement la remplacer. C'est voulu. Si tu te trompes, tu recrées le secret avec la même clé.

### Étape 2 : le profiles.yml pour la CI

Le runner GitHub n'a pas le dossier profile `profiles.yml`. Donc on crée un `profiles.yml` à la racine du projet (au même niveau que `dbt_project.yml`), qui ne contient que des `env_var(...)`, donc aucun identifiant en clair :

```yaml
dbt_projet:
  target: ci
  outputs:
    ci:
      type: snowflake
      account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
      user: "{{ env_var('SNOWFLAKE_USER') }}"
      password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
      role: dbt_role
      warehouse: dbt_wh
      database: analytics
      schema: ci
      threads: 4

```

Le nom `dbt_projet` doit correspondre exactement au champ `profile:` de ton `dbt_project.yml`. Le schéma `ci` isole les builds de CI donc ils ne touchent ni ton `dev` ni ta `prod`.

### Étape 3 : le workflow

Le workflow vit dans `.github/workflows/ci.yml`, à la racine du dépôt. Ce dossier n'existe pas par défaut, c'est à toi de le créer. Comme il commence par un point, ton explorateur Windows ne l'affiche pas forcément, mais dans VS Code tu peux taper directement le chemin complet `.github/workflows/ci.yml` à la création du fichier, et les dossiers se créent tout seuls.

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-25.png)

```yaml
name: dbt CI

on:
  pull_request:
    branches: [main]

jobs:
  ci:
    runs-on: ubuntu-latest
    env:
      SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
      SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
      SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - run: pip install dbt-snowflake

      - run: dbt deps

      - run: dbt build --profiles-dir .

```

Ce qui se passe, étape par étape : 

A chaque pull request vers `main`, GitHub démarre une machine, récupère ton code, installe Python puis dbt, ensuite installe les packages (`dbt deps`), et lance `dbt build`. 

Le bloc `env:` injecte tes infos de connexions dans des variables d'environnement, que le `profiles.yml` lit via ses `env_var`. Et `--profiles-dir .` dit à dbt de chercher le `profiles.yml` à la racine du projet.

💡

`ubuntu-latest` est un runner hébergé par GitHub donc il démarre une machine Linux dans le cloud, exécute tes étapes, puis la détruit. Tu n'as rien à configurer. C'est gratuit dans les limites incluses.

### Étape 4 : tester avec une pull request

Ton workflow se déclenche sur les pull requests vers `main`. Tu ne peux pas ouvrir une PR de `main` vers `main`, il te faut donc une branche source.

D'abord, pousse ces fichiers sur ta branche de travail :

```powershell
git add -f profiles.yml
git add .github/workflows/ci.yml
git commit -m "Mise en place de la CI dbt"
git push

```

Ensuite, on va créér une branche, faire un petit changement ou ajouter un commentaire pour ouvrir aprés une pull request :

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-26.png)

```powershell
git checkout -b test-ci
# On ajoute un commentaire dans un modèle
git add .
git commit -m "test de la CI"
git push -u origin test-ci

```

Sur GitHub, clique sur "Compare & pull request" pour ouvrir la PR de `test-ci` vers `main`. 

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-27.png)

Le workflow se lance, et tu vois le check apparaître directement dans la PR et dans l'onglet Actions. 

Quand il passe au vert, tu peux merger car le `ci.yml` arrive alors sur `main` et protège désormais toutes les futures pull requests.

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-28.png)

## Le problème du "tout reconstruire"

Notre CI marche, mais elle reconstruit tout le projet à chaque PR. Sur un petit projet, ça passe mais sur un projet de centaines de modèles, c'est interminable et ça coûte cher en compute, alors que la PR n'a touché que deux modèles.

Le Slim CI règle ça car il ne construit et ne teste que ce qui a changé, plus ce qui en dépend en aval. C'est là que `state:modified` entre en jeu.

## Le Slim CI avec state:modified

Le principe de `state:modified` : il compare l'état actuel du projet à un manifest de référence, passé avec `--state`. En CI, ce manifest de référence, c'est celui de ta dernière production. La commande devient :

```powershell
dbt build --select state:modified+ --defer --state ./prod-artifacts --profiles-dir .

```

- `**state:modified+**` : seulement les modèles modifiés depuis la prod, et tout ce qui en dépend en aval (le `+`).
- `**--state ./prod-artifacts**` : le dossier contenant le `manifest.json` de ta dernière prod, qui sert de point de comparaison.
- `**--defer**` : pour les modèles non reconstruits, dbt pointe vers la version qui existe déjà en prod, au lieu d'essayer de les rebuild. C'est ce qui permet de ne traiter que ta poignée de modèles tout en gardant un `ref()` qui fonctionne.

💡

Le `manifest.json` est généré par dbt à chaque exécution, dans `target/`. La CI le récupère avant de lancer le build. Sans ce manifest de référence, `state:modified` n'a rien à comparer.

Le conseil pratique : commence avec le `dbt build` complet qu'on a mis en place, vérifie que toute la chaîne fonctionne (secrets, profiles, build vert), et ajoute le `state:modified+ --defer` .

## Le déploiement (le CD)

La CI valide mais le CD déploie. Une fois donc la PR mergée dans `main`, on veut que la prod se reconstruise toute seule. Pour ça, un second workflow, déclenché cette fois sur les push vers `main` (et un merge de pull request, c'est justement un push sur `main`).

Crée `.github/workflows/cd.yml` :

```yaml
name: dbt CD

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
      SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
      SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - run: pip install dbt-snowflake

      - run: dbt deps

      - run: dbt build --target prod --profiles-dir .

      - name: Sauvegarder le manifest de prod
        uses: actions/upload-artifact@v4
        with:
          name: prod-manifest
          path: target/manifest.json
```

Deux différences avec la CI : 

Le déclencheur est `push` sur `main` (et non `pull_request`), et la commande utilise `--target prod`. C'est le même fichier `profiles.yml`, mais une cible différente car la CI écrit dans le schéma `ci`, le CD dans le schéma `prod`. 

Les secrets sont les mêmes ici par simplicité mais réalité souvent la prod utilise un rôle, un utilisateur ou même un compte dédié donc tu créerais alors des secrets séparés pour la prod.

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-29.png)

La dernière étape **sauvegarde le `manifest.json`** produit par ce run de prod, comme artefact GitHub. C'est exactement le manifest dont le Slim CI a besoin : à la prochaine pull request, ton workflow de CI le télécharge (avec `actions/download-artifact`) dans `./prod-artifacts`, et `state:modified` a enfin son point de comparaison. La boucle est bouclée : la prod produit le manifest, la CI s'en sert pour ne reconstruire que ce qui a changé. Chaque déploiement prépare le terrain du suivant.

## dbt Cloud

Tout ce qu'on vient de construire à la main, dbt Cloud le propose en intégré. Si tu utilises dbt Cloud, tu connectes ton dépôt, tu configures un job CI, et il gère pour toi le manifest de prod, le `--defer` et le `state:modified`. Pas de YAML GitHub ou actions à écrire.

Le choix dépend du contexte car GitHub Actions (ou GitLab CI) te donne le contrôle total et reste gratuit sur de petits volumes mais dbt Cloud enlève toute cette complexité au prix d'un abonnement. Dans les deux cas, le principe du Slim CI reste le même.

## 

Il ne reste qu'une brique à connaitre pour faire tourner tout ça proprement et vite en production et c'est l'orchestration et la performance et donc planifier les exécutions, les enchaîner avec le reste de ta stack, et optimiser temps et coût sur Snowflake.

---

## Aller plus loin

Cet article fait partie de la formation dbt complète, du premier modèle au déploiement en production.

👉 [Suivre toute la formation dbt](https://www.idriss-benbassou.com/formation-dbt/)

dbt tourne sur Snowflake dans ce parcours. Pour maîtriser le socle (warehouses, rôles, ingestion) :

👉 [Accéder à la Formation Snowflake](https://www.idriss-benbassou.com/formation-snowflake/)

Et pour t'entraîner en conditions d'examen, la certification dbt Analytics Engineering teste précisément la CI/CD et le Slim CI.

👉 [Préparer la certification dbt sur DataCertification.fr](https://datacertification.fr/certifications/dbt-analytics-engineer?ref=idriss-benbassou.com)

Tu veux que je t'accompagne sur ton projet data (Snowflake, dbt, modélisation, industrialisation) ?

👉 [Réserver un appel de 30 minutes](https://calendly.com/idriss-benbassou-datavio/30min?ref=idriss-benbassou.com)

---

## Questions fréquentes

#### Où mettre les identifiants Snowflake pour une CI dbt ?

Dans les secrets du dépôt GitHub, jamais dans le code. On va dans Settings, puis Secrets and variables, puis Actions, et on crée un secret par identifiant (compte, utilisateur, mot de passe etc...). Le workflow les injecte ensuite dans des variables d'environnement, que le profiles.yml lit via env\_var. Les secrets sont chiffrés et non relisibles après enregistrement.

#### Faut-il commiter le profiles.yml pour la CI ?

Le runner n'a pas le dossier de config, donc dbt a besoin d'un profiles.yml. La règle "ne pas commiter profiles.yml" vise les fichiers contenant des identifiants en clair. Un profiles.yml qui n'utilise que des env\_var ne contient aucun secret, il est donc safe à versionner. Attention juste à ce qu'il ne soit pas exclu par le .gitignore, sinon il n'arrivera pas sur le runner.

#### C'est quoi le Slim CI en dbt ?

Le Slim CI ne construit et ne teste que les modèles modifiés depuis la dernière production au lieu de reconstruire tout le projet.

#### C'est quoi --defer en dbt ?

L'option --defer indique à dbt que, pour les modèles non sélectionnés, il doit pointer vers la version qui existe déjà en production plutôt que de les reconstruire. Combinée à state:modified, elle permet de ne traiter que tes modèles modifiés tout en gardant des ref() qui fonctionnent, puisque les modèles en amont sont résolus vers la prod.