
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」を構築してみます。
【追記】
Snowflake Community Awards の「RISING COMMUNITY LEADER OF THE YEAR」部門・APJ枠のファイナリストに選ばれました。
詳細は下記よりご確認ください。
構成の概要
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 を構築できました。
この記事が何かの参考になれば幸いです!







