目次
「ステージングでは通ったのに本番でだけ落ちる」「ステージングのURLがGoogleにインデックスされていた」「テストのつもりが本番のDBに書き込んでいた」。環境を分けているつもりでも、分け方が曖昧だとこうした事故は繰り返し起きます。逆に、環境を増やしすぎて誰も全体を把握できなくなっているチームもあります。
この記事では、開発・ステージング・本番の3環境それぞれの目的を整理したうえで、構成をコードで揃えて差分をなくす方法、環境ごとの設定の注入、本番データに頼らないテストデータ戦略、ステージングを外部から守るアクセス制御、コストを抑える運用、そして環境が増えすぎる問題への対処を解説します。デプロイの自動化そのものはCI/CDパイプラインの基礎を前提とします。
3つの環境の目的を明確にする
開発・ステージング・本番の役割分担
| 環境 | 目的 | データ | 誰が触るか |
|---|---|---|---|
| 開発(dev / local) | 機能を作り、壊しながら試す | ダミー・最小限のシード | 開発者個人 |
| ステージング(staging) | 本番と同じ構成で最終確認・受け入れテスト | 本番相当の量と形の匿名化データ | 開発者・QA・発注側の確認者 |
| 本番(production) | 実際のユーザーにサービスを提供する | 実データ | ユーザー・限られた運用担当 |
ステージングの価値は「本番と同じかどうか」で決まります。OSやミドルウェアのバージョン、環境変数の種類、外部サービスとの接続方式、ロードバランサーやCDNの有無が本番と違えば、そこで確認した結果は本番の保証になりません。一方、開発環境はスピードが優先で、Docker Composeで手元に立てられる軽さの方が重要です。
本番との「意図した差分」と「意図しない差分」
すべてを本番と同一にするとコストが跳ね上がるため、差分はゼロにはできません。重要なのは、差分を意図して決めたもの(サーバー台数・インスタンスサイズ・外部決済のテストモード)と、放置された結果生まれたもの(手動でインストールしたライブラリ・設定ファイルの直接編集)に分けて、後者を排除することです。
構成をコードで揃え、環境差分をなくす
同じ定義ファイルから環境ごとの値だけ変える
環境差分を根絶する最も確実な方法は、構成を人手で作らないことです。IaC(Terraformなど)でインフラを定義し、同じモジュールに環境ごとの変数だけを渡して作成すれば、「ステージングにだけあるはずのないセキュリティグループ」のような差分は構造的に生まれません。アプリ側もDockerイメージを一度ビルドしたら、そのイメージをタグで固定してステージングと本番の両方にデプロイします。環境ごとにビルドし直すと、依存関係の解決タイミングの違いで別物になる可能性があります。
# Terraformで環境ごとの変数ファイルだけを切り替える
infra/
├── modules/web-service/ # 共通定義(LB・ECS・RDS など)
├── envs/
│ ├── staging.tfvars # instance_count = 1, db_class = "db.t4g.small"
│ └── production.tfvars # instance_count = 3, db_class = "db.r6g.large"
terraform workspace select staging
terraform apply -var-file=envs/staging.tfvars
環境ごとの設定はコードに書かず、外から注入する
接続先DBやAPIキー、外部サービスのモードなど環境で変わる値は、コードやイメージに埋め込まず、実行時に環境変数やシークレットマネージャーから注入します。コード内に if (env === 'production') のような分岐を増やすと、テストされていない本番専用パスが生まれ、ステージングで検証できない領域が広がります。
# 環境変数で切り替える(コードは同一)
# staging
DATABASE_URL=postgres://app:***@staging-db.internal:5432/app
STRIPE_SECRET_KEY=sk_test_xxx
APP_ENV=staging
ROBOTS_INDEX=false
# production
DATABASE_URL=postgres://app:***@prod-db.internal:5432/app
STRIPE_SECRET_KEY=sk_live_xxx
APP_ENV=production
ROBOTS_INDEX=true
# 起動時に必須の環境変数を検証する(Node.jsの例)
const required = ['DATABASE_URL', 'STRIPE_SECRET_KEY', 'APP_ENV'];
for (const key of required) {
if (!process.env[key]) throw new Error(`Missing env: ${key}`);
}
起動時に必須変数の存在をチェックしておくと、「ステージングにだけ設定し忘れた変数」がデプロイ直後に判明し、動いてから気づく事故を防げます。シークレットの保管場所の選び方はシークレット管理入門で詳しく扱っています。
本番データを使わないテストデータ戦略
本番データのコピーが危険な理由
「本番のダンプをステージングに流す」運用は手軽ですが、個人情報がアクセス制御の緩い環境に複製されることになり、漏えい時の影響範囲が本番と同じになります。開発者のPCにまでコピーされると、もはや管理不能です。また、本番データにはメールアドレスや決済情報が含まれるため、ステージングでバッチを動かした結果、実在のユーザーに通知メールが飛ぶ事故も起きます。
現実的な3段階
- シードデータ:開発環境向けに、主要な状態(一般ユーザー・管理者・退会済み・決済失敗など)を網羅した少量の固定データをスクリプトで投入する。
- 生成データ:ステージングの負荷や画面表示の確認向けに、Fakerなどで本番相当の件数を生成する。日本語氏名・住所・電話番号の形式を本番に寄せる。
- 匿名化コピー:本番と同じ分布でしか再現できない不具合の調査にだけ、メール・氏名・住所をハッシュや置換で潰したうえで、期限付きで利用する。匿名化スクリプト自体をコードとして管理し、実行ログを残す。
外部サービスは必ずテストモード(Stripeのテストキー、SESのサンドボックスなど)を使い、メール送信はMailpitやMailHogのような受信専用サーバーに向けて、ステージングから実在アドレスへ絶対に送信されない構成にしてください。
ステージングを外部から守るアクセス制御
Basic認証・IP制限・noindexの三重化
ステージングは本番と同じコードが動いているため、未公開の機能や脆弱性を含んだ状態で外部に露出しやすい場所です。検索エンジンに拾われると本番と重複したコンテンツとして評価にも影響します。以下を組み合わせるのが定石です。
# Nginx: Basic認証 + IP制限(オフィスとVPNのみ許可)
server {
server_name staging.example.com;
satisfy all; # 両方の条件を満たす必要がある
allow 203.0.113.0/24; # オフィス
allow 198.51.100.10; # VPN出口
deny all;
auth_basic "Staging";
auth_basic_user_file /etc/nginx/.htpasswd;
# クローラー対策(Basic認証があっても保険として付ける)
add_header X-Robots-Tag "noindex, nofollow" always;
location / {
proxy_pass http://app;
}
}
加えて、robots.txtでDisallowし、アプリ側でもAPP_ENVがproduction以外なら noindex のメタタグを出すようにしておきます。外部のWebhook(決済通知など)をステージングで受ける場合は、その経路だけIP制限の例外にするか、送信元の署名検証で守ります。VPNやIP制限の代わりにCloudflare AccessやAWSのALB認証でSSOと連携すると、パスワードの使い回しをなくせます。
本番への操作権限を分離する
環境の分離はネットワークだけでなく、権限でも行います。クラウドのアカウント(AWSならアカウント自体、GCPならプロジェクト)を環境ごとに分けると、ステージング用のクレデンシャルでは本番リソースに物理的に届かなくなります。同一アカウント内でタグやIAMポリシーだけで分ける方法は、ポリシーの書き間違い一つで境界が消えるため、可能ならアカウント分離を優先してください。
運用の落とし穴|コスト・環境の増えすぎ・分離不足の事例
必要なときだけ起動する
ステージングは24時間動いている必要はありません。営業時間外や休日に自動停止するスケジュールを組むだけで、稼働時間を大きく減らせます。EC2やRDSなら停止でコンピュート課金が止まり、コンテナ環境ならタスク数を0にするだけです。プルリクエストごとに一時環境を作る「プレビュー環境」も、マージやクローズで自動削除する仕組みとセットで導入します。
# AWS: 平日夜と休日にステージングのECSサービスを0台にする(EventBridge Scheduler)
aws scheduler create-schedule --name staging-stop \
--schedule-expression "cron(0 21 ? * MON-FRI *)" \
--schedule-expression-timezone "Asia/Tokyo" \
--flexible-time-window Mode=OFF \
--target '{"Arn":"arn:aws:scheduler:::aws-sdk:ecs:updateService","RoleArn":"arn:aws:iam::123456789012:role/scheduler-ecs","Input":"{\"Cluster\":\"staging\",\"Service\":\"web\",\"DesiredCount\":0}"}'
環境の数を増やす前に問う3つのこと
QA環境、UAT環境、デモ環境、負荷試験環境と増えていくと、どれが最新のコードなのか、どれに本番相当のデータがあるのか、誰も答えられなくなります。新しい環境を作る前に「既存環境の時間分割(プレビュー環境や時間帯の使い分け)で代替できないか」「その環境の削除条件は決まっているか」「IaCから同じ手順で作れるか」を確認してください。手作業で作った環境は、必ず"意図しない差分"の温床になります。
トラブル事例:ステージングのバッチが本番に書き込んだ
症状:ステージングで夜間バッチをテスト実行したところ、本番のユーザーにポイント付与の通知メールが届いた。
原因:バッチのコンテナには環境変数でステージングDBが指定されていたが、バッチ内の一部モジュールが古い設定ファイル(config/production.yml)を直接読み込んでおり、本番DBの接続情報がハードコードされていた。ステージングと本番が同一AWSアカウント内にあり、ネットワーク的にも本番DBへ到達できた。
対処:設定ファイルの直接読み込みを廃止して環境変数に一本化し、本番接続情報をリポジトリから完全に削除。さらにステージング用のセキュリティグループから本番DBへの経路を遮断し、最終的にAWSアカウントを環境ごとに分離した。「環境変数で分けている」だけでは足りず、ネットワークと権限でも到達できない状態にして初めて分離と呼べる、という教訓になった事例です。
まとめ
環境分離の目的は、本番と同じ条件で検証しながら、本番を壊さず、本番データを漏らさないことです。そのためには、構成をIaCとイメージで揃えて意図しない差分をなくし、設定は外から注入し、テストデータは本番を使わずに用意し、ステージングはBasic認証・IP制限・noindexで守り、権限とネットワークでも本番に届かないようにします。コストは自動停止で抑え、環境の数は削除条件とセットで増やしてください。GitOpsのようにGitを正とする運用まで進めると、環境間の差分はプルリクエストの差分として常に見える状態になります。自社の環境構成を見直したい場合は、Harmonic Societyのシステム開発・インフラ支援にご相談ください。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
- プログラミング
GitOps入門|ArgoCD・FluxでGitをインフラの正とするデプロイ運用
GitOpsのPush型とPull型の違い、差分検知と自動同期の仕組み、リポジトリ構成、ArgoCDの最小導入手順までを解説。git revertで戻せるデプロイ運用が理解でき、Kubernetes以外への応用の考え方もわかります。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します