
dbt Projects on Snowflake を GitHub Actions + OIDC 認証で CI/CD を試してみた
かわばたです。
dbt Projects on Snowflake で dbt プロジェクトを Snowflake 上で直接実行できるようになり、開発フローに CI/CD を組み込みたいケースは多いのではないでしょうか。その際に気になるのが GitHub Actions から Snowflake への認証方法です。パスワードや PAT(Programmatic Access Token)を GitHub Secrets に保存する方式は、長期シークレットの管理・ローテーションという運用負荷がつきまといます。
本記事では、GitHub Actions の OIDC トークンと Snowflake の Workload Identity Federation を使い、長期シークレットなしで「PR 時に dev 環境で dbt build を実行する CI」と「main マージ時に snow dbt deploy で本番デプロイする CD」を構築してみます。
構成の概要
dbt Projects on Snowflake の CI/CD
dbt Projects on Snowflake では、dbt プロジェクトをスキーマレベルの DBT PROJECT オブジェクトとしてデプロイし、Snowflake 上で実行します。公式ドキュメントでは次の CI/CD 構成が示されています。
- CI: Pull Request ごとにテスト用の DBT PROJECT オブジェクトをデプロイし、dev 環境に対して
dbt buildを実行する - CD: main ブランチへのマージ後に
snow dbt deployで本番用 DBT PROJECT オブジェクトを更新する
snow dbt deploy はバージョン付きのデプロイであり、デプロイのたびに新しいバージョンが積み上がります。実行履歴も保持されるため、いつどのバージョンで実行されたかを追跡できます。
OIDC(Workload Identity Federation)による認証
Snowflake の Workload Identity Federation を使うと、GitHub Actions が発行する短期の OIDC トークンで Snowflake に認証できます。GitHub Secrets に保存するのはアカウント識別子のみで、パスワード・PAT・キーペアは一切不要です。
仕組みとしては、TYPE = SERVICE のユーザーに WORKLOAD_IDENTITY として ISSUER(トークン発行者)と SUBJECT(トークンの sub claim)を登録しておき、Snowflake 側で GitHub の OIDC トークンを検証します。
sub claim とサービスユーザーの設計
GitHub Actions の OIDC トークンの sub claim は、トリガーとなるイベントによって形式が変わります。
| イベント | sub claim の形式 |
|---|---|
| pull_request | repo:<org>/<repo>:pull_request |
| push(ブランチ) | repo:<org>/<repo>:ref:refs/heads/<branch> |
| environment 指定ありのジョブ | repo:<org>/<repo>:environment:<name> |
Snowflake のサービスユーザーは SUBJECT と sub claim の完全一致が必要です。公式チュートリアルでは CI/CD 両方のジョブに environment: prod を指定して sub claim を統一し、サービスユーザー 1 つで構成しています。
今回は GitHub Environment を使わないシンプルな構成とするため、CI(pull_request)用と CD(main への push)用でサービスユーザーを 2 つ作成します。認証主体が分かれることで、CI 用ユーザーには dev のみ、CD 用ユーザーには prod のみといった権限分離にも発展させやすい構成です。
なお、本記事では検証を簡潔にするため 2 ユーザーに共通ロール(dbt_cicd_role)を付与しており、この構成のままでは CI 用ユーザーも prod にアクセス可能です。権限分離まで行う場合は、dbt_ci_role(dev と raw のみ)/ dbt_cd_role(prod と raw のみ)のようにロールを分け、各サービスユーザーへの付与と profiles.yml の各 target の role を対応させてください。
前提条件
- Snowflake アカウント(サービスユーザー作成のため ACCOUNTADMIN 相当の権限が必要)
- GitHub Actions が有効なリポジトリ
- Snowflake CLI 3.11.0 以上(OIDC 認証の要件。
snowflakedb/snowflake-actions@v3で導入) - dbt プロジェクト: 本記事では jaffle-shop(dbt Labs のサンプルプロジェクト)を使用
- dbt バージョン: dbt Core 1.11.11 を指定
- 本記事ではデフォルトブランチを
mainと仮定しています。masterなど別のブランチ名を使う場合は、ワークフローのbranchesと CD 用サービスユーザーの SUBJECT を実際のブランチ名に合わせて変更してください
事前準備
Snowflake 環境の作成
データベース、dev / prod スキーマ、ウェアハウス、CI/CD 用のロールを作成します。公式チュートリアルは ACCOUNTADMIN をそのまま使う構成ですが、今回は専用ロールに最小限の権限を付与します。
Snowflake 環境の作成
USE ROLE ACCOUNTADMIN;
CREATE DATABASE IF NOT EXISTS jaffle_shop_db;
CREATE SCHEMA IF NOT EXISTS jaffle_shop_db.dev;
CREATE SCHEMA IF NOT EXISTS jaffle_shop_db.prod;
CREATE SCHEMA IF NOT EXISTS jaffle_shop_db.raw; -- ソースデータ(seeds)用
CREATE WAREHOUSE IF NOT EXISTS jaffle_shop_wh
WAREHOUSE_SIZE = XSMALL
AUTO_SUSPEND = 60
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED = TRUE;
CREATE ROLE IF NOT EXISTS dbt_cicd_role;
GRANT USAGE ON DATABASE jaffle_shop_db TO ROLE dbt_cicd_role;
GRANT USAGE ON WAREHOUSE jaffle_shop_wh TO ROLE dbt_cicd_role;
GRANT ALL ON SCHEMA jaffle_shop_db.dev TO ROLE dbt_cicd_role;
GRANT ALL ON SCHEMA jaffle_shop_db.prod TO ROLE dbt_cicd_role;
GRANT ALL ON SCHEMA jaffle_shop_db.raw TO ROLE dbt_cicd_role;
GRANT CREATE DBT PROJECT ON SCHEMA jaffle_shop_db.dev TO ROLE dbt_cicd_role;
GRANT CREATE DBT PROJECT ON SCHEMA jaffle_shop_db.prod TO ROLE dbt_cicd_role;
GRANT CREATE SCHEMA ON DATABASE jaffle_shop_db TO ROLE dbt_cicd_role;
CREATE DBT PROJECT 権限が snow dbt deploy に必要な権限です。また、dbt は実行時にターゲットスキーマの存在確認と作成(CREATE SCHEMA IF NOT EXISTS)を行うため、スキーマを事前に作成済みでも、データベースへの CREATE SCHEMA 権限がないと dbt build が権限エラーで失敗します(検証時に実際に踏んだエラーです)。アクセス制御の詳細は以下のドキュメントにまとまっています。
dbt プロジェクト(jaffle-shop)の調整
dbt Projects on Snowflake では dbt_project.yml に加えて profiles.yml がプロジェクトルートに必須です。dev / prod の 2 ターゲットを定義します。
profiles.yml
jaffle_shop:
target: dev
outputs:
dev:
type: snowflake
account: '_' # プレースホルダー。実行は Snowflake 内部のため未使用
user: '_' # 同上
role: DBT_CICD_ROLE
database: JAFFLE_SHOP_DB
schema: DEV
warehouse: JAFFLE_SHOP_WH
threads: 8
prod:
type: snowflake
account: '_'
user: '_'
role: DBT_CICD_ROLE
database: JAFFLE_SHOP_DB
schema: PROD
warehouse: JAFFLE_SHOP_WH
threads: 8
注意: 実行は Snowflake 内部で行われるため、
accountやuserにはプレースホルダー文字列で問題ありません。パスワードの記載も不要です。一方でtype/database/schema/role/warehouseは必須です。さらにsnow dbt deployはデプロイ前に profiles.yml の内容を実環境に対して検証し、roleやwarehouseが存在しない・接続ユーザーからアクセスできない場合は「Role 'XXX' does not exist or is not accessible.」のようなエラーで弾かれます(全ターゲットが検証対象です)。実環境のオブジェクト名と完全に一致させてください。
OIDC サービスユーザーの作成
CI 用と CD 用の 2 ユーザーを作成します。SUBJECT がそれぞれのイベントの sub claim と完全一致するように設定します。
OIDC サービスユーザーの作成
USE ROLE ACCOUNTADMIN;
-- CI 用(pull_request イベント)
CREATE USER IF NOT EXISTS svc_gha_dbt_ci
TYPE = SERVICE
WORKLOAD_IDENTITY = (
TYPE = OIDC
ISSUER = 'https://token.actions.githubusercontent.com'
SUBJECT = 'repo:<your-org>/<your-dbt-repo>:pull_request'
)
DEFAULT_ROLE = dbt_cicd_role
DEFAULT_WAREHOUSE = jaffle_shop_wh
COMMENT = 'GitHub Actions CI (pull_request) service user';
-- CD 用(main への push イベント)
CREATE USER IF NOT EXISTS svc_gha_dbt_cd
TYPE = SERVICE
WORKLOAD_IDENTITY = (
TYPE = OIDC
ISSUER = 'https://token.actions.githubusercontent.com'
SUBJECT = 'repo:<your-org>/<your-dbt-repo>:ref:refs/heads/main'
)
DEFAULT_ROLE = dbt_cicd_role
DEFAULT_WAREHOUSE = jaffle_shop_wh
COMMENT = 'GitHub Actions CD (push to main) service user';
GRANT ROLE dbt_cicd_role TO USER svc_gha_dbt_ci;
GRANT ROLE dbt_cicd_role TO USER svc_gha_dbt_cd;
注意: SUBJECT は sub claim と完全一致している必要があります。
<your-org>/<your-dbt-repo>はリポジトリの実際のパスに置き換えてください。大文字小文字も含めて一致が必要です。
WORKLOAD_IDENTITY(TYPE=OIDC) が ISSUER/SUBJECT どおりに登録されているか確認しました。

GitHub リポジトリの設定
Settings を開き、Secrets and variables → Actions を選択します。New repository secret をクリックし、Secret を 1 件だけ登録します。
SNOWFLAKE_ACCOUNT: アカウント識別子(<orgname>-<account_name>形式)
登録するシークレットはこれだけです。パスワードも PAT も不要という点が OIDC 認証の大きなメリットです。

CI ワークフローの作成
.github/workflows/ci.yml を作成します。PR をトリガーに、テスト用の DBT PROJECT オブジェクトを dev スキーマにデプロイし、dev ターゲットで dbt build を実行します。
name: CI - dbt build on PR
run-name: PR by ${{ github.actor }}
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [main]
concurrency:
group: ci-dev
cancel-in-progress: false
permissions:
contents: read
id-token: write # OIDC トークン発行に必須
jobs:
dbt-ci:
runs-on: ubuntu-latest
env:
SNOWFLAKE_CLI_FEATURES_ENABLE_DBT: true
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_ROLE: DBT_CICD_ROLE
SNOWFLAKE_WAREHOUSE: JAFFLE_SHOP_WH
SNOWFLAKE_DATABASE: JAFFLE_SHOP_DB
SNOWFLAKE_SCHEMA: DEV
steps:
- uses: actions/checkout@v4
- name: Install dbt and resolve dependencies
run: |
pip install 'dbt-core==1.11.*'
dbt deps
- name: Install Snowflake CLI
uses: snowflakedb/snowflake-actions@v3
with:
use-oidc: true
- name: Test connection
run: snow connection test -x
- name: Deploy tester dbt project object
run: snow dbt deploy ci_jaffle_shop --source . --dbt-version 1.11.11 -x
- name: Build and test on dev
run: snow dbt execute -x ci_jaffle_shop build --target dev
ポイントは次のとおりです。
permissionsのid-token: writeが OIDC トークン発行に必須snowflakedb/snowflake-actions@v3にuse-oidc: trueを渡すと、認証用の環境変数(SNOWFLAKE_AUTHENTICATOR=WORKLOAD_IDENTITY等)が自動設定されるsnow dbtコマンドの利用には環境変数SNOWFLAKE_CLI_FEATURES_ENABLE_DBT: trueが必要-xは一時接続(temporary connection)オプションで、config ファイルなしに環境変数ベースで接続するSNOWFLAKE_ROLE/SNOWFLAKE_WAREHOUSEを明示することで、サービスユーザーのデフォルトロール設定に依存せず接続コンテキストを確定させる(未指定でデフォルトロールが効いていないと「Could not use database」エラーになる)concurrencyで CI を直列化している。全 PR が同じci_jaffle_shopオブジェクトを共有しており、同一 DBT PROJECT オブジェクトへのEXECUTE DBT PROJECTの同時実行はサポートされないため
CD ワークフローの作成
.github/workflows/deploy.yml を作成します。main への push をトリガーに、本番用 DBT PROJECT オブジェクトを prod スキーマにデプロイし、続けて prod ターゲットで build を実行します。
name: CD - deploy dbt project on merge
run-name: Deploy by ${{ github.actor }}
on:
push:
branches: [main]
concurrency:
group: cd-prod
cancel-in-progress: false
permissions:
contents: read
id-token: write
jobs:
dbt-deploy:
runs-on: ubuntu-latest
env:
SNOWFLAKE_CLI_FEATURES_ENABLE_DBT: true
SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
SNOWFLAKE_ROLE: DBT_CICD_ROLE
SNOWFLAKE_WAREHOUSE: JAFFLE_SHOP_WH
SNOWFLAKE_DATABASE: JAFFLE_SHOP_DB
SNOWFLAKE_SCHEMA: PROD
steps:
- uses: actions/checkout@v4
- name: Install dbt and resolve dependencies
run: |
pip install 'dbt-core==1.11.*'
dbt deps
- name: Install Snowflake CLI
uses: snowflakedb/snowflake-actions@v3
with:
use-oidc: true
- name: Test connection
run: snow connection test -x
- name: Deploy production dbt project object
run: snow dbt deploy jaffle_shop --source . --default-target prod --dbt-version 1.11.11 -x
- name: Build models on prod
run: snow dbt execute -x jaffle_shop build --target prod
- name: List dbt project objects
run: snow dbt list -x
CD では --default-target prod を指定し、DBT PROJECT オブジェクトのデフォルトターゲットを prod にしています。当初はデプロイのみの構成で検証しましたが、デプロイだけでは DBT PROJECT オブジェクトの新バージョンが登録されるのみで prod スキーマのテーブルは更新されないため、マージでデータまで反映されるよう build ステップを追加しています。モデル数が多い本番運用では、build を Snowflake Task のスケジュール実行に分離し、CD はデプロイのみとする構成も選択肢です。
また concurrency は CD の直列化のためのものです。同一 DBT PROJECT オブジェクトへの EXECUTE DBT PROJECT は同時実行できないため、マージが連続した場合に build が衝突しないよう cancel-in-progress: false で後続を待機させます。
試してみた
OIDC 認証の確認
ワークフローを main に push すると CD が起動します。まずは snow connection test -x の結果で OIDC 認証が通ることを確認します。

CI: PR 作成でテストが実行される
feature branch でモデルに軽微な変更を加えて PR を作成すると、CI ワークフローが起動します。
今回はcustomers.sqlのカラム名を一部変更しています。

PRを作成します。

CI ではテスト用オブジェクト ci_jaffle_shop が dev スキーマにデプロイされ、dev ターゲットで dbt build が実行されます。

この時点で dev スキーマには変更が反映され、prod スキーマは変更前のままであることを確認できます。

CD: マージで本番オブジェクトが更新される
PR をマージすると CD ワークフローが起動し、本番オブジェクトが新しいバージョンとしてデプロイされ、続く build ステップで prod スキーマのテーブル・ビューも更新されます。


prod スキーマにも反映されていることが確認できました。

テスト失敗時に CI がブロックされることの確認
意図的にテストが失敗する変更(not_null 違反など)で PR を作成すると、CI が失敗します。ブランチ保護ルールと組み合わせることで、テストが通らない変更のマージをブロックできます。
今回はcustomers.sqlに対して、下記内容を最後に加えています。
-- test: 検証のため customer_id を意図的に重複させ、unique テストを失敗させる
select * from joined
union all
(select * from joined limit 1)

制限事項・注意点
snow dbt deploy --forceはCREATE OR REPLACE DBT PROJECTとして動作し、既存のバージョンと実行履歴が失われます。通常の CD パイプラインでは使用しないことを推奨します- DBT PROJECT オブジェクトは Serverless Task では実行できず、ユーザー管理のウェアハウスが必要です
- 同一 DBT PROJECT オブジェクトに対する
EXECUTE DBT PROJECTの同時実行はサポートされません - dbt Cloud プロジェクトは非サポートです(dbt Core / dbt Fusion のみ)
- アカウントでネットワークポリシーを運用している場合、GitHub ホステッドランナーからの接続には Snowflake マネージドのネットワークルール
SNOWFLAKE.NETWORK_SECURITY.GITHUBACTIONS_GLOBALを許可リストに追加する必要があります - 本記事はシンプル構成のためサービスユーザーを 2 つに分けましたが、本番運用では GitHub Environment(
environment: prod)と必須承認者を設定し、SUBJECT をrepo:<org>/<repo>:environment:prodとする構成を推奨します。承認ゲートを通過するまでデプロイジョブが実行されないため、より安全な CD になります - GitHub の OIDC トークンの sub claim 仕様は変更・拡張される可能性があるため、実装時は GitHub 公式ドキュメントも確認してください
最後に
GitHub Actions の OIDC トークンと Workload Identity Federation により、GitHub Secrets にはアカウント識別子のみという構成で dbt Projects on Snowflake の CI/CD を構築できました。
この記事が何かの参考になれば幸いです!






