ステージング環境の作り方 — 本番事故を防ぐdev/staging/prod分離

VPS・サーバー選定中級
ステージング環境dev/staging/prod環境分離VPSデプロイ

本記事はアフィリエイト広告(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 を使うと、stagingproduction でそれぞれ別の 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 という「本番より権限管理が緩い環境」に入るからだ。

マスキングの典型的な手順は次の通りだ。

  1. 本番 DB のダンプを取得する
  2. スクリプトでマスキングを適用する(後述)
  3. マスキング済みダンプを 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-anonymizerFaker ライブラリを使う方法もある。


小規模での現実解(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 サービス。

ConoHa VPS

Xserver VPS

関連記事

VPS・サーバー選定PostgreSQLパフォーマンスチューニング: 中小企業DBの遅延を解消する手順VPS・サーバー選定高可用性(HA)構成の最小コスト実装:2台のVPSでフェイルオーバーを組むVPS・サーバー選定ConoHa VPS vs さくらのVPS 2026年版:法人ユースで選ぶならどっちか
記事一覧に戻る