- 設計
開発環境・検証環境・本番環境の違い|環境定義書テンプレート
開発環境は作る場所、検証環境はテストする場所、本番環境は利用者が使う場所です。同じプログラムを、目的の違う複数の場所で動かすために環境を分けます。その間に、本番と同じ構成でリリース前の最終確認をするステージング環境と、開発者の手元のローカル環境が加わり、5つに分けて考えると整理しやすくなります。
この記事では、5環境の違いを早見表で比べたうえで、環境一覧と環境定義書(環境ごとに違う設定値の一覧)のテンプレートを、架空の「備品予約システム」の記入例つきで全文載せます。後半では、環境変数ファイルの差分を diff コマンドと Python で確かめた実行結果を示します。
確認環境:Ubuntu 24.04 / GNU diffutils 3.10(diff)/ bash / Python 3.11.15。確認日 2026-10-05。
この記事の結論
- 環境の違いは、目的・利用者・データ・構成・デプロイ契機・外部連携先の6つで比べると整理できます。
- 検証環境は「機能が仕様どおりか」を確かめる場所、ステージング環境は「本番と同じ構成・手順でリリースできるか」を確かめる場所です。
- 環境の数は、規模と要員で決めます。小さく始めるなら3環境(ローカル・検証・本番)、利用部門の受け入れや性能確認があるなら5環境が目安です。
- 環境ごとの違いは、環境一覧(どんな環境があるか)と環境定義書(設定値がどう違うか)の2つの表にまとめます。
- 定義書と実際の設定ファイルは、diff やスクリプトで機械的に突き合わせるとずれに気づけます。
5環境の違い早見表
| 観点 | ローカル環境 | 開発環境 | 検証環境 | ステージング環境 | 本番環境 |
|---|---|---|---|---|---|
| 目的 | 実装と単体テスト | 結合の確認、動作の共有 | 結合テスト・総合テスト | リリース手順の予行、受け入れ確認、性能確認 | 利用者へのサービス提供 |
| 利用者 | 開発者本人 | 開発チーム | テスト担当、開発チーム | 利用部門、運用担当、テスト担当 | 利用者、運用担当 |
| データ | 初期データ(シード) | シード+手入力 | テストケース用に作ったデータ | 本番相当の件数(個人情報はマスク) | 本番データ |
| 構成 | 開発者のPC | 最小構成のサーバー | 最小構成のサーバー | 本番と同じ構成 | 冗長化した構成 |
| デプロイ契機 | 保存のたび | メインのブランチへのマージ | テスト担当が版を指定 | リリース候補ができたとき | リリース判定の承認後 |
| 外部連携先 | モック | モック | 連携先のサンドボックス | 連携先のサンドボックス | 連携先の本番 |
| 壊してよいか | よい | よい(チームに声をかける) | テスト中は固定 | 本番と同じ扱い | 不可 |
| 英語の略称の例 | local | dev | test、qa | stg、staging | prod、production |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「開発環境・検証環境・本番環境の違い|環境定義書テンプレート」
各環境の役割と、呼び名の対応
環境の呼び名は組織によって違います。最初に、自分たちの呼び名がどれに当たるかを揃えておくと、会話の食い違いが減ります。
| この記事での呼び名 | 別の呼び名の例 | 一言でいうと |
|---|---|---|
| ローカル環境 | 個人環境、手元の環境 | 1人で作って動かす場所 |
| 開発環境 | 開発サーバー、結合環境、dev | みんなのコードを合わせて動かす場所 |
| 検証環境 | テスト環境、QA環境、評価環境 | テストケースに沿って確かめる場所 |
| ステージング環境 | 本番相当環境、受け入れ環境、プレ本番、リハーサル環境 | 本番と同じ形で最終確認する場所 |
| 本番環境 | プロダクション、商用環境 | 利用者が使う場所 |
「開発環境」は、開発者のPCを指す場合と共有の開発サーバーを指す場合があります。「検証環境」も、テスト用の環境を指す場合とステージング環境を含めて指す場合があります。環境一覧に環境IDと目的を書いて、呼び名ではなくIDで呼ぶと取り違えを防げます。
変更は、ローカル → 開発 → 検証 → ステージング → 本番の順に進みます。後ろの環境ほど本番に近く、変更の頻度は下がり、変更の手続きは増えます。
検証環境とステージング環境の違い
検証環境とステージング環境はどちらも「テストする場所」ですが、確かめる対象が違います。
| 観点 | 検証環境 | ステージング環境 |
|---|---|---|
| 確かめること | 機能が仕様どおりに動くか | 本番と同じ構成・手順・データ量で問題なく動くか |
| 主なテスト | 結合テスト、総合テスト、不具合修正の確認 | 受け入れテスト、性能テスト、リリース手順とリストア手順の予行 |
| 構成 | 最小構成でよい | 本番と同じ(台数・冗長化・ミドルウェアの版・ネットワーク経路) |
| データ | テストケースに合わせて作る。作り直しやすさを優先 | 本番相当の件数。個人情報はマスクして使う |
| 載せる版 | テスト対象の版。テスト中は固定する | リリース候補の版だけ |
| デプロイのやり方 | 簡略でよい | 本番と同じ手順・同じ道具 |
| 設定値 | テストしやすさを優先(短い有効期限など) | 接続先以外は本番と同じ |
| 費用 | 小さい | 本番に近い |
ステージング環境の価値は「本番との差が小さいこと」そのものです。差が大きくなるほど、ステージングで通った確認が本番の裏づけになりにくくなります。後述の環境定義書で、ステージングと本番の列を並べて差分を管理するのはこのためです。
何環境用意するか:規模と要員で決める判断基準
環境は多いほど確認の機会が増えますが、費用と維持の手間も増えます。次の表を出発点にして決めます。
| 規模・要員の目安 | 用意する環境 | 考え方 |
|---|---|---|
| 1〜3人、社内向けの小さなツール | ローカル・検証・本番(3環境) | 検証環境が開発・ステージングを兼ねる。本番とのデプロイ手順だけは同じにする |
| 3〜10人、開発者とテスト担当が分かれている | ローカル・開発・検証・本番(4環境) | 開発中の変更でテストが中断されないよう、開発と検証を分ける |
| 10人以上、利用部門の受け入れや性能確認がある | ローカル・開発・検証・ステージング・本番(5環境) | 本番と同じ構成で確認する場所を独立させる |
| 複数の版を同時にテストする | 5環境+検証環境を複数(検証1・検証2) | 版ごとに検証環境を割り当てる |
条件から決める場合は、次の表を使います。
| 条件 | 選ぶもの |
|---|---|
| テスト担当が開発者と別にいる | 開発環境と検証環境を分ける |
| 利用部門が操作して受け入れを判断する | ステージング環境を用意する |
| 性能テストをする、またはデータ量で挙動が変わる処理がある | 本番と同じ構成のステージング環境を用意する |
| リリース手順やリストア手順を事前に通しで試したい | ステージング環境を用意する |
| 外部の連携先にサンドボックスが1つしかない | サンドボックスにつなぐ環境を1つに決め、ほかはモックにする |
| 費用を抑えたい | 開発・検証は使う時間帯だけ起動する。ステージングは構成を保ち、台数を減らす場合は差分として定義書に書く |
| 一時的な確認の場がほしい | 変更ごとに作って消す一時環境を検討し、常設の環境は増やさない |
環境一覧テンプレート(記入例)
環境一覧は、1行が1環境です。どんな環境があり、誰が何のために使い、誰が管理しているかを書きます。
| 環境ID | 環境名 | 目的 | 利用者 | URL | 構成 | DB | データ | デプロイ契機 | デプロイ担当 | 外部連携先 | 稼働時間 | 管理者 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| LOCAL | ローカル環境 | 実装と単体テスト | 開発者本人 | http://localhost:8080 |
開発者のPC(コンテナ) | reserve_local(PC内) | 初期データ(シード) | 保存のたび | 開発者 | モック | 随時 | 各開発者 |
| DEV | 開発環境 | 結合の確認、開発者どうしの動作共有 | 開発チーム | https://dev.reserve.example.com |
アプリ1台・DB1台(小) | reserve_dev |
シード+手入力 | main へのマージで自動 | CI | モック | 平日 8:00〜22:00 | 開発リーダー |
| TEST | 検証環境 | 結合テスト・総合テスト | テスト担当、開発チーム | https://test.reserve.example.com |
アプリ1台・DB1台(小) | reserve_test |
テストケース用データ | テスト担当の依頼で版を指定 | CI(手動承認) | 連携先のサンドボックス | 平日 8:00〜22:00 | テストリーダー |
| STG | ステージング環境 | リリース手順の予行、受け入れ確認、性能確認 | 利用部門、運用担当、テスト担当 | https://stg.reserve.example.com |
本番と同じ構成(アプリ2台・DB冗長) | reserve_stg |
本番相当の件数(個人情報はマスク) | リリース候補のタグで自動 | CI(手動承認) | 連携先のサンドボックス | 常時 | 運用リーダー |
| PROD | 本番環境 | 利用者へのサービス提供 | 利用者、運用担当 | https://reserve.example.com |
アプリ2台・DB冗長 | reserve_prod |
本番データ | リリース判定の承認後 | 運用担当(CI) | 連携先の本番 | 常時 | 運用リーダー |
表計算ソフトに貼るときは、次のタブ区切りを使ってください。
環境ID 環境名 目的 利用者 URL 構成 DB データ デプロイ契機 デプロイ担当 外部連携先 稼働時間 管理者
LOCAL ローカル環境 実装と単体テスト 開発者本人 http://localhost:8080 開発者のPC(コンテナ) reserve_local(PC内) 初期データ(シード) 保存のたび 開発者 モック 随時 各開発者
DEV 開発環境 結合の確認、開発者どうしの動作共有 開発チーム https://dev.reserve.example.com アプリ1台・DB1台(小) reserve_dev シード+手入力 main へのマージで自動 CI モック 平日 8:00〜22:00 開発リーダー
TEST 検証環境 結合テスト・総合テスト テスト担当、開発チーム https://test.reserve.example.com アプリ1台・DB1台(小) reserve_test テストケース用データ テスト担当の依頼で版を指定 CI(手動承認) 連携先のサンドボックス 平日 8:00〜22:00 テストリーダー
STG ステージング環境 リリース手順の予行、受け入れ確認、性能確認 利用部門、運用担当、テスト担当 https://stg.reserve.example.com 本番と同じ構成(アプリ2台・DB冗長) reserve_stg 本番相当の件数(個人情報はマスク) リリース候補のタグで自動 CI(手動承認) 連携先のサンドボックス 常時 運用リーダー
PROD 本番環境 利用者へのサービス提供 利用者、運用担当 https://reserve.example.com アプリ2台・DB冗長 reserve_prod 本番データ リリース判定の承認後 運用担当(CI) 連携先の本番 常時 運用リーダー
列ごとの書き方です。
| 列 | 書くこと |
|---|---|
| 環境ID | 短い英字。設定ファイル名、DB名、ホスト名に同じ文字を入れる |
| 目的 | その環境でしか確かめられないこと |
| 利用者 | 役割で書く。利用者が増えたら権限の見直しのきっかけにする |
| 構成 | 台数と冗長化の有無。本番との違いが分かるように |
| データ | 何のデータか、本番データ由来なら加工の方法 |
| デプロイ契機・担当 | 何をきっかけに、誰(何)が載せるか |
| 外部連携先 | モック・サンドボックス・本番のどれにつなぐか |
| 稼働時間 | 常時か、時間帯を区切るか |
| 管理者 | 問い合わせと変更の承認をする人 |
環境定義書テンプレート:環境差分の設定値一覧
環境定義書の中心は、設定項目を行に、環境を列に並べた差分表です。値が環境ごとに違う項目だけでなく、「全環境で同じにすると決めた項目」も載せておくと、後から値がずれたときに気づけます。
| 分類 | 設定項目 | キー | ローカル | 開発 | 検証 | ステージング | 本番 | 差分の理由・備考 |
|---|---|---|---|---|---|---|---|---|
| 基本 | 環境名 | APP_ENV |
local | dev | test | stg | prod | ログや画面の帯に表示 |
| 基本 | URL | APP_URL |
http://localhost:8080 |
https://dev.reserve.example.com |
https://test.reserve.example.com |
https://stg.reserve.example.com |
https://reserve.example.com |
本番以外は社内ネットワークからのみ |
| DB | 接続先ホスト | DB_HOST |
localhost | db-dev.internal | db-test.internal | db-stg.internal | db-prod.internal | 環境ごとに別のDB |
| DB | DB名 | DB_NAME |
reserve_local |
reserve_dev |
reserve_test |
reserve_stg |
reserve_prod |
名前に環境を入れて取り違えを防ぐ |
| DB | パスワード | DB_PASSWORD |
(各自) | (秘密情報の保管先) | (秘密情報の保管先) | (秘密情報の保管先) | (秘密情報の保管先) | 値は定義書に書かず、保管先だけ書く |
| DB | 接続プール数 | DB_POOL_SIZE |
5 | 5 | 10 | 20 | 20 | ステージングは本番と同じ |
| ログ | ログレベル | LOG_LEVEL |
DEBUG | DEBUG | INFO | INFO | WARN | 本番は量を抑える。調査時だけ一時的に INFO |
| メール | 送信モード | MAIL_MODE |
file | sandbox | sandbox | allowlist | live | 本番以外から利用者へ届かないようにする |
| メール | 送信先サーバー | MAIL_HOST |
localhost | mail-sandbox.internal | mail-sandbox.internal | smtp.internal | smtp.internal | ステージングは本番と同じ経路 |
| メール | 許可する宛先 | MAIL_ALLOWLIST |
― | ― | ― | @example.com | ― | allowlist のときだけ使う |
| バッチ | バッチ起動 | BATCH_ENABLED |
false | false | true | true | true | 開発は手動で起動 |
| 外部連携 | 予定表APIの接続先 | EXT_CALENDAR_API |
http://localhost:9000/mock |
https://mock.reserve.example.com/calendar |
https://sandbox.calendar.example.net/v1 |
https://sandbox.calendar.example.net/v1 |
https://api.calendar.example.net/v1 |
本番用の接続先は本番だけ |
| 画面 | デバッグ表示 | DEBUG | true | true | false | false | false | 詳細なエラー画面は開発まで |
| 画面 | セッション有効時間(分) | SESSION_TIMEOUT_MIN |
480 | 480 | 30 | 30 | 30 | 検証以降は本番と同じ |
| 運用 | バックアップ | (設定ファイル外) | なし | なし | なし | 日次・7日保持 | 日次・30日保持 | リストア手順はステージングで予行 |
| 運用 | 監視の通知先 | (設定ファイル外) | なし | なし | チームのチャット | チームのチャット | 運用担当の当番 | 本番だけ夜間も通知 |
表計算ソフト用のタブ区切りです。後述のスクリプトは、このファイルを env_definition.tsv という名前で読み込みます。
分類 設定項目 キー ローカル 開発 検証 ステージング 本番 差分の理由・備考
基本 環境名 APP_ENV local dev test stg prod ログや画面の帯に表示
基本 URL APP_URL http://localhost:8080 https://dev.reserve.example.com https://test.reserve.example.com https://stg.reserve.example.com https://reserve.example.com 本番以外は社内ネットワークからのみ
DB 接続先ホスト DB_HOST localhost db-dev.internal db-test.internal db-stg.internal db-prod.internal 環境ごとに別のDB
DB DB名 DB_NAME reserve_local reserve_dev reserve_test reserve_stg reserve_prod 名前に環境を入れて取り違えを防ぐ
DB パスワード DB_PASSWORD (各自) (秘密情報の保管先) (秘密情報の保管先) (秘密情報の保管先) (秘密情報の保管先) 値は定義書に書かず、保管先だけ書く
DB 接続プール数 DB_POOL_SIZE 5 5 10 20 20 ステージングは本番と同じ
ログ ログレベル LOG_LEVEL DEBUG DEBUG INFO INFO WARN 本番は量を抑える。調査時だけ一時的に INFO
メール 送信モード MAIL_MODE file sandbox sandbox allowlist live 本番以外から利用者へ届かないようにする
メール 送信先サーバー MAIL_HOST localhost mail-sandbox.internal mail-sandbox.internal smtp.internal smtp.internal ステージングは本番と同じ経路
メール 許可する宛先 MAIL_ALLOWLIST ― ― ― @example.com ― allowlist のときだけ使う
バッチ バッチ起動 BATCH_ENABLED false false true true true 開発は手動で起動
外部連携 予定表APIの接続先 EXT_CALENDAR_API http://localhost:9000/mock https://mock.reserve.example.com/calendar https://sandbox.calendar.example.net/v1 https://sandbox.calendar.example.net/v1 https://api.calendar.example.net/v1 本番用の接続先は本番だけ
画面 デバッグ表示 DEBUG true true false false false 詳細なエラー画面は開発まで
画面 セッション有効時間(分) SESSION_TIMEOUT_MIN 480 480 30 30 30 検証以降は本番と同じ
運用 バックアップ (設定ファイル外) なし なし なし 日次・7日保持 日次・30日保持 リストア手順はステージングで予行
運用 監視の通知先 (設定ファイル外) なし なし チームのチャット チームのチャット 運用担当の当番 本番だけ夜間も通知
書くときのポイントは3つです。
- パスワードや API キーの値は書かない。定義書には保管先(秘密情報の管理の仕組みの名前や項目名)だけを書きます。
- 「差分の理由」を必ず書く。理由の書けない差分は、揃えられる差分です。
- 設定ファイルの外にある差分も行にする。バックアップ、監視の通知先、サーバーの台数、ミドルウェアの版など、アプリの設定値ではない違いも同じ表で管理します。
設定を環境変数で持つ考え方は、The Twelve-Factor App の「設定」の章で、デプロイごとに変わる値(DB などのリソースへの接続情報、外部サービスの認証情報、ホスト名など)をコードから分離して環境変数に格納する方法として説明されています。同じ章は、設定を「開発」「本番」のような環境名でまとめて管理すると、環境が増えたときに組み合わせが増えていく点にも触れています。環境定義書の差分表は、その増え方を1枚で見渡すための道具です。
本番以外から利用者へメールが届かないようにする設定
差分表のうち、特に事前に決めておきたいのがメールの送信モードです。
| 送信モード | 動き | 使う環境 |
|---|---|---|
| file | 送信せず、ファイルに書き出す | ローカル |
| sandbox | 確認用のメールサーバーに送り、外へは出さない | 開発、検証 |
| allowlist | 許可したドメイン・アドレスにだけ送る | ステージング |
| live | そのまま送る | 本番 |
ステージング環境は本番相当のデータを持つので、宛先に実在の利用者のアドレスが入り得ます。データのマスクと、送信モードの両方で届かないようにしておきます。
環境変数ファイルの差分を diff で確かめる
定義書を書いたら、実際の設定ファイルと合っているかを確かめます。ここでは、5つの環境変数ファイル(.env.local、.env.dev、.env.test、.env.stg、.env.prod)を用意し、ステージングと本番を比べます。パスワードの値は説明用の作り物です。
.env.stg の内容です。
APP_ENV=stg
APP_URL=https://stg.reserve.example.com
DB_HOST=db-stg.internal
DB_NAME=reserve_stg
DB_PASSWORD=stg-pass-3
DB_POOL_SIZE=20
LOG_LEVEL=INFO
MAIL_MODE=allowlist
MAIL_HOST=smtp.internal
MAIL_ALLOWLIST=@example.com
BATCH_ENABLED=true
EXT_CALENDAR_API=https://sandbox.calendar.example.net/v1
DEBUG=true
.env.prod の内容です。
APP_ENV=prod
APP_URL=https://reserve.example.com
DB_HOST=db-prod.internal
DB_NAME=reserve_prod
DB_PASSWORD=prod-pass-4
DB_POOL_SIZE=20
LOG_LEVEL=WARN
MAIL_MODE=live
MAIL_HOST=smtp.internal
BATCH_ENABLED=true
EXT_CALENDAR_API=https://api.calendar.example.net/v1
DEBUG=false
SESSION_TIMEOUT_MIN=30
diff -u で2つの環境を比べる
diff -u .env.stg .env.prod
echo "終了コード: $?"
実行結果です。
--- .env.stg 2026-10-05 22:27:33.537024786 +0900
+++ .env.prod 2026-10-05 22:27:33.538371169 +0900
@@ -1,13 +1,13 @@
-APP_ENV=stg
-APP_URL=https://stg.reserve.example.com
-DB_HOST=db-stg.internal
-DB_NAME=reserve_stg
-DB_PASSWORD=stg-pass-3
+APP_ENV=prod
+APP_URL=https://reserve.example.com
+DB_HOST=db-prod.internal
+DB_NAME=reserve_prod
+DB_PASSWORD=prod-pass-4
DB_POOL_SIZE=20
-LOG_LEVEL=INFO
-MAIL_MODE=allowlist
+LOG_LEVEL=WARN
+MAIL_MODE=live
MAIL_HOST=smtp.internal
-MAIL_ALLOWLIST=@example.com
BATCH_ENABLED=true
-EXT_CALENDAR_API=https://sandbox.calendar.example.net/v1
-DEBUG=true
+EXT_CALENDAR_API=https://api.calendar.example.net/v1
+DEBUG=false
+SESSION_TIMEOUT_MIN=30
終了コード: 1
行頭が - の行はステージングだけ、+ の行は本番だけにある内容です。接続先やログレベルのように意図した差分に混ざって、DEBUG がステージングだけ true のままであること、SESSION_TIMEOUT_MIN が本番にしか無いこと、MAIL_ALLOWLIST がステージングにしか無いことが分かります。
diff は、差分があると終了コード 1、無いと 0 を返します。自動チェックに組み込むときは、この終了コードで判定できます。
キーの過不足だけを比べる
値まで出すと、パスワードが画面やログに残ります。共有の場で確かめるときは、キーの名前だけを取り出して比べます。
diff <(cut -d= -f1 .env.stg | sort) <(cut -d= -f1 .env.prod | sort)
echo "終了コード: $?"
実行結果です。
11d10
< MAIL_ALLOWLIST
13a13
> SESSION_TIMEOUT_MIN
終了コード: 1
< はステージングだけ、> は本番だけにあるキーです。<( … ) は bash の書き方で、コマンドの出力をファイルのように渡しています。sort を通しているので、ファイル内の並び順が違っていても差分になりません。
5環境を横に並べた差分表を作る
2つずつ比べる diff に対し、全環境を一度に見たいときは小さなスクリプトで表にします。キー名に PASSWORD・SECRET・TOKEN・KEY を含む値は伏せ字にします。
"""環境変数ファイルを横に並べ、差分表(Markdown)と確認事項を出す。"""
import sys
from pathlib import Path
SECRET_WORDS = ("PASSWORD", "SECRET", "TOKEN", "KEY")
def load(path: str) -> dict[str, str]:
env = {}
for line in Path(path).read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
key, _, value = line.partition("=")
env[key.strip()] = value.strip()
return env
def mask(key: str, value: str) -> str:
return "***" if any(w in key for w in SECRET_WORDS) else value
files = sys.argv[1:]
envs = {f: load(f) for f in files}
names = [f.split(".env.")[-1] for f in files]
keys = sorted(set().union(*envs.values()))
print("| キー | " + " | ".join(names) + " | 差分 |")
print("|---|" + "---|" * (len(names) + 1))
warnings = []
for k in keys:
row = [mask(k, envs[f][k]) if k in envs[f] else "(なし)" for f in files]
kinds = {envs[f].get(k) for f in files}
print(f"| {k} | " + " | ".join(row) + f" | {'同じ' if len(kinds) == 1 else '環境差分'} |")
missing = [n for n, f in zip(names, files) if k not in envs[f]]
if missing:
warnings.append(f"{k}: {', '.join(missing)} に定義がありません")
print()
for w in warnings:
print("確認:", w)
sys.exit(1 if warnings else 0)
python3 envdiff.py .env.local .env.dev .env.test .env.stg .env.prod
実行結果です(Markdown の表として出力されます)。
| キー | local | dev | test | stg | prod | 差分 |
|---|---|---|---|---|---|---|
| APP_ENV | local | dev | test | stg | prod | 環境差分 |
| APP_URL | http://localhost:8080 | https://dev.reserve.example.com | https://test.reserve.example.com | https://stg.reserve.example.com | https://reserve.example.com | 環境差分 |
| BATCH_ENABLED | false | false | true | true | true | 環境差分 |
| DB_HOST | localhost | db-dev.internal | db-test.internal | db-stg.internal | db-prod.internal | 環境差分 |
| DB_NAME | reserve_local | reserve_dev | reserve_test | reserve_stg | reserve_prod | 環境差分 |
| DB_PASSWORD | *** | *** | *** | *** | *** | 環境差分 |
| DB_POOL_SIZE | 5 | 5 | 10 | 20 | 20 | 環境差分 |
| DEBUG | true | true | false | true | false | 環境差分 |
| EXT_CALENDAR_API | http://localhost:9000/mock | https://mock.reserve.example.com/calendar | https://sandbox.calendar.example.net/v1 | https://sandbox.calendar.example.net/v1 | https://api.calendar.example.net/v1 | 環境差分 |
| LOG_LEVEL | DEBUG | DEBUG | INFO | INFO | WARN | 環境差分 |
| MAIL_ALLOWLIST | (なし) | (なし) | (なし) | @example.com | (なし) | 環境差分 |
| MAIL_HOST | localhost | mail-sandbox.internal | mail-sandbox.internal | smtp.internal | smtp.internal | 環境差分 |
| MAIL_MODE | file | sandbox | sandbox | allowlist | live | 環境差分 |
| SESSION_TIMEOUT_MIN | (なし) | (なし) | (なし) | (なし) | 30 | 環境差分 |
確認: MAIL_ALLOWLIST: local, dev, test, prod に定義がありません
確認: SESSION_TIMEOUT_MIN: local, dev, test, stg に定義がありません
環境定義書と設定ファイルを突き合わせる
最後に、定義書のタブ区切りファイル(env_definition.tsv)を正として、各環境のファイルを検査します。
"""環境定義書(タブ区切り)と、各環境の環境変数ファイルを突き合わせる。"""
import csv
import re
import sys
COLUMNS = {"ローカル": ".env.local", "開発": ".env.dev", "検証": ".env.test",
"ステージング": ".env.stg", "本番": ".env.prod"}
def load(path: str) -> dict[str, str]:
env = {}
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#"):
key, _, value = line.partition("=")
env[key] = value
return env
actual = {col: load(path) for col, path in COLUMNS.items()}
count = 0
with open("env_definition.tsv", encoding="utf-8", newline="") as f:
for row in csv.DictReader(f, delimiter="\t"):
key = row["キー"]
if not re.fullmatch(r"[A-Z0-9_]+", key):
continue # 設定ファイル外の項目は対象外
for col, path in COLUMNS.items():
expected = row[col]
if expected.startswith("("): # 秘密情報などは値を比べない
continue
got = actual[col].get(key)
if expected == "―":
if got is not None:
print(f"{path}: {key} は定義書では「なし」ですが、{got} が設定されています")
count += 1
elif got is None:
print(f"{path}: {key} がありません(定義書の値: {expected})")
count += 1
elif got != expected:
print(f"{path}: {key} が定義書と違います(定義書: {expected} / ファイル: {got})")
count += 1
print(f"指摘 {count} 件")
sys.exit(1 if count else 0)
実行結果です。
.env.stg: DEBUG が定義書と違います(定義書: false / ファイル: true)
.env.local: SESSION_TIMEOUT_MIN がありません(定義書の値: 480)
.env.dev: SESSION_TIMEOUT_MIN がありません(定義書の値: 480)
.env.test: SESSION_TIMEOUT_MIN がありません(定義書の値: 30)
.env.stg: SESSION_TIMEOUT_MIN がありません(定義書の値: 30)
指摘 5 件
定義書では DEBUG はステージングで false、SESSION_TIMEOUT_MIN は全環境に値がある、と決めていました。実際のファイルは5か所で定義書と違っていたことになります。定義書を表計算ソフトだけで持たず、タブ区切りでも保存しておくと、このように検査の入力に使えます。
見た目が同じなのに差分が出るとき
改行コードや行末の空白が違うと、同じに見える行が差分として出ます。
printf 'APP_ENV=prod\r\nLOG_LEVEL=WARN \n' > a.env
printf 'APP_ENV=prod\nLOG_LEVEL=WARN\n' > b.env
diff a.env b.env
echo "終了コード: $?"
diff <(cat -A a.env) <(cat -A b.env)
diff --strip-trailing-cr -Z a.env b.env
echo "終了コード: $?"
実行結果です。
1,2c1,2
< APP_ENV=prod
< LOG_LEVEL=WARN
---
> APP_ENV=prod
> LOG_LEVEL=WARN
終了コード: 1
1,2c1,2
< APP_ENV=prod^M$
< LOG_LEVEL=WARN $
---
> APP_ENV=prod$
> LOG_LEVEL=WARN$
終了コード: 0
cat -A を通すと、行末の ^M(Windows の改行コードに含まれる CR)と、行末の空白($ の手前の空白)が見えます。--strip-trailing-cr は行末の CR を、-Z は行末の空白を無視するオプションで、両方を付けると差分なし(終了コード 0)になりました。行末の空白は、読み込み方によっては値の一部として扱われるので、無視して済ませるよりファイル側を直しておくと安心です。
環境差分を小さく保つチェックリスト
| 番号 | 確認項目 | タイミング |
|---|---|---|
| 1 | 環境一覧に、すべての環境と管理者が載っている | 環境を作る・やめるとき |
| 2 | 設定項目を追加したら、全環境の列に値を書いた | 設定項目を追加するとき |
| 3 | 差分のある行には理由が書いてある | 定義書のレビュー |
| 4 | ステージングと本番で、接続先以外の差分が無い(あれば理由がある) | リリース前 |
| 5 | 定義書と設定ファイルを突き合わせ、指摘が 0 件 | リリース前、自動チェック |
| 6 | 本番以外の環境から、利用者へメール・通知が届かない | 環境を作るとき、データを入れ替えるとき |
| 7 | 本番以外の環境が、連携先の本番につながっていない | 環境を作るとき |
| 8 | 本番データを使う環境で、個人情報がマスクされている | データを入れ替えるとき |
| 9 | 秘密情報の値が、定義書・リポジトリ・チャットに書かれていない | 随時 |
| 10 | ミドルウェアや実行環境の版が、ステージングと本番で揃っている | 版を上げるとき |
確かめて分かったこと
- diff -u の出力には、意図した差分と意図しない差分が同じ見た目で並びます。13行のファイルでも、接続先の違いの中に DEBUG=true が紛れていました。定義書を正にして突き合わせる方法だと、意図しない5か所だけが出ました。
- diff -u は値をそのまま表示するので、パスワードも出力に含まれます。キーだけを比べる方法や、伏せ字にするスクリプトと使い分けます。
- キーの比較は sort を通すと、並び順の違いに左右されません。sort なしで比べると、順番の入れ替えだけで差分が出ます。
- diff の終了コードは、差分あり 1、差分なし 0 でした。スクリプトから呼ぶときは、1 を「エラー」ではなく「差分あり」として扱います。
- 行末の CR と空白は画面では見えず、cat -A で初めて見えました。設定ファイルを Windows と Linux の両方で編集する場合に起こります。
- 「ある環境にしか無いキー」は、2環境の diff では片側の比較しかできません。5環境を横に並べると、SESSION_TIMEOUT_MIN が本番にしか無いことが一目で分かりました。
よくある質問
Q. 開発環境と本番環境の違いは何ですか? A. 開発環境は作って試す場所、本番環境は利用者が実際に使う場所です。開発環境は自由に変更でき、データも作り物ですが、本番環境は承認された版だけを載せ、本番データを扱います。
Q. 検証環境とステージング環境の違いは何ですか? A. 検証環境は機能が仕様どおりかをテストする場所、ステージング環境は本番と同じ構成と手順でリリース前の最終確認をする場所です。検証環境は最小構成でよく、ステージング環境は本番との差を小さく保つことに意味があります。
Q. ステージング環境は必ず要りますか? A. 必須ではなく、規模と確認したい内容で決めます。利用部門の受け入れ、性能テスト、リリース手順の予行のどれかがあるなら用意する価値があります。小さなツールなら、検証環境が兼ねる3環境から始められます。
Q. 環境定義書とは何ですか? A. 環境ごとの構成と設定値の違いをまとめた設計書です。環境一覧(どんな環境があるか)と、設定項目を行・環境を列にした差分表の2つを中心に作ります。
Q. 環境ごとに設定が違って動かないときは、どこから確かめますか? A. 動く環境と動かない環境の設定ファイルを diff で比べ、キーの過不足と値の違いを見ます。見た目が同じなのに差分が出る場合は、cat -A で改行コードと行末の空白を確かめます。
まとめ
- 開発環境は作る場所、検証環境はテストする場所、本番環境は利用者が使う場所です。ローカルとステージングを加えた5環境で整理します。
- 違いは目的・利用者・データ・構成・デプロイ契機・外部連携先で比べます。
- 検証環境は機能の確認、ステージング環境は本番と同じ形での最終確認と役割を分けます。
- 環境の数は規模と要員で決め、3環境から始めて必要に応じて増やします。
- 環境一覧と、環境差分の設定値一覧を作り、差分には理由を書きます。
- 定義書と設定ファイルは diff とスクリプトで突き合わせ、リリース前に指摘 0 件を確かめます。
参考・出典
- The Twelve-Factor App「III. 設定」(https://12factor.net/ja/config)2026-10-05 に内容を確認
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「開発環境・検証環境・本番環境の違い|環境定義書テンプレート」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。