環境変数の既定値が staging バケットのままだと、destroy で本番画像が消える
※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。
結論
環境変数の既定値(フォールバック)に他環境のバケット名を残したまま本番で seed を実行すると、その値はログにも現れないまま本番データベースに書き込まれ、後日その環境を destroy した瞬間に本番の参照先ごと消える。
結論
環境変数に既定値(フォールバック)を持たせるとき、その既定値が他環境(staging)に実在するリソースを指していると、呼び出し側が明示的な上書きを忘れても正常に動作してしまいます。 動作してしまう分だけ、上書き漏れに気づく手段がありません。今回は本番向けの seed スクリプトがこの既定値をそのまま本番データベースへ書き込み、後日 staging 環境を destroy した瞬間に、本番が参照していたバケットごと画像が消えました。
症状
サイネージ向けアプリケーションで、画像アセット(広告バナー)を Cloud Storage の公開バケットから配信しています。DB の media_url カラムにバケットの公開 URL を保存し、端末側はその URL を直接 GET する構成です。
ある日、staging 環境をコスト削減のために撤去(terraform destroy)したところ、本番で表示していた画像 5 枚が突然 404 になりました。 staging を消しただけで、なぜ本番の画像が消えるのか、最初は原因が分かりませんでした。
原因
media_url を書き込む seed スクリプトは、バケットのベース URL を環境変数から読みつつ、未設定の場合に備えて既定値を持っていました。
export const AD_MEDIA_BASE =
process.env.SEED_AD_MEDIA_BASE ??
"https://storage.googleapis.com/myapp-staging-media/assets";
問題は、この既定値が staging のバケットを指していたことです。本番環境のデプロイ設定では SEED_AD_MEDIA_BASE を明示的に上書きしておらず、?? による既定値がそのまま採用されていました。
process.env.X ?? デフォルト値 という書き方自体は、未設定時にも動作を止めないための一般的なパターンです。しかしそのデフォルト値が「別環境の、現に存在するリソース」を指していた場合、上書き漏れはエラーにならず、静かに他環境のリソースへ依存した状態のまま本番運用が始まります。 staging バケットのオブジェクトが実在する間は画像は正常に表示されるため、この依存関係はコードを読まない限り表に出てきません。
表面化したのは、staging 環境そのものを terraform destroy した瞬間でした。バケットが消え、本番 DB に焼き込まれていた media_url が指す先が失われ、5 枚の画像が一斉に 404 になりました。
直し方
既定値を staging ではなく本番バケットに変更し、環境をまたいだ暗黙の依存を断ちました。
export const AD_MEDIA_BASE =
process.env.SEED_AD_MEDIA_BASE ??
"https://storage.googleapis.com/myapp-prod-media/assets";
すでに DB に書き込まれてしまっていた media_url は、seed を再実行するだけでは直りません。参照先のバケットにオブジェクトが存在しないままなので、消えた画像は原本から本番バケットへ再アップロードし直す必要がありました。
再発防止
この既定値を直したコミットには、「以後 prod 以外を既定にしないこと」という注記をコード自体に残しました。環境変数の既定値を持つコード全てに同じ確認を求める仕組みまでは作れていませんが、少なくともこのファイルについては、次に触る人が同じ理由でもう一度 staging を既定に戻すことがないよう、事故の経緯をコードのそばに置いています。
環境変数に既定値を持たせる設計そのものは今後も使います。ただし既定値の選び方は変えました。「上書きを忘れても動く値」ではなく「上書きを忘れたらすぐ壊れる値」を既定にするという基準です。他環境の実在するリソースを既定にすると、上書き漏れは正常動作に紛れて発見が遅れます。存在しないバケット名や空文字であれば、上書き漏れはその場でエラーになり、staging を消すタイミングまで持ち越されることはありません。
環境をまたいだ設定の巻き添えという意味では、terraform の enabled = false では環境を撤去できない で書いた撤去まわりの落とし穴とも根が近く、「片方の環境を消す操作が、もう片方に何を残しているか」は都度確認したほうがいい対象だと考えています。
よくある質問
Q1本番の環境変数を上書きし忘れても、なぜ気づけなかったのですか?
既定値(フォールバック)は明示的に上書きしなくても動作するため、seed 実行時にエラーは出ません。DB に書き込まれた URL 文字列そのものを見比べない限り、どちらの環境のバケットを指しているかはログにも現れず、気づく手段がありませんでした。
Q2staging を destroy しなければ問題は表面化しなかったのですか?
はい。既定値が誤って staging のバケットを指していても、staging 環境が存在するあいだはオブジェクトが実在するため画像は正常に表示されます。問題が表面化するのは、コスト削減などの理由で staging 環境そのものを撤去した瞬間です。
Q3同じ事故を防ぐには既定値をどう設計すればよいですか?
既定値は「呼び出し側が上書きを忘れたら、すぐに壊れて分かる値」にすることです。他環境の実在するリソースを指す値を既定にすると、上書き漏れが正常動作に紛れて長期間発覚しません。存在しないバケット名や空文字を既定にしておけば、上書き漏れはその場でエラーになります。
確認した環境
- Terraform / Google Cloud Storage(バケットは環境変数の既定値で解決)
- 2026-08-04 のコミット時点
この記事の根拠
- TypeScriptファイル 28〜40行目コミット 5d81772
本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。