CI上でdbtを動かすとき、最大の落とし穴は「秘密情報の露出」と「環境切替の一貫性不足」です。この記事は、dbtの標準機能(env_var、profiles.yml、target)と一般的なCIのシークレットストアを使い、開発・検証・本番を安全に切り替える最小構成を示します。
Analytics Engineer試験で頻出の論点も併せて整理します。特に、env_varの既定値、profiles.ymlのJinja、target.nameの使い分け、CIログのマスキング、そして環境ごとのスキーマ切替がチェックされがちです。
dbtはJinja経由で環境変数を参照できます。env_var('KEY', 'default')の形式で、CIから注入した値やローカルの環境変数を安全に取り込みます。defaultを省くと、未定義時にコンパイルエラーになり、事故を防げます。
接続設定はprofiles.ymlに置き、ターゲット(target)でdev/stg/prodなどの出力先を切り替えます。モデル側はtarget.nameやtarget.schemaを参照できます。秘密情報(パスワード、トークン、接続文字列)は必ずCIやdbt Cloudのシークレットストアから環境変数として注入し、リポジトリに書かないのが原則です。
env_varの基本用法(モデル/Jinjaとdbt_project.yml)
-- models/stg_orders.sql
{{
config(
schema=env_var('DBT_SCHEMA', target.schema) # CIで上書き、未設定ならtarget.schema
)
}}
select *
from {{ source('raw', 'orders') }}
-- dbt_project.yml(抜粋)
name: my_dbt
version: 1.0.0
config-version: 2
models:
+materialized: view
vars:
environment: "{{ env_var('DBT_ENVIRONMENT', 'dev') }}"環境ごとの差分はprofiles.ymlで吸収するのが定石です。ターゲットはenv_varで外から指定できるようにし、スキーマやデータベース名にも環境サフィックスを付けます。これによりCIとローカルの切替が対称になります。
スキーマ名の分岐はgenerate_schema_nameマクロやconfig(schema=...)で一元管理。SQL本文にif文を書かず、構成で切り替えるとメンテナンス性が高く、試験でも模範解答に近い扱いです。
Snowflake例: profiles.ymlで環境とシークレットを外だし
my_profile:
target: "{{ env_var('DBT_TARGET_NAME', 'dev') }}"
outputs:
dev:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
role: "{{ env_var('SNOWFLAKE_ROLE', 'DEV_ROLE') }}"
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE', 'DEV_WH') }}"
database: "{{ env_var('SNOWFLAKE_DATABASE', 'ANALYTICS_DEV') }}"
schema: "{{ env_var('DBT_SCHEMA', 'dbt_dev') }}"
ci:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
role: "{{ env_var('SNOWFLAKE_ROLE', 'CI_ROLE') }}"
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE', 'CI_WH') }}"
database: "{{ env_var('SNOWFLAKE_DATABASE', 'ANALYTICS_CI') }}"
schema: "{{ env_var('DBT_SCHEMA', 'dbt_ci') }}"
prod:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
role: "{{ env_var('SNOWFLAKE_ROLE', 'PROD_ROLE') }}"
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE', 'PROD_WH') }}"
database: "{{ env_var('SNOWFLAKE_DATABASE', 'ANALYTICS') }}"
schema: "{{ env_var('DBT_SCHEMA', 'dbt') }}"CIが提供するシークレットストア(GitHub Actions Secrets、GitLab CI/CD Variables、CircleCI Contexts等)は、環境変数として注入され、ログで自動マスキングされます。dbt側はenv_varで受け取るだけで済み、単純で安全です。
大規模・高規制環境ではHashiCorp Vault等の専用ストアを併用し、CIから短命トークンを取得して注入する設計が一般的です。どの場合も、リポジトリやprofiles.ymlに秘密値を直書きしないこと、権限を最小化すること、ログに出さないことが原則です。
| 方式 | 注入方法 | マスキング/権限制御 | 適合シーン |
|---|---|---|---|
| GitHub Actions Secrets | env: KEY: ${{ secrets.KEY }} | 自動マスキング・環境/環境保護 | 中小規模〜一般用途のCI |
| GitLab CI/CD Variables | variables/Masked/Protected | マスク/保護ブランチ/環境スコープ | 環境ごとの厳格制御が必要な場合 |
| dbt Cloud(環境変数) | 環境/Jobに設定→Jinjaでenv_var | 暗号化保存・UI制御 | dbt Cloudで一元管理したい場合 |
| HashiCorp Vault併用 | CIがOIDC等で短命トークン取得→env注入 | きめ細かなポリシー/監査 | 高規制・ゼロトラスト要件 |
GitHub Actionsでの安全な注入例(envに束ねる)
env:
DBT_TARGET_NAME: ci
DBT_ENVIRONMENT: ci
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}
SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}最小構成は、依存解決→コンパイル/検証→実行→テストの直列です。ターゲットはciに固定し、スキーマ/DBはCI専用リソースを使います。実行ログはマスクされ、SQL内に秘密値が含まれない設計にします。
以下はGitHub Actionsでdbt-snowflakeを動かす例です。各CIのYAMLでも考え方は同じです。
CIでのシークレット注入と実行フロー
GitHub Actions: dbt CIワークフロー(抜粋)
name: dbt-ci
on:
pull_request:
branches: [ main ]
jobs:
run-dbt:
runs-on: ubuntu-latest
env:
DBT_TARGET_NAME: ci
DBT_ENVIRONMENT: ci
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}
SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
DBT_SCHEMA: dbt_ci_${{ github.actor }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dbt adapter
run: |
python -m pip install --upgrade pip
pip install dbt-snowflake
- name: Verify deps and connection
run: |
dbt deps
dbt debug --target ci --no-write-json
- name: Compile
run: dbt compile --target ci --warn-error
- name: Run and test
run: |
dbt run --target ci --fail-fast --select state:modified+
dbt test --target ci --fail-fastCIのset -xやechoで環境変数を出力するとマスクが効かないケースがあります。秘密値は参照しない・ログに書かない・画面に出さないが基本です。dbtのログは接続テスト結果などを出しますが、CI側でマスキングを有効化し、debugの詳細出力を最小限にします。
Jinja内で必須の環境変数が欠落している場合は、早期にコンパイルエラーを発生させて停止させます。これにより、誤った接続先や匿名接続で実行してしまう事故を避けられます。
秘密値を出さないための実用スニペット
# シェル: コマンドエコー抑止
set +x
# Jinja: 必須環境変数の検査(任意のmacroやmodel冒頭で)
{% if not env_var('SNOWFLAKE_PASSWORD', none) %}
{{ exceptions.raise_compiler_error('Missing SNOWFLAKE_PASSWORD') }}
{% endif %}
# dbt実行時は必要最小限の出力
# 例: 失敗時にのみ詳細を参照する運用
dbt run --target ci --fail-fast
# 必要なら --log-level warn などで情報量を絞るAnalytics Engineer試験では、環境変数と構成の責務分離がよく問われます。環境依存の分岐をSQL本文に持ち込まず、profiles.ymlとtargetで切り替える、秘密はCIやdbt Cloudのセキュアストアから注入、env_varの既定値を理解している、が鍵です。
また、開発から本番まで同一パイプラインで構成を差し替えられるか、ログ/メタデータに秘密が出ていないか、といった運用面の判断問題も出題されます。
環境を切り替えて実行する例(コマンド)
# CIターゲットで実行
DBT_TARGET_NAME=ci DBT_ENVIRONMENT=ci dbt run --target ci
# ローカル開発(デフォルトのdevにフォールバック)
dbt run # profiles.ymlのtarget: dev が使われることを想定
# 明示的にprodを指定(本番は保護ブランチ/承認必須が望ましい)
DBT_TARGET_NAME=prod dbt test --target prodAnalytics Engineer
問題 1
Snowflakeに接続するdbt CoreプロジェクトをCIで実行する。パスワードをリポジトリに含めず、ブランチごとにスキーマを切り替えたい。最も適切な組み合わせはどれか?
正解: A
秘密はCIのシークレットストアから環境変数で注入し、profiles.ymlでenv_varを使って参照するのが標準かつ安全。スキーマはDBT_SCHEMA等で外だしし、ターゲットはciに固定して構成で切り替える。varsにパスワードを置く・SQLで分岐・手作業変更は非推奨。
env_varとvarsの違いは?どちらに秘密を置くべき?
env_varはOS環境変数をJinjaから参照する仕組み、varsはdbtのユーザー変数です。秘密値はvarsではなく、CIやdbt Cloudのシークレットストア→環境変数→env_varで参照するのが原則です。
dbt Cloudではどう環境変数を使う?
EnvironmentやJobの設定で環境変数を登録し、モデルやdbt_project.yml/profiles.ymlのJinjaでenv_var('KEY')として参照します。値はUIで管理され、リポジトリには含まれません。
未設定の環境変数を検知して安全に失敗させるには?
env_var('KEY')のdefaultを省略すると未定義でコンパイルエラーになります。あるいはJinjaで検査し、exceptions.raise_compiler_errorで明示的に停止させる方法も有効です。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
dbt Model の基礎: SQL で定義する変換の最小単位
Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...
dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点
dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...
dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点
dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...
dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト
Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....
dbt_project.yml の読み方:主要設定と命名を最短で掴む
dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...