上原正吉(EarthLink Network Co., Ltd.)。AI会社・AI社員による業務自動化と、人の逐次介入なしに24時間自動開発を回す仕組みを自社で実践しています。開発ノウハウと具体的な設定方法、運用して分かった改善点を公開しています。
同じ SENTRY_DSN を project ごとに貼っている ── 共通の値は Global Keys に置いて bind する
この記事は、連載「dotvault の使い方 ── .env を手で配るのをやめ、CI・チーム・AI エージェントに 1 コマンドで配るまで」の第 6 回です。この連載が終わる時に、dotvault を誰でも無料で使える形で公開する予定です。
Sentry の DSN を取り替える日には、その値を使っている project の .env を順に開いて、同じ値を project の数だけ貼り直すことになります。Cloudflare の API トークンも同じです。どの project が持っているかを覚えていなければ、grep で探してから貼ります。1 つ貼り忘れると、その project だけ古い値で動きます。
「project ごとに .env があるのだから、共通の値も project ごとに持つしかない」と考えがちですが、それは組織で 1 つしか無い値を、project の数だけ複製しているだけです。複製が project の数だけあれば、取り替えも、漏れた時に止めるのも、project の数だけ必要です。Global Keys に 1 回置けば、bind した project は次の pull で受け取ります(この回の実測では、demo-app の development の .env が 5 key から 7 key に増えました)。
この回では、組織で共有する値(API トークンや DSN)を 1 か所に置く Global Keys と、それを使う project が自分で受け取りを宣言する bind を扱います。画面と CLI で SENTRY_DSN と CLOUDFLARE_API_TOKEN を置き、2 回目から使っている demo-app に bind し、pull で .env に混ざるところ、production だけ別の値になるところ、project が同じ名前の key を持っていたらどちらが勝つかまで進めます。
結論
-
組織で共有する値は、project ではなく Global Keys に置きます。 画面のサイドバーの Global Keys か、CLI の
dotvault global setです。置いた値は「共通の値」(画面では Common、CLI では_all)として全環境に効き、環境ごとに別の値(上書き)を足せます。 -
project は、bind した key だけを受け取ります。 置いただけでは、どの project にも配られません。project の Global keys タブで Bind するか、project のディレクトリで
dotvault global use <KEY>を実行します。受け取る時の名前は--as(画面では alias)で変えられます。 -
受け取りは、いつもの
pullとrunです。 追加の操作はありません。bind した key は.envの管理区間(dotvault が書き込む、# BEGINの行と# ENDの行の間)の先頭に並びます(並ぶ位置の話で、優先順位とは別です)。production をpullすると、production の上書き値が入ります。pullは実行したディレクトリの.envを書き換えるので、production を確かめる時は別のディレクトリで実行します。production に project の key が 1 つも無くてもpullは通り、bind した key 2 つ(production の上書き値)と、project の Common (_shared) のAPI_BASE_URLの 3 key が書かれます。 -
同じ名前の key を project が環境の値として持っていれば、project の値が勝ちます。 pool(org で共有する値の置き場で、画面の Global Keys)の値は、共通の値も環境ごとの上書きも、いちばん弱い層です。受け取りをやめるのは、project の Global keys タブの Unbind か、CLI の
global dropです。CLI のglobal delete KEYは共通の値を消し、global delete KEY -E <env>はその環境の上書きを消します。上書きも含めて key をまるごと消すのは、画面の Delete key です。 -
CLI の
globalのコマンドで poolを触る時は--org <id>が要ります。global keysとglobal setを--org無しで実行すると、ORG_CONTEXT_REQUIREDで止まりました(終了コード 1。終了コードはコマンドが終わる時に返す数で、0 が成功、それ以外が失敗です)。1 つの org にしか入っていなくても、pool を触るglobalのコマンドは org を自動で選びません。describeとdeleteも--orgを付けて実行します。project のディレクトリで実行するglobal use・global status・global dropは、--org無しで動きます(どれも終了コード 0)。org の id はdotvault whoamiに出ます。ただし、globalを付けない素のdotvault setは別です。project-config.jsonの無いディレクトリで実行すると、--org無しでもそのまま pool の共通の値に書かれます(終了コード 0)。project の key をsetする時は、project のディレクトリで実行します。 -
bind の状態を見る場所は、project の Global keys タブと
global statusです。 project の Keys タブは project に置いた key の一覧で、bind した key は Global keys タブの BOUND AS の列に出ます。
本文(読了 約14分)
前提
5 回目の続きで、demo-app の development に .env(5 key)と .env.local(1 key)がある状態から始めます(5 回目を読んでいなくても追えます)。org は 2 回目で作った自分 1 人の Personal で、Global Keys も最初から使えます。
1. project の key と Global Keys の違い
4 回目までの key は、project の、環境の中にありました。Global Keys は、そのどちらにも属しません。
| project の key | Global Keys(pool)の key | |
|---|---|---|
| 置き場 | project × 環境(3 回目の Common (_shared) は、project の中の、全環境に共通の key の置き場所) | org に 1 つ。project には属さない |
| 置く | project の Keys タブ、set / import
|
サイドバーの Global Keys、global set
|
| 値の層 | 環境の値と、project の Common (_shared) の値 | 共通の値(Common / _all)と、環境ごとの上書き |
| 配る先 | その project | bind した project だけ。置いただけでは配られない |
| 受け取る |
pull / run
|
同じ pull / run。追加の操作は無い |
| 同じ名前なら | ── | project の値が勝つ。pool の値はいちばん弱い |
| 値を見る | Keys タブの Reveal、pull
|
画面には出ない。pull の結果で見る |
画面の Global Keys の注意書きにも、同じことが書いてあります。「This is an org-shared secret pool that isn't tied to any project. … Only the ones a project explicitly binds from its own Global keys tab are included in that project's pull. … This page never displays the values themselves — check with dotvault global keys or dotvault pull.」
2. 画面で pool に値を置く
サイドバーの Global Keys を押します。最初は空です。
Add a key に、key の名前・値・説明を入れます。名前に使えるのは英字・数字・_ で、先頭は英字か _ です(例: SENTRY_DSN)。
Add key を押すと、「Added SENTRY_DSN (common value).」と出て、Keys in the pool に 1 行増えます。COMMON / OVERRIDE の列に Common のタグが付きます。ここで置いた値は共通の値で、development でも staging でも production でも、この値が配られます。
production だけ別の値にするには、行の Update value を押します。
選択肢は Common (_all) と 3 つの環境です。production を選んで値を入れ、Update を押すと「Updated the value of SENTRY_DSN (production).」と出ます。
Common と production の 2 つのタグが付きました。この状態の意味は、「production は上書きの値、それ以外の環境は共通の値」です。上書きが付くと、Delete override のボタンも出ます。
画面には、値そのものは出ません。4 回目の Keys タブにあった Reveal に当たるものが、この画面にはありません。入れた値を確かめるのは、5 節の pull です。
3. CLI で pool に値を置く
同じことを CLI でやります。pool を触るコマンドは dotvault global の下にあります。
最初の dotvault global keys は失敗しています。ORG_CONTEXT_REQUIRED は、「どの org の pool か分からない」という意味です。pool を触る global のコマンドは、1 つの org にしか入っていなくても、login から org を選びません。pool は project に属さないので、project から org をたどることもできません。--org <id> を付けます。id は dotvault whoami の org: の行に出ます。この連載の demo の org は Personal なので org_personal_… という長い id ですが、入力するのは 1 回で、あとは端末の履歴から呼び出せます。
global set KEY=VALUE は、-E を付けなければ共通の値([_all])、-E production を付ければ production の上書きです。global describe KEY "説明" で説明を付けます。global keys の envs の列は、common,production のように、共通の値があるか・どの環境に上書きがあるかを並べます。
ここまでで、pool には画面で置いた SENTRY_DSN と CLI で置いた CLOUDFLARE_API_TOKEN の 2 つがあり、どちらも共通の値と production の上書きを持っています。画面で置いたものが CLI に、CLI で置いたものが画面に、同じ行として出ます。
4. project に bind する
pool に置いただけでは、demo-app には何も届きません。project の側で受け取りを宣言します。
CLI では、project のディレクトリで dotvault global use <KEY> です。
--as CF_API_TOKEN は、受け取る時の名前を変える指定です。pool の名前は CLOUDFLARE_API_TOKEN のまま、demo-app の .env には CF_API_TOKEN=… として書かれます。アプリのコードが読んでいる名前に合わせたい時に使います。
3 つ目の GH_TOKEN は、pool に無い名前を bind してみたものです。「Bound」と出た後に、「the global project has no value yet for: GH_TOKEN」という警告が付きます。bind は通りますが、値が無いので pull には入りません(5 節で確かめます)。
画面では project の Global keys タブで、BOUND AS の列に bind の状態が出ます(6b 節で、画面から alias を付けて bind します)。
global status と global use / drop は、project のディレクトリで実行するか、-p <project> で project を指定します。project-config.json の無いディレクトリで -p も無しに実行すると、9 節の VALIDATION_ERROR になります。
5. pull で受け取る
demo-app のディレクトリで dotvault pull を実行します。追加の指定はありません。
development の .env は 5 key から 7 key になりました。増えた 2 つ、CF_API_TOKEN と SENTRY_DSN は、管理区間(# BEGIN と # END の間)の先頭に並びます。project の key は、その後に名前順です。4 節で bind した GH_TOKEN は、pool に値が無いので書かれていません。警告も出ません。
出力の「7 keys」には、pool から来た 2 つも含まれます。pool から来た key は管理区間の先頭に並び、どれを bind しているかは global status で見られます。
下半分は production です。pull は、実行したディレクトリの .env の管理区間を、指定した環境の値で書き直します(下の prod-check の .env の管理区間が production の 3 key だけになっているのは、そのためです)。demo-app で -E production を実行すると、development の DATABASE_URL なども管理区間から消え、production の値が手元の .env に入ります。そこで、project-config.json だけを写した別のディレクトリ(prod-check)で pull -E production を実行しました。demo-app の production には、4 回目まで key を 1 つも置いていません。それでも pull は通り、3 key が書かれました。CF_API_TOKEN と SENTRY_DSN は production の上書きの値(cf_demo_prod_… と prodPublicKey)で、API_BASE_URL は 3 回目で project の Common (_shared) に置いた値です。「1 excluded」は、production に key の無い .env.local が書かれなかった、という意味です。
staging も試しました。staging には上書きが無いので、CF_API_TOKEN と SENTRY_DSN は共通の値で入ります(画像は省略・生の出力は保存しています)。環境を指定して置いた値はその環境だけ、それ以外は共通の値、という決まりが、pull の結果で確かめられました。
run でも同じです。6b 節の最後の画面で、dotvault run -- sh -c 'printenv CF_API_TOKEN SENTRY_DSN_WEB' が 2 つの値を出しています。
6. 同じ名前の key を project が持っていたら
pool の SENTRY_DSN を bind したまま、demo-app の development に同じ名前の key を置いてみます。
pull の後の .env で、SENTRY_DSN の値は project に置いた projectOwnKey の方です。key の数は 7 のまま(pool の分が project の分に置き換わり、増えていません)。位置は bind の並び(管理区間の先頭側)のままで、値だけが project のものになっています。
pool の値は、いちばん弱い層です。project が環境の値を持っていれば、そちらが勝ちます。
受け取りをやめるのは global drop です。画面では、Global keys タブの行の Unbind ボタンが同じ操作です(CLI のヘルプでも drop は「Unbind an org global key from this project.」と説明されています)。GH_TOKEN と SENTRY_DSN を drop すると、global status は CLOUDFLARE_API_TOKEN の 1 行になります。その後、撮影のために置いた project 側の SENTRY_DSN を dotvault delete で消して、pull すると 6 key(5 回目までの 5 key + CF_API_TOKEN)に戻ります。drop は project の bind を外すだけで、pool の key はそのまま残っています。
6b に進む前に、CLOUDFLARE_API_TOKEN の production の上書きを CLI で消しておきます。dotvault global delete CLOUDFLARE_API_TOKEN -E production --org <id> で、出力は「deleted 1 key(s) from the global pool [production]」です。この後の global keys で CLOUDFLARE_API_TOKEN の envs は common だけになります(どちらの出力も、7 節の最後の端末画面にあります)。6b の画面で CLOUDFLARE_API_TOKEN の行が Common だけなのは、このためです。
6b. 画面で bind し直す(alias を付ける)
drop した SENTRY_DSN を、今度は画面から、別の名前を付けて bind し直します。画面では、project の Global keys タブです。pool の key が全部並び、この project が bind しているかどうかが BOUND AS の列に出ます。
SENTRY_DSN は直前に drop したので、Bind のボタンが出ています。CLI で bind した CLOUDFLARE_API_TOKEN は、画面にも CF_API_TOKEN として出ています。SENTRY_DSN の行の alias (optional) の欄に SENTRY_DSN_WEB と入れて Bind を押すと、「Bound SENTRY_DSN.」と出て、BOUND AS に SENTRY_DSN_WEB が入ります。
project の Keys タブは project に置いた key の一覧で、development は 5 回目の終わりと同じ 4 key(development の値として置いたもの)です。5 回目の .env が 5 key だったのは、残りの 1 つ API_BASE_URL が、3 回目に project の Common (_shared) に置いた値だからです。bind の状態を見る場所は、Global keys タブと global status です。
画面で bind したものは CLI の global status にも出ます。
global status は CLOUDFLARE_API_TOKEN -> CF_API_TOKEN と SENTRY_DSN -> SENTRY_DSN_WEB の 2 行で、pull は 7 key、.env には SENTRY_DSN_WEB=… として書かれます。7 回目はこの状態から始めます。
7. pool から消す
消す操作は 2 段あります。上書きだけ消すか、key ごと消すかです。
画面では、行の Delete override を押すと、消す環境を選ぶ欄と Delete override ボタンが出ます(CLI では、下の global delete … -E production が同じ、production の上書きだけを消す操作です)。
Delete key は、共通の値と上書きを全部消します。押すと、行の下に「This deletes the common value and every override (1).」と、上書きの数を添えた確認が出て、もう一度 Delete を押します。
Delete を押すと、さらにブラウザの確認ダイアログが出て、消す key の名前が入ります。捨てるために作った key TMP_DELETE_ME で確かめると、「Delete TMP_DELETE_ME entirely (the common value and every override)? This cannot be undone.」と出ました(ブラウザのダイアログは画像に撮れないので、文言だけ引用します)。
CLI では、global delete KEY が共通の値、global delete KEY -E production が production の上書きだけです。下の画面は、6 節の最後の CLOUDFLARE_API_TOKEN の上書きの削除を撮ったものです。撮影では、その直前に素の set も試しています(そのため、途中の 3 行の global keys では、CLOUDFLARE_API_TOKEN にまだ production の上書きがあります)。
この画面の最初の 2 行は、別の話です。project-config.json の無いディレクトリで、global を付けない素の dotvault set BARE_TEST=1 を実行すると、「set 1 key(s) in the global pool [_all]」と出ます。project が決まらない set は、pool に書きます。--org も要りません。1 つの org にしか入っていなければ、素の set は org を自動で決めます。global のコマンドには、3 節のとおり --org を付けます。4 回目の set を、project-config.json の無い場所で実行すると、project ではなく pool に入る、ということです。意図せず pool に書いたら、global keys で見て global delete で消します。
最後の global keys で、CLOUDFLARE_API_TOKEN の envs が common だけになりました。production の上書きを消しても、共通の値は残ります。
8. 権限と service token
この回の org は Personal(自分 1 人の org)なので、権限の設定はありません。CI に渡す service token は 7 回目で、人を招いた org で誰が pool に書けるかは 9 回目の権限の回で、画面を見ながら扱います。
9. うまくいかない時の出力
この回で出た失敗と、名前が間違っていても成功の文が出る操作をまとめます。
| 操作 | 出力 | 終了コード |
|---|---|---|
global keys / global set を --org 無しで |
ORG_CONTEXT_REQUIRED: X-Org-Context header required to resolve an org for this session | 1 |
global describe NO_SUCH_KEY "…"(pool に無い名前) |
described NO_SUCH_KEY in the global pool [_all](key は作られない) | 0 |
global use GH_TOKEN(pool に無い名前) |
Bound GH_TOKEN … + Warning: the global project has no value yet for: GH_TOKEN | 0 |
global drop NOT_BOUND(bind していない名前) |
Dropped NOT_BOUND from demo-app. | 0 |
global status(project-config.json の無い場所・-p 無し) |
VALIDATION_ERROR: No project-config.json found … For the org-wide pool use dotvault global …
|
6 |
global set --org …(KEY=VALUE 無し) |
error: missing required argument 'pairs' | 1 |
1 行目は、どの org の pool かが決まらない、という意味です。--org <id> を付けて実行し直します(3 節の global keys --org … の実行例)。
2 行目と 4 行目は、pool に無い名前に説明を付けても、bind していない名前を drop しても、成功の文で 0 が返ります。名前が合っているかは、global keys と global status で確かめます。3 行目の use には、警告が付きます。
この表は CLI 0.15.7 の出力です。名前の入力の間違いや bind の漏れは、4 節と 5 節のとおり global status と pull の出力で確かめます。
次回
7 回目は、CI に人のパスワードを置かずに secret を渡す回です。画面で発行した service token 1 つで、GitHub Actions が production の値を受け取るところまで進めます。この回で bind した CF_API_TOKEN と SENTRY_DSN_WEB も、7 回目の pull の結果に出てきます。
dotvault は、.env や設定ファイルの secret をサーバー側に置き、権限のある人・CI・AI エージェントが 1 コマンドで受け取るサービスです(https://dotvault.io)。この連載が終わる時に、dotvault を誰でも無料で使える形で公開する予定です。公開したら、この連載の手順どおりに使い始められます。
興味のある方は、ぜひ「いいね」と記事の購読をお願いいたします。
自社プロダクトは https://www.eln.ne.jp/products にまとめています。
筆者について
上原正吉。EarthLink Network Co., Ltd. 代表。AI社員に日々の業務を任せ、開発では人が逐一指示しなくても作業が進むHOOTL(Human Out Of The Loop)の仕組みを自社で実践しています。このブログでは、業務自動化と24時間自動開発のノウハウ、具体的な設定方法、日々の運用で見つかった問題と改善を共有します。
また、AIで業務や開発を組み替えたい会社・チーム向けに、AI活用のコンサルティングも受け付けています。ご相談は www.eln.ne.jp からどうぞ。
EarthLink Network は、会社の全業務を AI で回すために、必要になったものを自社で作っています。いま作っているプロダクトの一覧と概要は、こちらにまとめています。
→ EarthLink Network が自社でつくっている18のプロダクト
会社と各プロダクトの詳細は、公式サイト www.eln.ne.jp をご覧ください。





![CLI で pool に値を置く端末画面(再構成)。dotvault whoami で email と「org: org_personal_f4785468-… Personal (personal, org_admin)」、dotvault global keys で「ORG_CONTEXT_REQUIRED: X-Org-Context header required to resolve an org for this session」、--org を付けた global keys で SENTRY_DSN の行(envs 列が common,production)、global set CLOUDFLARE_API_TOKEN=… で「set 1 key(s) in the global pool [_all]」、-E production を付けて「[production]」、global describe で「described CLOUDFLARE_API_TOKEN in the global pool [_all]」、最後の global keys で 2 行。](https://qiita-user-contents.imgix.net/https%3A%2F%2Fraw.githubusercontent.com%2FEarthLinkNetwork%2Fblog_public%2Fmain%2Fimages%2Fdvlt-006%2F6-4-global-set.png?ixlib=rb-4.1.1&auto=format&gif-q=60&q=75&s=f4dd9642b0c70791c47e65106424c219)


![同じ名前の key の端末画面(再構成)。dotvault set SENTRY_DSN=https://projectOwnKey@… で「set SENTRY_DSN (v1) in .env [demo-app/development]」、dotvault pull で 7 keys、grep -n SENTRY .env で 6 行目が projectOwnKey の値、dotvault global drop GH_TOKEN と global drop SENTRY_DSN で「Dropped … from demo-app.」、global status で CLOUDFLARE_API_TOKEN -> CF_API_TOKEN の 1 行、dotvault delete SENTRY_DSN で「deleted 1 keys from .env」、dotvault pull で 6 keys、grep で 5 行目の CF_API_TOKEN だけ。](https://qiita-user-contents.imgix.net/https%3A%2F%2Fraw.githubusercontent.com%2FEarthLinkNetwork%2Fblog_public%2Fmain%2Fimages%2Fdvlt-006%2F6-7-project-wins.png?ixlib=rb-4.1.1&auto=format&gif-q=60&q=75&s=857d02af5891643ebd8074993fffdad5)





![bare set と delete の端末画面(再構成)。project-config.json の無い /work/nowhere で、dotvault set BARE_TEST=1 で「set 1 key(s) in the global pool [_all]」、global keys で BARE_TEST を含む 3 行、global delete BARE_TEST で「deleted 1 key(s) from the global pool [_all]」、global delete CLOUDFLARE_API_TOKEN -E production で「[production]」、global keys で CLOUDFLARE_API_TOKEN の envs が common だけになった 2 行。](https://qiita-user-contents.imgix.net/https%3A%2F%2Fraw.githubusercontent.com%2FEarthLinkNetwork%2Fblog_public%2Fmain%2Fimages%2Fdvlt-006%2F6-8-bare-set.png?ixlib=rb-4.1.1&auto=format&gif-q=60&q=75&s=630a8f85754f8d7c18a093d8e9a1a535)
![失敗の端末画面(再構成)。dotvault global keys で ORG_CONTEXT_REQUIRED と 1、global describe NO_SUCH_KEY "does not exist" --org … で「described NO_SUCH_KEY in the global pool [_all]」と 0、global drop NOT_BOUND で「Dropped NOT_BOUND from demo-app.」と 0、config の無いディレクトリでの global status で VALIDATION_ERROR(No project-config.json found …)と 6、global set --org … で「error: missing required argument 'pairs'」と 1。](https://qiita-user-contents.imgix.net/https%3A%2F%2Fraw.githubusercontent.com%2FEarthLinkNetwork%2Fblog_public%2Fmain%2Fimages%2Fdvlt-006%2F6-14-gotchas.png?ixlib=rb-4.1.1&auto=format&gif-q=60&q=75&s=3999320d50c59711df60a7cfba67dd85)