ステージング環境の作り方 — 本番事故を防ぐdev/staging/prod分離
本記事はアフィリエイト広告(A8.net)を含みます。価格・仕様は2026年6月時点の目安です。最新は各公式サイトでご確認ください。
TL;DR
- 本番環境で直接作業すると、確認のつもりだった変更が即座にユーザーに届く。切り戻す手段がなければその損害はすべて本番に残る。
- 3環境(開発/ステージング/本番)それぞれに役割がある。ステージングは「本番の写し」として機能させるのが原則だ。
- 環境ごとの設定は
.envファイルや CI/CD の Secrets で分離し、コードに直書きしない。 - 本番データを開発・ステージングで使う必要がある場合は、個人情報をマスキングしてから持ち込む。
- 小規模なら1台の VPS をサブドメインで分けるだけで十分に機能する構成が作れる。
なぜ本番で直接いじると危険か
「少し確認したいだけ」「1行直すだけ」という理由で本番サーバーに入って作業することがある。この習慣が事故の温床になる。
| 状況 | 何が起きるか |
|---|---|
| 設定ファイルを直接編集する | typo 1つでサービスが落ちる。Nginx の設定ミスは即ダウンタイム |
| バージョンアップを本番で試す | 依存ライブラリの競合が発覚しても戻す手段が限られる |
| 新機能を動作確認しながら入れる | ユーザーが動作確認中の不完全な状態を見る |
| 「ちょっと確認」でDBを書き換える | UPDATE に WHERE を忘れると全行が上書きされる |
問題の本質は「試す場所」と「本物が動く場所」が同じであることだ。試行錯誤には失敗がつきもので、その失敗をユーザーに見せる必要はない。それを分離するのが環境分離の目的だ。
3環境の役割
一般的なウェブシステムは次の3環境で構成される。
| 環境 | 通称 | 役割 |
|---|---|---|
| 開発環境 | dev / local | 開発者が手元で試す。動けばいい段階 |
| ステージング環境 | staging | 本番リリース前の最終確認。本番に限りなく近い状態で動かす |
| 本番環境 | prod / production | 実際のユーザーが使う。変更は必ず staging を経由してから入れる |
この3環境のうち、最も重要度が高いにもかかわらず省略されやすいのがステージングだ。「開発で動いたから本番へ」という流れは、問題を本番で発見するまで気づけない構造になっている。
ステージングは「本番の写し」として作る
ステージングが機能するための条件は、本番との差異が最小であることだ。差異が大きいほど「staging では動いたのに本番で壊れた」という事態が起きる。
近づけるべき要素は次の3点だ。
1. アプリケーションの設定
DB の接続先、外部 API のエンドポイント、ファイルパス。これらが staging と prod で異なると、staging でのテストが意味を持たなくなる。設定は環境変数で外出しし、実行環境ごとに値を差し替える構造にする(後述)。
2. データの形
本番と同じスキーマ・同じデータ量規模のデータを staging に置く。データ形式が違うと、本番では発生する処理がテストできない。個人情報は必ずマスキングしてから持ち込む(後述)。
3. インフラの構成
本番が Nginx + Gunicorn + PostgreSQL なら、staging も同じ構成にする。開発環境に使う SQLite を staging でも使い続けると、PostgreSQL 特有の挙動(トランザクション・JSON 型・文字コード)で差が出る。
環境ごとの設定分離(環境変数・シークレット)
設定を環境変数で管理する方法は /articles/env-secrets-management/ で詳しく扱っている。ここではステージング環境の観点から整理する。
.env ファイルで切り替える
Node.js や Python の多くのフレームワークは .env ファイルを自動的に読み込む。環境ごとに .env.staging、.env.production を用意し、デプロイ時に対象の .env を読ませる構成が一般的だ。
# .env.staging
APP_ENV=staging
DATABASE_URL=postgresql://user:pass@staging-db:5432/myapp_staging
EXTERNAL_API_URL=https://sandbox.example.com/api
LOG_LEVEL=debug
# .env.production
APP_ENV=production
DATABASE_URL=postgresql://user:pass@prod-db:5432/myapp_prod
EXTERNAL_API_URL=https://api.example.com/api
LOG_LEVEL=warn
注意点は2つある。.env ファイルに本物のパスワードやトークンが入る場合は .gitignore に追加してリポジトリにコミットしない。もう一点は、.env.staging のような環境名入りファイルも同様だ。テンプレートとして .env.example(値は空欄またはダミー)だけをリポジトリに置く運用が標準的だ。
CI/CD の Secrets で管理する
GitHub Actions を使ったデプロイでは、DB パスワードや API キーなどの機密値は Secrets に格納し、ワークフロー内で参照する。詳細は /articles/github-actions-deploy-automation/ で扱っているが、環境ごとの Secrets 管理については GitHub の「Environments」機能が使える。
GitHub の Environments を使うと、staging と production でそれぞれ別の Secrets を持たせられる。
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging # staging 環境の Secrets を使う
steps:
- name: ステージングへデプロイ
run: |
# ${{ secrets.DATABASE_URL }} は staging 環境の値が入る
echo "Deploying to staging"
deploy-production:
runs-on: ubuntu-latest
environment: production # production 環境の Secrets を使う
needs: deploy-staging # staging デプロイ完了後に実行
steps:
- name: 本番へデプロイ
run: |
echo "Deploying to production"
needs: deploy-staging で、staging へのデプロイが成功してから production へ進む順序を強制できる。
デプロイの流れ(staging検証→prod昇格)
環境分離が機能するためには、変更が必ず staging を経由してから本番に入るフローを守ることだ。
開発者のローカル(dev)
↓ push to staging branch
GitHub Actions が staging へデプロイ
↓ 動作確認・QA
問題なければ main にマージ or タグ付け
↓ GitHub Actions が本番へデプロイ
prod
このフローで重要なのは「staging を通らずに prod へ直接デプロイする経路をなくす」ことだ。GitHub Actions の Environments に required_reviewers を設定すると、production への自動デプロイを承認制にできる。小規模でも、1人が self-approve する形で「意識的に本番へ上げる判断をした」というステップを挟む価値がある。
staging で確認すべき項目
| 確認項目 | なぜ |
|---|---|
| アプリが正常に起動するか | 起動時にエラーが出る変更を本番へ持ち込まない |
| 主要な機能が動くか | DB 接続・外部 API 呼び出し・ファイル操作 |
| マイグレーションが通るか | スキーマ変更は staging の DB に先に流す |
| エラーログが増えていないか | 画面上は動いていてもログに例外が出ている場合がある |
| パフォーマンスが極端に落ちていないか | N+1 クエリの混入を staging の規模感で検知する |
本番データの扱い(個人情報のマスキング)
staging に本番と同じ形のデータを置くことは前述した通りだが、本番 DB のダンプをそのまま staging に流し込む方法は取れない。本番の個人情報が staging という「本番より権限管理が緩い環境」に入るからだ。
マスキングの典型的な手順は次の通りだ。
- 本番 DB のダンプを取得する
- スクリプトでマスキングを適用する(後述)
- マスキング済みダンプを staging に流し込む
マスキングスクリプトの例(PostgreSQL + Python)。
import psycopg2
import random
import string
def random_email():
name = ''.join(random.choices(string.ascii_lowercase, k=8))
return f"{name}@example.com"
def mask_users(conn):
cur = conn.cursor()
cur.execute("SELECT id FROM users")
ids = [row[0] for row in cur.fetchall()]
for uid in ids:
cur.execute(
"UPDATE users SET email = %s, name = %s WHERE id = %s",
(random_email(), f"User_{uid}", uid)
)
conn.commit()
cur.close()
マスキング対象は個人情報保護法の「個人情報」に該当するものすべてだ。氏名・メールアドレス・住所・電話番号・生年月日が主な対象になる。外部のマスキングツールとしては postgresql-anonymizer や Faker ライブラリを使う方法もある。
小規模での現実解(1VPSで分ける・サブドメインで分ける)
「3環境を完全に分けるのはサーバー費用がかさむ」という懸念は正当だ。小規模サービスや個人開発では、コストと運用負荷を現実的なラインに抑える構成を取る。
1台の VPS をポートとサブドメインで分ける
最もシンプルな現実解は、1台の VPS 上でアプリを2つ(または3つ)動かし、サブドメインで振り分ける構成だ。
VPS 1台
├── staging.example.com → app(ポート8001)
│ ↑ Nginx がリバースプロキシで振り分け
└── example.com → app(ポート8000)
Nginx の設定例。
# /etc/nginx/conf.d/staging.conf
server {
listen 80;
server_name staging.example.com;
location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# /etc/nginx/conf.d/prod.conf
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
アプリのプロセスは systemd の unit ファイルをポート違いで2本用意する方法か、PM2(Node.js)の場合は ecosystem.config.js でポートを分ける方法が使いやすい。
DB については、コスト最小化を優先するなら同一 PostgreSQL インスタンス内でデータベース名を分ける(myapp_staging / myapp_prod)方法でも機能する。ただし、staging の DB 操作ミスが prod DB に波及しないよう、ユーザー権限は分けておく。
-- staging 専用ユーザーは staging DB のみアクセス可
CREATE USER staging_user WITH PASSWORD 'password';
GRANT ALL PRIVILEGES ON DATABASE myapp_staging TO staging_user;
-- prod DB へのアクセス権は付与しない
staging のアクセス制限
staging は開発中のコードが動く環境なので、外部から誰でもアクセスできる状態は避ける。Nginx の auth_basic で Basic 認証を設定するか、特定 IP からのみアクセスを許可するのが最低限の対策だ。
server {
listen 80;
server_name staging.example.com;
# Basic 認証
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://127.0.0.1:8001;
}
}
.htpasswd ファイルは htpasswd -c /etc/nginx/.htpasswd username で作成する。
ConoHa VPS や Xserver VPS であれば、コントロールパネルでセキュリティグループ(ファイアウォール)を設定し、staging ポートへのアクセスを社内 IP や自宅 IP に限定する方法も取れる。VPS の選定基準については 個人開発者のVPS選び方 で別途扱っている。
まとめ
| 対応項目 | やること | 効果 |
|---|---|---|
| 本番作業の禁止 | staging を必ず経由するルールを作る | 本番事故の発生経路を絞る |
| 環境変数で設定分離 | .env.staging / .env.production を分ける | 設定起因の環境差異をなくす |
| Secrets 管理 | GitHub Environments で環境ごとに分離 | 誤った環境への認証情報流入を防ぐ |
| データマスキング | 個人情報を書き換えてから staging へ | 情報漏洩リスクを構造的に排除 |
| staging 検証フロー | 起動確認・機能確認・マイグレーション確認 | 本番投入前に問題を検知する |
| 小規模構成 | 1VPS + サブドメイン + ポート分離 | コスト最小で3環境の考え方を実現 |
| staging アクセス制限 | Basic 認証 or IP 制限 | 開発中コードの意図せぬ公開を防ぐ |
環境分離は「完璧な構成を一度に整える」ものではない。まず staging サブドメインを1つ立て、本番と同じ設定ファイルを使って動かしてみることから始めると、どこに差異があるかが見えてくる。その差異を一つずつ埋めていく作業が、実質的な環境分離の整備になる。
各サービス公式
ステージング環境の構築に使える VPS サービス。