【1Password】.env.local をやめて op run で秘密情報を渡すようにした

この記事でわかること
- 1Password の参照(op://)だけを並べた .env.op をコミットして、op run で値を渡す方法
- npm スクリプトと自作スクリプトを、.env.local が無くても動くようにする対応
- Terraform 用のトークンをアプリ側と分けておく理由
- 値を画面に出さずに移行して、消す前に一致を確かめた手順
macOS で 1Password のデスクトップアプリと 1Password CLI(op)を使っている前提です。CI 側は GitHub Secrets のままで、この記事では触れていません。
概要
この記事では、このブログのリポジトリで使っていた .env.local をやめて、秘密情報を 1Password から op run で渡すようにした話についてまとめています。
それまでは .env.local と、wrangler 用にそこからコピーした .dev.vars の2つのファイルに、API キーなどが平文で入っていました。全部で14項目です。
きっかけは、記事に「参考になった」ボタンを付けたときに、Terraform 用の Cloudflare API トークンを新しく作ったことです。
また .env.local に1行増やすのか...と思って、そういえば 1Password でまとめて管理できないかなと考えました。
対象者
対象者は下記のような方々になります。
.env.localに API キーを平文で置いていて、なんとなく気になっている方- 1Password を使っていて、CLI も使ってみたい方
- Next.js や wrangler でローカル開発をしている方
動作環境
| 項目 | バージョンなど |
|---|---|
| OS | macOS |
| パスワード管理 | 1Password デスクトップアプリ + 1Password CLI(op) |
| Next.js | 15.5 |
| wrangler | 4.152 |
op run とは
op run は 1Password CLI のコマンドで、op://保管庫/アイテム/フィールド という形の参照を、実行時に実際の値に置き換えて環境変数として渡してくれます。
値はファイルには残らず、実行しているプロセスにだけ渡るので、ディスクに平文の秘密情報を置かなくて済みます。
手順
この章では順番に手順を説明していこうと思います。
1Password にアイテムを作る
まずは 1Password の保管庫 Private に kt-tech.blog というアイテムを作りました。
フィールドのラベルは環境変数名にそろえています。
こうしておくと、参照が下記のように環境変数名と同じ形で書けるので割と分かりやすいです。
op://Private/kt-tech.blog/NOTION_API_KEY.env.op をコミットする
次に、参照だけを並べた .env.op をリポジトリのルートに置きます。値は入っていないのでコミットして大丈夫です。
.env.op の一部はこんな感じです。
# アプリ本体(next dev / build / wrangler pages dev)
NOTION_API_KEY=op://Private/kt-tech.blog/NOTION_API_KEY
NOTION_DATABASE_ID=op://Private/kt-tech.blog/NOTION_DATABASE_ID
NEXT_PUBLIC_GA_ID=op://Private/kt-tech.blog/NEXT_PUBLIC_GA_ID
SITE_URL=op://Private/kt-tech.blog/SITE_URL
# アイキャッチ生成などのスクリプト(scripts/*.mjs)
GEMINI_API_KEY=op://Private/kt-tech.blog/GEMINI_API_KEY実行するときは下記のようにします。
op run --env-file=.env.op -- <コマンド>毎回打つのは面倒なので、package.json にスクリプトを追加しました。
"dev": "next dev",
"dev:op": "op run --env-file=.env.op -- next dev",
"preview:op": "op run --env-file=.env.op -- npm run preview",npm run dev は消さずに残しています。.env.local で動かしたい場合や、1Password が無い環境のためです。
ちなみに .dev.vars は、npm run preview:op を実行したときに .env.op に名前があるキーだけを書き出して作り直すようにしています。なので普段は消したままで大丈夫です。
スクリプトは process.env を優先する
アイキャッチ生成などのスクリプトは、今まで .env.local を自前で読み込んでいました。
これだと .env.local が無いと動かないので、process.env にあればそちらを優先して、無いキーだけ .env.local から埋めるように変えました。
scripts/load-env.mjs はこんな感じです。
export function loadEnv(path = '.env.local') {
if (!existsSync(path)) return;
for (const [key, val] of Object.entries(parseEnvFile(path))) {
if (!process.env[key]) process.env[key] = val;
}
}.env.local が無くてもエラーにはしていません。足りない値はそれぞれのスクリプト側で確認しています。
Terraform 用は分ける
Terraform で使う Cloudflare の API トークンは、infra/.env.op に分けました。
CLOUDFLARE_API_TOKEN=op://Private/kt-tech.blog/CLOUDFLARE_API_TOKEN
CLOUDFLARE_ACCOUNT_ID=op://Private/kt-tech.blog/CLOUDFLARE_ACCOUNT_IDこのトークンは D1 や Pages、R2 などの編集権限を持っているので、next dev や wrangler にまで渡したくなかったからです。
アプリ側の .env.op には入れていません。
Terraform は infra/tf.sh というラッパー経由で動かしていて、環境変数が無ければ自分自身を op run の中で呼び直すようにしています。
if [[ -z ${CLOUDFLARE_API_TOKEN:-} || -z ${CLOUDFLARE_ACCOUNT_ID:-} ]]; then
export KT_TF_UNDER_OP=1
exec op run --env-file=.env.op -- "./$(basename "$0")" "$@"
fi(実際には op が無い場合や、呼び直しても空だった場合のエラー処理も入れています)
CLI と連携をオンにする
1Password デスクトップアプリの設定で「CLI と連携」をオンにすると、op を実行したときに Touch ID で許可する形になります。
パスワードを毎回打たなくていいのでめちゃくちゃ楽です。
1つ小ネタで、この連携モードだと op whoami はエラーになるのですが、op read などは普通に動きます。
なので op whoami が通らなくても、連携できていないわけではないので注意です。
移行
この章では .env.local から 1Password に移した手順について書いていきます。
値を画面に出さずに流し込む
移行するときは、値をターミナルに出さないようにスクリプトで .env.local から 1Password に流し込みました。
その後、.env.local の値と 1Password から読んだ値をハッシュで比較して、全13項目が一致するのを確認しています。
値そのものを見比べると画面やログに残ってしまうので、ハッシュで比べるのが良いかなという感じです。
消す前に動作確認する
.env.local が無い状態で npm run dev:op が動くのを確認してから、.env.local と .dev.vars を消しました。
用途が分からないトークン
移行の途中で、.env.local に古いトークンが残っているのを見つけました。
正直何に使っていたのか思い出せなかったのですが、消して何かが動かなくなるのも怖いので、念のため 1Password に LEGACY として退避しています。(いつかちゃんと整理します...)
CI はそのまま
GitHub Actions の CI/CD は今まで通り GitHub Secrets を使っていて、今回は変えていません。
感想
.env.local は .gitignore に入っているので、正直これまであまり気にしていませんでした。
ただ、Terraform 用に強い権限のトークンが増えたタイミングで見直してみると、同じ値が .env.local と .dev.vars の2か所に平文で置いてあったので、移してよかったなと思いました。
参照だけのファイルをコミットできるので、どの環境変数が必要なのかがリポジトリを見れば分かるようになったのも個人的にはありがたいです。
おわりに
.env.local に秘密情報を置いている方は、1Password を使っているならぜひ試してみてください。
間違っている点等ありましたら指摘いただけると嬉しいです。


