dbt入門(第7弾 後編): Snowflake × GitHub ActionsでCI/CD完全自動化 - 実践・運用編
🚀 はじめに
この記事は dbt入門(第7弾 後編 / CI/CD実践・運用編) です。
前編[1]では、3つのdbt運用アプローチの比較、GitHub/GitHub Actionsの基礎知識、Snowflake鍵ペア認証(JWT)の実装、PR検証(CI)の実装(Slim CI) を解説しました。
後編では、本番デプロイ(CD)の実装、失敗時のリカバリー・ロールバック、運用Tips を扱います。
前編と同様に、GitHub Actions未経験でも追えるように「概念 → 手順 → 実装例」の順で進めます。
前編
第7弾 前編:CI/CD基礎・準備編
https://zenn.dev/yujmatsu/articles/20260104_sf_dbt_cicd_part1
後編でわかること
- 本番デプロイ(CD)の実装(mainマージ時に自動デプロイ)
- 承認ゲートの設定(GitHub Environmentsで本番デプロイ前に承認)
- 失敗時のリカバリー・ロールバック(Snowflake Time Travel活用)
- 実装コードまとめ(CDワークフロー、メタデータ記録マクロなど)
- ハマりどころ7つ(実務で詰まりやすいポイントと対処法)
- 運用Tips(コスト最適化、セキュリティ、CI高速化、docs自動公開)
🚢 本番デプロイ(CD)の実装
mainブランチへマージされたら、本番環境へ自動デプロイします。

図1: mainマージ→GitHub Actions CD起動→承認ゲート(任意)→Snowflake本番へデプロイ→通知の流れ。
CDの目的
- 手動デプロイの排除:mainマージ後、自動で本番環境へ反映
- デプロイの一貫性:毎回同じ手順で実行され、ヒューマンエラーを減らす
- 監査証跡:いつ、誰が、どのコミットを反映したかが残る
- リリース高速化:PRマージから反映までを短縮(運用上の待ちを減らす)
Slim CIを成立させるための「初回デプロイ」
前編で紹介したSlim CIの --defer は、未変更モデルを 既存の参照先(多くは本番) に向けます。
そのため、最初に一度は本番(参照先)にモデルが存在する状態を作る必要があります。
- 初回だけは手動でもOK(例:ローカルで
dbt build --target prod) - 以降は、このセクションのCDで mainマージをトリガーに自動反映できます
補足
初回から完全自動にしたい場合は、後述のCDワークフローをworkflow_dispatch(手動実行)にも対応させると便利です。
ワークフロー設計(トリガー: push to main)
.github/workflows/dbt_cd.yml を作成します。
前編のCIと同じく、profiles.yml.templateをコピーして使う構成にしておくと管理がラクです。
ポイント
- 承認ゲートを使う場合は、
environment: productionを指定します(後述)- 連続マージが起きてもデプロイが競合しないよう、
concurrencyを入れるのが安全です
cat > .github/workflows/dbt_cd.yml << 'EOF'
name: dbt CD (Production Deployment)
on:
push:
branches: [main]
paths:
- "models/**"
- "tests/**"
- "macros/**"
- "seeds/**"
- "snapshots/**"
- "dbt_project.yml"
- "packages.yml"
- "profiles.yml.template"
- ".github/workflows/**"
permissions:
contents: read
concurrency:
group: dbt-cd-production
cancel-in-progress: false # 本番はキャンセルせず順番に実行する想定
jobs:
dbt-cd:
runs-on: ubuntu-latest
environment: production # 承認ゲート(任意): 不要ならこの行を削除
env:
DBT_PROFILES_DIR: .
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
SNOWFLAKE_PRIVATE_KEY_PATH: rsa_key.p8
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dbt (pin recommended)
run: |
pip install --upgrade pip
pip install "dbt-core==1.10.*" "dbt-snowflake==1.10.*"
- name: Create profiles.yml from template
run: cp profiles.yml.template profiles.yml
- name: Setup Snowflake private key
run: |
printf "%s" "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > rsa_key.p8
chmod 600 rsa_key.p8
- name: Install dbt packages
run: dbt deps
- name: Pre-deployment check (compile)
run: dbt compile --target prod
# ※「デプロイ前テスト」は運用方針により追加(本文で解説)
# - name: Pre-deployment smoke tests (optional)
# run: dbt test --target prod --select tag:smoke
- name: Deploy (dbt build)
id: dbt_build
run: |
# フルビルド(全モデル・全テストを実行)
dbt build --target prod
- name: Post-deployment verification
if: success()
run: |
echo "✅ Deployment completed successfully at $(date)"
echo "📦 Commit: $GITHUB_SHA"
echo "👤 Actor: $GITHUB_ACTOR"
echo "🎯 Target: prod"
echo "🗄️ Database: $SNOWFLAKE_DATABASE"
- name: Deployment failed
if: failure()
run: |
echo "❌ Deployment failed at $(date)"
echo "📦 Commit: $GITHUB_SHA"
echo "👤 Actor: $GITHUB_ACTOR"
exit 1
- name: Clean up private key
if: always()
run: rm -f rsa_key.p8 profiles.yml
# Slack通知(任意): 使う場合は Secrets に SLACK_WEBHOOK_URL を追加
- name: Notify Slack (success)
if: success()
uses: slackapi/slack-github-action@v1.27.0
with:
payload: |
{
"text": "✅ dbt Production Deployment Successful",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*dbt Production Deployment Successful* ✅\n\n- Commit: `${{ github.sha }}`\n- Actor : `${{ github.actor }}`\n- Run : https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Notify Slack (failure)
if: failure()
uses: slackapi/slack-github-action@v1.27.0
with:
payload: |
{
"text": "❌ dbt Production Deployment Failed",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*dbt Production Deployment Failed* ❌\n\n- Commit: `${{ github.sha }}`\n- Actor : `${{ github.actor }}`\n- Run : https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\nログを確認し、必要ならロールバックを検討してください。"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
EOF
ワークフローの解説
1) トリガー設定(push to main)
on:
push:
branches: [main]
- mainへのpush(≒PRマージ) でCDが動きます
-
paths:を入れることで、READMEだけの更新などで不要なデプロイが走るのを防げます
2) 承認ゲート(environment: production)
environment: production
この行を追加すると、本番デプロイ前に人間の承認が必要になります(後述)。
学習用で不要なら削除してOKです。
3) デプロイの中心は dbt build に寄せる
- name: Deploy (dbt build)
run: dbt build --target prod
-
dbt buildは run + test をまとめて行います -
dbt run→dbt testを分けるより、ワークフローをシンプルに保ちやすいです
補足
「デプロイ前にチェックしたい」場合は、compileを必ず入れるのがおすすめです(構文エラーを早期に潰せる)。
4) 連続マージ対策(concurrency)
concurrency:
group: dbt-cd-production
cancel-in-progress: false
- 連続でmainにマージが起きても、本番デプロイが競合しにくい構成です
- 本番は「途中キャンセルより順次実行」を優先することが多いので
falseにしています
(任意)デプロイ前テストの考え方
「デプロイ前に本番データで dbt test したい」ケースもありますが、運用では次の点に注意が必要です。
- 新規モデル/新規テストが追加された直後だと、デプロイ前にテストが落ちることがあります
- 「デプロイ前にやるなら、対象を絞った smoke test にする」のが現実的です
例:tag:smoke をつけたテストだけ先に実行する
- name: Pre-deployment smoke tests (optional)
run: dbt test --target prod --select tag:smoke
運用のコツ
smokeは「本番の既存データに対して、毎回チェックしたい最小セット」に絞るのがおすすめです。
5) Slack通知
- name: Notify Slack (Success)
if: success()
uses: slackapi/slack-github-action@v1.27.0
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"text": "✅ dbt Production Deployment Successful",
...
}
- デプロイ成功・失敗をSlackに通知
- Slack Incoming WebhookのURLを
SLACK_WEBHOOK_URLとしてGitHub Secretsに登録
Slack Webhook URLの取得方法:
- Slackワークスペース → Apps → "Incoming Webhooks" を検索・追加
- 通知先チャンネルを選択(例:
#data-engineering) - Webhook URLをコピー(例:
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX) - GitHub Secrets に
SLACK_WEBHOOK_URLとして登録
✅ 承認ゲート(GitHub Environments)の設定
本番デプロイ前に人間による承認を必須にする場合、GitHub Environmentsを使います。

図2: mainマージ後、GitHub EnvironmentsでCDが一時停止し、承認されるまでデプロイが保留される。
設定手順
Step 1: Environment作成
- GitHubリポジトリ → Settings → Environments
- 「New environment」をクリック
-
Name:
production - 「Configure environment」をクリック
Step 2: 承認者設定
- Environment protection rules
- ✅ Required reviewers
- 「Add reviewers」で承認者を追加(例:チームリーダー)
- 「Save protection rules」
Step 3: 動作確認
- mainへPRをマージ
- GitHub Actionsの
dbt CDを開く - 「Review deployments」→ 承認者が「Approve and deploy」
補足
承認が長期間されないと、待機のままになります。運用ルール(承認SLA)もセットで決めるのがおすすめです。
🧾 デプロイログを残す(メタデータテーブル)
「いつ、誰が、どのコミットをデプロイしたか」を追えるようにします。
まずは 最小構成 としてテーブル1つで十分です。
Step 1: メタデータテーブル作成(Snowflakeで実行)
CREATE TABLE IF NOT EXISTS DBT_DB.DBT_PROD.DEPLOYMENT_HISTORY (
deployment_id NUMBER AUTOINCREMENT,
git_sha STRING,
git_branch STRING,
deployed_by STRING,
deployed_at TIMESTAMP_LTZ DEFAULT CURRENT_TIMESTAMP(),
dbt_version STRING,
status STRING -- 'success' / 'failure'
);
注意
SnowflakeのPRIMARY KEYは「宣言」できても強制されません。
監査用途ならこの程度でOKです(厳密にしたい場合は別途設計)。
Step 2: 記録用マクロ(macros/log_deployment.sql)
{% macro log_deployment_info(status) %}
{% set sql %}
INSERT INTO {{ target.database }}.{{ target.schema }}.DEPLOYMENT_HISTORY
(git_sha, git_branch, deployed_by, dbt_version, status)
VALUES (
'{{ env_var("GITHUB_SHA", "unknown") }}',
'{{ env_var("GITHUB_REF_NAME", "unknown") }}',
'{{ env_var("GITHUB_ACTOR", "unknown") }}',
'{{ dbt_version }}',
'{{ status }}'
)
{% endset %}
{% do run_query(sql) %}
{% do log("Deployment logged with status: " ~ status, info=True) %}
{% endmacro %}
Step 3: CDワークフローで呼び出す
dbt build の後に、成功/失敗を記録します。
- name: Deploy (dbt build)
id: dbt_build
run: dbt build --target prod
- name: Record deployment (success)
if: success()
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_ACTOR: ${{ github.actor }}
run: dbt run-operation log_deployment_info --args "{"status": "success"}" --target prod
- name: Record deployment (failure)
if: failure()
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_ACTOR: ${{ github.actor }}
run: dbt run-operation log_deployment_info --args "{"status": "failure"}" --target prod
運用ポイント
「同じコミットを何度もデプロイしたくない」場合は、
DEPLOYMENT_HISTORYでgit_shaを確認してガードする運用にできます(後述)。
🔄 失敗時のリカバリー・ロールバック
CI/CD導入後も失敗は起きます。重要なのは 失敗時に素早く戻せる仕組み を用意しておくことです。

図3: CI失敗→修正→再実行、CD失敗→ロールバック→修正→再デプロイの流れ。
CI失敗時の対処(よくある2パターン)
ケース1: SQLコンパイルエラー
Compilation Error in model stg_customers (models/staging/stg_customers.sql)
column "customer_id" does not exist
対処の流れ
- ログからモデル名を特定(例:
stg_customers) - ローカルで
dbt compile --select stg_customersを実行して確認 -
target/compiled/...のコンパイル済SQLを見て原因を修正 - PRへpush → CIが再実行(自動)
ケース2: テスト失敗
Failure in test unique_stg_customers_customer_id (models/staging/schema.yml)
Got 2 results, expected 0.
対処の流れ
- ローカルで
dbt test --select stg_customersを実行して再現 - PRスキーマのデータを確認(例:重複の特定)
SELECT customer_id, COUNT(*) FROM DBT_PR_123.stg_customers GROUP BY customer_id HAVING COUNT(*) > 1; - モデルロジック or ソース側の問題を修正
- PR更新 → CI再実行
CD失敗時のロールバック(Snowflake Time Travel)
本番デプロイが失敗した場合、Snowflakeの Time Travel を使って過去の状態に戻せます。
Time Travelの保持期間
版や設定によって保持期間は変わります。導入時は必ず最新の公式ドキュメントで確認してください。
ロールバック例1: 1時間前の状態に戻す
CREATE OR REPLACE TABLE DBT_DB.DBT_PROD.CUSTOMERS
CLONE DBT_DB.DBT_PROD.CUSTOMERS AT(OFFSET => -3600);
ロールバック例2: 特定時刻に戻す
CREATE OR REPLACE TABLE DBT_DB.DBT_PROD.CUSTOMERS
CLONE DBT_DB.DBT_PROD.CUSTOMERS AT(TIMESTAMP => '2026-01-04 10:00:00'::TIMESTAMP_LTZ);
ロールバック例3: 特定のQuery IDの直前に戻す
CREATE OR REPLACE TABLE DBT_DB.DBT_PROD.CUSTOMERS
CLONE DBT_DB.DBT_PROD.CUSTOMERS BEFORE(STATEMENT => '01a1b2c3-0000-0000-0000-000000000000');
注意(重要)
CREATE OR REPLACEが絡むと、状況によっては「期待した過去版に戻れない」ことがあります。
本番運用では、ロールバック手順を事前に演習して「戻せること」を確認しておくのがおすすめです。
📄 実装コードまとめ(後編で追加したもの)
前編の内容に加えて、後編で登場したファイルをまとめます。
(前編のCIワークフローや鍵ペア認証周りは、前編記事を参照してください)
.github/workflows/dbt_cd.yml(CD)
name: dbt CD (Production Deployment)
on:
push:
branches: [main]
permissions:
contents: read
concurrency:
group: dbt-cd-production
cancel-in-progress: false
jobs:
dbt-cd:
runs-on: ubuntu-latest
environment: production
env:
DBT_PROFILES_DIR: .
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
SNOWFLAKE_PRIVATE_KEY_PATH: rsa_key.p8
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dbt (pin recommended)
run: |
pip install --upgrade pip
pip install "dbt-core==1.10.*" "dbt-snowflake==1.10.*"
- name: Create profiles.yml
run: cp profiles.yml.template profiles.yml
- name: Setup Snowflake private key
run: |
printf "%s" "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > rsa_key.p8
chmod 600 rsa_key.p8
- name: Install dbt packages
run: dbt deps
- name: Pre-deployment check (compile)
run: dbt compile --target prod
- name: Deploy (dbt build)
id: dbt_build
run: |
# フルビルド(全モデル・全テストを実行)
dbt build --target prod
- name: Clean up private key
if: always()
run: rm -f rsa_key.p8 profiles.yml
macros/log_deployment.sql
{% macro log_deployment_info(status) %}
{% set sql %}
INSERT INTO {{ target.database }}.{{ target.schema }}.DEPLOYMENT_HISTORY
(git_sha, git_branch, deployed_by, dbt_version, status)
VALUES (
'{{ env_var("GITHUB_SHA", "unknown") }}',
'{{ env_var("GITHUB_REF_NAME", "unknown") }}',
'{{ env_var("GITHUB_ACTOR", "unknown") }}',
'{{ dbt_version }}',
'{{ status }}'
)
{% endset %}
{% do run_query(sql) %}
{% do log("Deployment logged with status: " ~ status, info=True) %}
{% endmacro %}
🔧 ハマりどころ(実務で詰まる7つ)
CI/CD導入時に詰まりやすいポイントと対処法をまとめます。
1) 認証エラー(account identifier / region)
エラー例
250001: Could not connect to Snowflake backend after 0 attempt(s).
原因:SNOWFLAKE_ACCOUNT の形式が違うことが多いです。
確認
SELECT CURRENT_ACCOUNT(), CURRENT_REGION();
対処
- GitHub Secretsの
SNOWFLAKE_ACCOUNTを修正 -
account_locator.region形式(例:abc12345.us-east-1)や、組織名形式など、利用中の形式に合わせる
2) 秘密鍵のフォーマットエラー(改行が消える)
エラー例
Could not deserialize the private key
対処
- Secretsには
-----BEGIN PRIVATE KEY-----からENDまで 改行を保持して貼り付け - ワークフローでは
printf "%s"で書き出す(echoより安全)
printf "%s" "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > /tmp/snowflake_key.p8
3) profiles.ymlの環境変数が入らない
エラー例
Env var required but not provided: 'SNOWFLAKE_ACCOUNT'
対処
- Secrets名(大文字小文字)を確認
- ワークフローで env に明示的に渡す(前編/後編ともにこの方式)
4) PRスキーマの削除が想定どおりに動かない(+schema設定)
dbt_project.yml で +schema: を使っていると、CIの一時スキーマが
DBT_PR_123DBT_PR_123_STAGINGDBT_PR_123_MARTS
のように 複数に増えることがあります(dbtのデフォルト命名規則)。
この場合、単純に DROP SCHEMA DBT_PR_123 だけだと取り残しが出ます。
対処(おすすめ)
- CIターゲット(
target.name == ci)では「常に target.schema に寄せる」ようにgenerate_schema_nameを上書きする
-- macros/generate_schema_name.sql
{% macro generate_schema_name(custom_schema_name, node) %}
{% if target.name == "ci" %}
{{ target.schema }}
{% else %}
{{ default__generate_schema_name(custom_schema_name, node) }}
{% endif %}
{% endmacro %}
これでCIは DBT_PR_123 に集約でき、クリーンアップが簡単になります。
5) dbtプロジェクトがサブディレクトリにある
モノレポ構成などでdbtがサブディレクトリにあると、そのままだと失敗します。
対処(例)
defaults:
run:
working-directory: ./dbt_project
6) ログの見方(GitHub Actions / Snowflake)
GitHub Actions
- 該当ワークフロー → 各ステップのログを展開
dbt詳細ログ
dbt build --target prod --debug
Snowflake側の確認
-
SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORYは反映に遅延があることがあります - すぐ見たい場合は
INFORMATION_SCHEMA.QUERY_HISTORY()の利用も検討してください
7) コスト最適化(CI用Warehouseを小さくする)
対処例
- CI専用Warehouseを小さくする(例:X-Small)
- AUTO_SUSPENDを短くする(例:60秒)
- Slim CIで実行範囲を絞る
- pip/dbt packagesをキャッシュする
コストは環境や契約で変動します。あくまで目安として、継続的にモニタリングしてください。
🎯 運用Tips
1) CIの実行時間最適化(キャッシュ)
pipキャッシュ
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
dbt packagesキャッシュ(任意)
- name: Cache dbt packages
uses: actions/cache@v4
with:
path: dbt_packages
key: dbt-packages-${{ hashFiles('packages.yml') }}
2) セキュリティ(Secrets管理と最小権限)
- 本番は
productionEnvironment Secrets を使い、アクセス可能範囲を絞る - CI用ロールと本番用ロールを分離する(最小権限)
- 秘密鍵は定期ローテーション(例:90日ごと)を検討
3) モニタリング・アラート
- CD失敗はSlack通知(後述の例)
- Snowflakeのクレジット消費はWarehouse単位で定期確認
4) dbt docsの自動公開(発展)
dbt docs generate をCI/CDで実行し、GitHub PagesやS3で公開できます。
(docs/lineageの活用は第6弾を参照)
🧪 実機検証:新しいmartモデルでCI/CD動作確認
ここまでの内容が実際に動作することを確認するため、新しいmartモデルを追加してCI/CDパイプラインを実行します。
検証用の .github/workflows/dbt_cd.yml は以下をコピーしてください。
※承認部分と通知の部分を削除しています。
.github/workflows/dbt_cd.yml
name: dbt CD (Production Deployment)
on:
push:
branches: [main]
paths:
- "models/**"
- "tests/**"
- "macros/**"
- "seeds/**"
- "snapshots/**"
- "dbt_project.yml"
- "packages.yml"
- "profiles.yml.template"
- ".github/workflows/**"
permissions:
contents: read
concurrency:
group: dbt-cd-production
cancel-in-progress: false # 本番はキャンセルせず順番に実行する想定
jobs:
dbt-cd:
runs-on: ubuntu-latest
env:
DBT_PROFILES_DIR: .
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}
SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }}
SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }}
SNOWFLAKE_PRIVATE_KEY_PATH: rsa_key.p8
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dbt (pin recommended)
run: |
pip install --upgrade pip
pip install "dbt-core==1.10.*" "dbt-snowflake==1.10.*"
- name: Create profiles.yml from template
run: cp profiles.yml.template profiles.yml
- name: Setup Snowflake private key
run: |
printf "%s" "${{ secrets.SNOWFLAKE_PRIVATE_KEY }}" > rsa_key.p8
chmod 600 rsa_key.p8
- name: Install dbt packages
run: dbt deps
- name: Pre-deployment check (compile)
run: dbt compile --target prod
- name: Deploy (dbt build)
id: dbt_build
run: |
# フルビルド(全モデル・全テストを実行)
dbt build --target prod
- name: Post-deployment verification
if: success()
run: |
echo "✅ Deployment completed successfully at $(date)"
echo "📦 Commit: $GITHUB_SHA"
echo "👤 Actor: $GITHUB_ACTOR"
echo "🎯 Target: prod"
echo "🗄️ Database: $SNOWFLAKE_DATABASE"
- name: Deployment failed
if: failure()
run: |
echo "❌ Deployment failed at $(date)"
echo "📦 Commit: $GITHUB_SHA"
echo "👤 Actor: $GITHUB_ACTOR"
exit 1
- name: Clean up private key
if: always()
run: rm -f rsa_key.p8 profiles.yml
検証の流れ
- 事前準備:dbtサンプルモデルの削除(推奨)
- 新しいmartモデル(
mart_order_summary.sql)を作成 - ローカルでテスト実行
- ブランチ作成・コミット・プッシュ
- PR作成
- GitHub Actions CI実行確認
- PRマージ
- GitHub Actions CD実行確認
- Snowflakeで本番データ確認
Step 0: 事前準備(推奨)- dbtサンプルモデルの削除
dbtプロジェクト初期化時に作成される models/example/ ディレクトリのサンプルモデルには、学習用に意図的にNULL値が含まれています。CI/CDでテストが失敗する原因になるため、削除することを推奨します。
# 1. サンプルモデルディレクトリを削除
rm -rf models/example/
# 2. dbt_project.yml から example 設定を削除
# 以下のセクションを削除またはコメントアウト
# models:
# my_dbt_project:
# example:
# +materialized: view
ポイント:この作業は一度だけ実行すればOKです。すでに削除済みの場合はスキップしてください。
Step 1: 新しいmartモデルを作成
既存の stg_orders モデルを使って、日次の注文サマリを集計するmartモデルを作成します。
前提:第3弾で作成した stg_orders が存在することを確認してください。存在しない場合は、以下を先に実行:
# ローカル環境で
dbt run --select stg_orders --target dev
models/marts/ ディレクトリを作成:
mkdir -p models/marts
models/marts/mart_order_summary.sql を作成:
{{ config(
materialized='table',
tags=['mart', 'daily']
) }}
WITH order_base AS (
SELECT
DATE(order_date) AS order_date,
order_id,
customer_id,
amount
FROM {{ ref('stg_orders') }}
)
SELECT
order_date,
COUNT(DISTINCT order_id) AS total_orders,
COUNT(DISTINCT customer_id) AS unique_customers,
SUM(amount) AS total_revenue,
AVG(amount) AS avg_order_value,
MIN(amount) AS min_order_value,
MAX(amount) AS max_order_value
FROM order_base
GROUP BY order_date
ORDER BY order_date DESC
(任意)テストを追加:
models/marts/schema.yml を作成:
version: 2
models:
- name: mart_order_summary
description: "日次の注文サマリ(注文数、顧客数、売上合計など)"
columns:
- name: order_date
description: "注文日"
tests:
- not_null
- unique
- name: total_orders
description: "注文数"
tests:
- not_null
- name: total_revenue
description: "売上合計"
tests:
- not_null
Step 2: ローカルでテスト実行
# モデルを実行
dbt run --select mart_order_summary --target dev
# テストを実行
dbt test --select mart_order_summary --target dev
期待される結果:
1 of 1 OK created sql table model PUBLIC.mart_order_summary ......... [SUCCESS 1 in X.XXs]
1 of 3 PASS not_null_mart_order_summary_order_date .................. [PASS in X.XXs]
2 of 3 PASS unique_mart_order_summary_order_date .................... [PASS in X.XXs]
3 of 3 PASS not_null_mart_order_summary_total_orders ............... [PASS in X.XXs]
Snowflakeで確認:
SELECT * FROM DBT_DB.PUBLIC.MART_ORDER_SUMMARY LIMIT 10;
Step 3: ブランチ作成・コミット・プッシュ
# 新しいブランチを作成
git checkout -b feature/add-mart-order-summary
# ファイルをステージング
git add models/marts/
# コミット
git commit -m "Add mart_order_summary model for daily order aggregation"
# GitHubへプッシュ
git push origin feature/add-mart-order-summary
Step 4: PR作成
- GitHubリポジトリのページへアクセス
- 「Compare & pull request」をクリック
- PR タイトル:
Add mart_order_summary model - PR 本文(例):
## 概要
日次の注文サマリを集計するmartモデルを追加しました。
## 変更内容
- `models/marts/mart_order_summary.sql` を追加
- `models/marts/schema.yml` にテスト定義を追加
## テスト
- ローカル環境で `dbt run --select mart_order_summary` 実行確認
- テストもすべてパス
## 確認事項
- [ ] CI(GitHub Actions)が成功すること
- [ ] マージ後、本番環境にデプロイされること
- 「Create pull request」をクリック
Step 5: GitHub Actions CI実行確認
PRを作成すると、前編で設定した dbt CI(Slim CI) が自動実行されます。
確認ポイント:
- GitHubリポジトリ → Actions タブ
- 「dbt CI (Slim CI)」ワークフローが実行中であることを確認
- 各ステップのログを展開して確認:
- ✅ Checkout code
- ✅ Set up Python
- ✅ Install dbt
- ✅ Create profiles.yml
- ✅ Setup Snowflake private key
- ✅ Install dbt packages
- ✅ Create PR Schema
- ✅ Run dbt build (Slim CI)
- ✅ Drop PR Schema
- ✅ Clean up private key
期待される結果:
-
dbt build --select state:modified+ --defer --state ./prod_state --target ciが実行される -
mart_order_summaryが新規モデルとして検出され、ビルド・テストされる - PRスキーマ(例:
DBT_PR_123)が作成され、最後に削除される - すべてのステップが ✅ 成功
Snowflakeで確認(CIが成功している間):
-- PRスキーマが作成されていることを確認(CI実行中のみ)
SHOW SCHEMAS LIKE 'DBT_PR_%' IN DATABASE DBT_DB;
-- CI完了後はPRスキーマが削除されていることを確認
SHOW SCHEMAS LIKE 'DBT_PR_%' IN DATABASE DBT_DB;
-- (結果:0件)

図4: PRに「✅ dbt CI (Slim CI) success」とコメントが追加される

図5: dbt CIの詳細な処理結果
Step 6: PRマージ
CIが成功したら、PRをマージします。
- PR画面で「Merge pull request」をクリック
- 「Confirm merge」をクリック
- (任意)ブランチを削除:「Delete branch」をクリック
Step 7: GitHub Actions CD実行確認
PRがmainにマージされると、dbt CD(Production Deployment) が自動実行されます。
確認ポイント:
- GitHubリポジトリ → Actions タブ
- 「dbt CD (Production Deployment)」ワークフローが実行中であることを確認
- 各ステップのログを展開して確認:
- ✅ Checkout code
- ✅ Set up Python
- ✅ Install dbt
- ✅ Create profiles.yml from template
- ✅ Setup Snowflake private key
- ✅ Install dbt packages
- ✅ Pre-deployment check (compile)
- ✅ Deploy (dbt build)
- ✅ Post-deployment verification
- ✅ Clean up private key
期待される結果:
-
dbt build --target prodが実行される -
mart_order_summaryが本番スキーマ(DBT_DB.DBT_PROD)に作成される - すべてのテストがパス
- Post-deployment verification でコミットSHA、実行者が出力される

図6: dbt CDの詳細な処理結果
Step 8: Snowflakeで本番データ確認
CD成功後、Snowflakeで本番データを確認します。
-- 本番スキーマのテーブル一覧を確認
SHOW TABLES IN SCHEMA DBT_DB.DBT_PROD;
-- mart_order_summary が作成されていることを確認
SELECT * FROM DBT_DB.DBT_PROD.MART_ORDER_SUMMARY LIMIT 10;
-- データ内容の確認
SELECT
order_date,
total_orders,
unique_customers,
total_revenue,
avg_order_value
FROM DBT_DB.DBT_PROD.MART_ORDER_SUMMARY
ORDER BY order_date DESC
LIMIT 5;
期待される結果:
-
MART_ORDER_SUMMARYテーブルが存在する - 日次の注文サマリデータが格納されている
-
total_orders、total_revenueなどの集計値が正しく計算されている

図7: Snowflake側の確認結果
検証完了!
おめでとうございます!新しいmartモデルを追加し、CI/CDパイプラインが正常に動作することを確認できました。
確認できたこと:
- ✅ ローカルで新しいモデルを開発・テスト
- ✅ ブランチ作成・コミット・プッシュ
- ✅ PR作成で自動的にCIが起動
- ✅ Slim CIが変更箇所のみをテスト
- ✅ PRスキーマが自動的に作成・削除される
- ✅ PRマージで自動的にCDが起動
- ✅ 本番環境へ自動デプロイ
- ✅ Snowflakeで本番データを確認
この一連の流れが、GitHub ActionsでdbtのCI/CDパイプラインの実体です。
次の実験アイデア
さらに理解を深めるために、以下の実験も試してみてください:
1) CI失敗を体験する
意図的にエラーを起こしてCIの動作を確認:
-- models/marts/mart_order_summary.sql に構文エラーを追加
SELECT * FROM non_existent_table;
→ PRを作成 → CIが失敗 → ログで原因を確認 → 修正 → CI再実行
2) テスト失敗を体験する
テストを追加して失敗させる:
# models/marts/schema.yml
- name: mart_order_summary
columns:
- name: total_revenue
tests:
- not_null
- dbt_utils.expression_is_true:
expression: "> 0" # 意図的に厳しい条件
→ PRを作成 → CIでテスト失敗 → 原因を確認 → 修正
3) 本番デプロイ後のロールバック演習
Time Travelでロールバックを試す:
-- 現在の状態を確認
SELECT COUNT(*) FROM DBT_DB.DBT_PROD.MART_ORDER_SUMMARY;
-- 5分前の状態に戻す
CREATE OR REPLACE TABLE DBT_DB.DBT_PROD.MART_ORDER_SUMMARY
CLONE DBT_DB.DBT_PROD.MART_ORDER_SUMMARY AT(OFFSET => -300);
-- ロールバックされたことを確認
SELECT COUNT(*) FROM DBT_DB.DBT_PROD.MART_ORDER_SUMMARY;
📝 まとめ
CI/CD導入で得られた効果
この記事で構築したCI/CDパイプラインにより、次のような効果が得られます。
-
✅ レビュー品質向上
- PR作成時に自動テストが実行され、レビュー前に問題を検出
- 「このPRは動くのか?」という不安が解消される
-
✅ デプロイ時間短縮
- mainマージ後、自動で本番デプロイ(手動実行の手間がゼロ)
- 承認ゲートで「いつ本番に反映されたか」が明確
-
✅ 本番汚染の防止
- PR検証は一時スキーマ(
DBT_PR_123)で実行され、本番DBに影響しない
- PR検証は一時スキーマ(
-
✅ ロールバック容易性
- Time Travelで過去の状態に即座に戻せる
- デプロイ履歴をメタデータで管理し、いつでも追跡可能
-
✅ 監査証跡
- いつ、誰が、何をデプロイしたかが記録される
- コンプライアンス要件にも対応
次のステップ
CI/CDが整ったら、次は以下も検討できます。
大規模運用への移行:
- モノレポ管理(複数dbtプロジェクトを1リポジトリで管理)
- 並列実行(
dbt run --threads 16) - ジョブスケジューリング(Airflow / Dagster統合)
dbt Cloud移行の検討:
- dbt Coreから dbt Cloud への移行を検討
- dbt Cloud のCI/CD機能(Slim CI / Job Scheduler)を活用
- コスト vs 運用工数のトレードオフを評価
Data Observability導入:
- データ品質監視(elementary / Monte Carlo / Soda)
- テスト失敗率のSLO設定(第4弾テスト戦略参照)
- Slackアラート連携
マルチ環境対応:
- staging環境の追加(本番デプロイ前の統合テスト)
- 環境ごとのパラメータ管理(dbt Cloud variables / GitHub Environments)
セキュリティ強化:
- OIDC認証への移行(秘密鍵不要)
- SOC 2 / ISO 27001 対応
さいごに
前編・後編を通じて、GitHub ActionsでdbtのCI/CDパイプラインを完全自動化する方法を解説しました。
重要なポイント:
- 3つのアプローチを理解し、自チームに合った方法を選ぶ
- 鍵ペア認証でセキュアにSnowflakeへ接続
- Slim CIで変更箇所のみテスト(高速化・コスト削減)
- 承認ゲートで本番デプロイを制御
- Time Travelで失敗時のロールバックを迅速化
この記事が、あなたのチームのdbt運用を次のレベルに引き上げる助けになれば幸いです!
🔗 参考リンク
Snowflake公式ドキュメント
dbt Labs公式ドキュメント
GitHub Actions
関連記事
- octabirdさん「dbtで出来そうなCI一通り実装してみた」(Slim CI詳細)
過去の記事
- 第1弾:dbtの全体像
https://zenn.dev/yujmatsu/articles/20251226_sf_dbt_intro - 第2弾:dbt Projects on Snowflake実践
https://zenn.dev/yujmatsu/articles/20251227_sf_dbt_tutorial - 第3弾:dbt × Snowflake ローカル開発環境構築
https://zenn.dev/yujmatsu/articles/20251228_sf_dbt_local_setup - 第4弾:データ品質を守るテスト戦略
https://zenn.dev/yujmatsu/articles/20251229_sf_dbt_test - 第5弾 前編:Claude Code×dbt基礎編
https://zenn.dev/yujmatsu/articles/20251231_sf_dbt_claude_code_part1 - 第5弾 後編:Claude Code応用編
https://zenn.dev/yujmatsu/articles/20260101_sf_dbt_claude_code_part2 - 第6弾:ドキュメント&Data Lineage
https://zenn.dev/yujmatsu/articles/20260103_sf_dbt_docs_lineage - 第7弾 前編:CI/CD基礎・準備編
https://zenn.dev/yujmatsu/articles/20260104_sf_dbt_cicd_part1
Discussion