Conteneuriser dbt Core pour la production signifie construire une image Docker qui encapsule votre runtime dbt, vos dépendances et le code de votre projet dans un artefact reproductible et portable. Que vous déployiez vers des Cloud Run Jobs, Kubernetes ou tout runtime de conteneurs, les patterns de conteneurisation sont identiques.
L’approche conteneur découple l’exécution dbt de toute plateforme d’orchestration spécifique. Votre projet dbt s’exécute de manière identique dans Cloud Run Jobs aujourd’hui et dans Kubernetes demain. L’orchestrateur devient interchangeable — il décide simplement quand le conteneur s’exécute, pas comment.
Dockerfile multi-étapes
Un build multi-étapes garde les images petites tout en assurant la reproductibilité. La première étape installe les dépendances (y compris les outils de build comme git) ; la seconde ne copie que les artefacts de runtime :
# Étape de buildFROM python:3.11-slim as builder
WORKDIR /app
# Installer les dépendances de buildRUN apt-get update && apt-get install -y --no-install-recommends \ git \ && rm -rf /var/lib/apt/lists/*
# Installer dbt avec des versions épingléesRUN pip install --no-cache-dir \ dbt-core==1.9.0 \ dbt-bigquery==1.9.0
# Copier le projet dbtCOPY dbt_project/ /app/dbt_project/COPY profiles.yml /app/profiles.yml
# Étape de runtimeFROM python:3.11-slim
WORKDIR /app
# Copier les packages installés depuis le builderCOPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packagesCOPY --from=builder /usr/local/bin/dbt /usr/local/bin/dbt
# Copier le projet dbtCOPY --from=builder /app/dbt_project /app/dbt_projectCOPY --from=builder /app/profiles.yml /app/profiles.yml
# Définir le répertoire de travail au projet dbtWORKDIR /app/dbt_project
# Commande par défautCMD ["dbt", "build", "--profiles-dir", "/app"]L’étape de build a besoin de git pour les packages qui s’installent depuis des dépôts Git. L’étape de runtime n’en a pas besoin. Les builds multi-étapes vous permettent d’inclure des outils de build sans alourdir l’image finale. Le résultat est une image plus petite, plus rapide à télécharger et avec une surface d’attaque réduite.
Épinglage des versions
Épinglez des versions exactes de dbt-core et des adaptateurs. Toujours.
dbt-core==1.9.0dbt-bigquery==1.9.0Utiliser latest ou des versions non épinglées crée des cauchemars de débogage quand le comportement change entre les exécutions. Un modèle qui a réussi lundi échoue mercredi parce qu’une mise à jour mineure a changé la résolution d’une macro. Le message d’erreur pointe vers votre SQL, pas vers le changement de version. Vous passez des heures à déboguer la logique de transformation alors que le problème est une dérive d’infrastructure.
Épinglez aussi l’image de base Python. python:3.11-slim est mieux que python:3-slim car ce dernier se met à jour silencieusement quand Python 3.12 ou 3.13 devient le tag par défaut. Pour une reproductibilité maximale, épinglez le digest : python:3.11-slim@sha256:abc123.... La plupart des équipes trouvent que l’épinglage à la version mineure (3.11) constitue le bon équilibre entre reproductibilité et charge de maintenance.
La stratégie deux-dépôts
Séparez votre projet dbt de la définition de votre image Docker. Cette approche à deux dépôts permet des cycles de développement indépendants :
dbt-project-repo/├── models/├── macros/├── tests/├── dbt_project.yml└── profiles.yml
dbt-runner-repo/├── Dockerfile├── cloudbuild.yaml└── scripts/ └── run-dbt.shLes analystes de données mettent à jour les modèles SQL sans toucher à l’infrastructure. Les ingénieurs de plateforme mettent à jour le conteneur — versions Python, versions dbt, configuration du build — sans modifier la logique de transformation. La surface de conflit entre ces deux groupes tombe à zéro.
Deux approches pour intégrer le projet dbt dans le conteneur :
Intégrer les modèles dans l’image pendant le CI/CD. Le pipeline CI clone le dépôt du projet dbt et copie les modèles dans l’image au moment du build. Cela fournit le contrôle de version — chaque image est un snapshot d’un commit spécifique. Vous pouvez revenir à une image précédente si un déploiement introduit des régressions. La plupart des équipes devraient commencer ici.
Cloner le projet dbt au runtime. Le conteneur clone le dépôt quand il démarre. Cela ajoute de la flexibilité — vous pouvez pointer vers différentes branches ou tags via des variables d’environnement — mais sacrifie la reproductibilité. La même image pourrait produire des résultats différents selon ce qui se trouve dans le dépôt lors de l’exécution. Réservez cette approche aux environnements de développement ou de staging où une itération rapide est nécessaire.
Construire et pousser vers Artifact Registry
Artifact Registry de GCP stocke vos images de conteneurs. Créez un dépôt, puis utilisez Cloud Build pour construire et pousser :
# Créer le dépôt s'il n'existe pasgcloud artifacts repositories create dbt-images \ --repository-format=docker \ --location=us-central1 \ --description="Images Docker dbt"
# Construire et poussergcloud builds submit \ --tag us-central1-docker.pkg.dev/PROJECT_ID/dbt-images/dbt-runner:v1.0.0Taguez les images avec des versions sémantiques (v1.0.0, v1.1.0) plutôt que latest. Quand un incident se produit en production, vous devez savoir exactement quelle version de l’image s’exécute. Le tag latest est mutable — il pointe vers ce qui a été poussé le plus récemment — donc il ne vous dit rien d’utile lors de la réponse à un incident.
Pour les builds automatisés, un cloudbuild.yaml dans le dépôt runner déclenche des builds sur push :
steps: - name: 'gcr.io/cloud-builders/docker' args: ['build', '-t', '${_IMAGE_TAG}', '.'] - name: 'gcr.io/cloud-builders/docker' args: ['push', '${_IMAGE_TAG}']substitutions: _IMAGE_TAG: 'us-central1-docker.pkg.dev/${PROJECT_ID}/dbt-images/dbt-runner:${SHORT_SHA}'L’utilisation de ${SHORT_SHA} comme tag lie chaque image à son commit source. Combiné avec l’approche d’intégration, cela offre une traçabilité complète : d’un conteneur en exécution au code exact qui l’a construit.
profiles.yml pour dbt conteneurisé
Le profiles.yml à l’intérieur d’un conteneur doit être agnostique à l’environnement. Utilisez env_var() pour tout ce qui varie entre les environnements :
dbt_project: target: prod outputs: prod: type: bigquery method: oauth project: "{{ env_var('GCP_PROJECT') }}" dataset: "{{ env_var('DBT_DATASET', 'analytics') }}" location: "{{ env_var('BQ_LOCATION', 'US') }}" threads: 4 timeout_seconds: 300Le paramètre method: oauth indique à dbt-bigquery d’utiliser les identifiants que l’environnement d’exécution fournit. Dans Cloud Run Jobs, c’est le compte de service attaché via Workload Identity. Dans un conteneur Docker local, ce sont vos identifiants ADC montés dans le conteneur. La même image fonctionne partout.
Les valeurs par défaut dans env_var('DBT_DATASET', 'analytics') empêchent les échecs quand une variable d’environnement n’est pas définie, tout en permettant la surcharge au moment du déploiement. Cela garde l’image portable entre développement, staging et production sans reconstruire.
Quand les conteneurs ne valent pas l’effort
Tous les déploiements dbt ne nécessitent pas la conteneurisation. Si vous exécutez dbt en local ou via dbt Cloud, la surcharge des conteneurs ajoute de la complexité sans bénéfice. Les conteneurs justifient leur place quand :
- Vous avez besoin de runs de production reproductibles avec des dépendances épinglées
- Plusieurs environnements (dev, staging, prod) doivent s’exécuter depuis le même artefact
- Votre plateforme d’orchestration (Cloud Run, Kubernetes, Dagster) attend des conteneurs
- Vous souhaitez découpler la gestion des versions dbt des machines des développeurs
Pour un développeur solo exécutant dbt build depuis la ligne de commande contre un dataset de développement, un environnement virtuel avec pip install dbt-core dbt-bigquery est plus simple et suffisant.