開発環境は作る場所、検証環境はテストする場所、本番環境は利用者が使う場所です。同じプログラムを、目的の違う複数の場所で動かすために環境を分けます。その間に、本番と同じ構成でリリース前の最終確認をするステージング環境と、開発者の手元のローカル環境が加わり、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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?