サイトアイコンmaita tomoya dev io

#60 .envファイル完全ガイド:環境変数管理のベストプラクティスとセキュリティ

開発中に「APIキーはどこに保存すればいい?」「本番環境と開発環境で設定を分けたい」と悩んだことはありませんか?.envファイルは、これらの問題を解決する重要な仕組みです。本記事では、.envファイルの基本から、セキュリティ上の注意点、よくある失敗例まで詳しく解説します。

📚 目次

.envファイルとは?

.envファイルは、環境変数を定義するためのテキストファイルです。プロジェクトの設定値や秘密情報を安全に管理するために使用されます。

基本的な構造

# .envファイルの例
DATABASE_URL=postgresql://localhost:5432/myapp
API_KEY=sk-1234567890abcdef
NODE_ENV=development
PORT=3000

特徴

  • シンプルな形式: KEY=VALUEの形式で記述
  • コメント対応: #で始まる行はコメント
  • 改行区切り: 1行に1つの環境変数
  • クォート不要: 通常は値をクォートで囲む必要なし(スペースを含む場合は必要)

なぜ.envファイルが必要なのか

1. 秘密情報の保護

// ❌ 悪い例:コードに直接記述
const apiKey = "sk-1234567890abcdef";  // GitHubに公開される!

// ✅ 良い例:環境変数から取得
const apiKey = process.env.API_KEY;  // .envから読み込み

2. 環境ごとの設定切り替え

# .env.development
DATABASE_URL=postgresql://localhost:5432/dev_db
API_ENDPOINT=http://localhost:3000

# .env.production
DATABASE_URL=postgresql://prod-server:5432/prod_db
API_ENDPOINT=https://api.example.com

3. チーム開発での設定共有

# .env.example(GitHubにコミット)
DATABASE_URL=your_database_url_here
API_KEY=your_api_key_here
PORT=3000

# .env(各開発者がローカルで作成)
DATABASE_URL=postgresql://localhost:5432/myapp
API_KEY=sk-actual-key-123456
PORT=3000

基本的な使い方

1. Node.jsでの使用(dotenvパッケージ)

# インストール
npm install dotenv
// index.js の最初に記述
require('dotenv').config();

// または ES6
import dotenv from 'dotenv';
dotenv.config();

// 環境変数の使用
console.log(process.env.DATABASE_URL);
console.log(process.env.API_KEY);

2. 複数の.envファイルの管理

// 環境に応じて読み込むファイルを変更
const dotenv = require('dotenv');

if (process.env.NODE_ENV === 'production') {
  dotenv.config({ path: '.env.production' });
} else {
  dotenv.config({ path: '.env.development' });
}

3. デフォルト値の設定

// 環境変数が未定義の場合のデフォルト値
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL || 'postgresql://localhost:5432/default';

グローバル設定とプロジェクト設定

ファイルの配置と優先順位

~/.env                       # グローバル設定(すべてのプロジェクト)
~/project1/.env             # プロジェクト1専用
~/project1/src/.env         # サブディレクトリ(通常は使わない)

読み込み優先順位

多くのツールは以下の順序で.envを探します:

  1. カレントディレクトリ.env
  2. 親ディレクトリ を順に探索
  3. ホームディレクトリ.env

グローバル vs プロジェクト設定の使い分け

# ~/.env(グローバル - 個人の認証情報)
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
GEMINI_API_KEY=AIxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxx
NPM_TOKEN=npm_xxxxxxxxxxxx

# project/.env(プロジェクト固有 - アプリ設定)
DATABASE_URL=postgresql://localhost:5432/myapp
REDIS_URL=redis://localhost:6379
API_BASE_URL=http://localhost:3000
FEATURE_FLAG_NEW_UI=true

セキュリティ:絶対に守るべきルール

🚨 Rule 1: .envファイルは絶対にGitにコミットしない

# .gitignore に必ず追加
.env
.env.*
!.env.example  # exampleファイルは除外

🚨 Rule 2: .env.exampleを用意する

# .env.example(これはGitにコミットOK)
# データベース接続
DATABASE_URL=postgresql://username:password@localhost:5432/dbname

# 外部API
STRIPE_API_KEY=sk_test_xxxxxxxxxxxxx
SENDGRID_API_KEY=SG.xxxxxxxxxxxxx

# アプリケーション設定
PORT=3000
NODE_ENV=development

🚨 Rule 3: 本番環境では.envファイルを使わない

# 本番環境では環境変数を直接設定
# Heroku
heroku config:set API_KEY=production_key

# AWS ECS
aws ecs update-service --environment API_KEY=production_key

# Docker
docker run -e API_KEY=production_key myapp

🚨 Rule 4: APIキーの権限は最小限に

// APIキーごとに権限を分ける
STRIPE_PUBLISHABLE_KEY=pk_test_xxx  // フロントエンド用(公開可能)
STRIPE_SECRET_KEY=sk_test_xxx       // バックエンド用(秘密)

// 環境ごとにキーを分ける
DEV_API_KEY=dev_xxx    // 開発環境(制限付き)
PROD_API_KEY=prod_xxx  // 本番環境(必要最小限の権限)

よくある失敗と対処法

❌ ケース1: .envをGitHubに公開してしまった

# 誤ってコミットしてしまった場合の対処法

# 1. まず.gitignoreに追加
echo ".env" >> .gitignore

# 2. キャッシュから削除
git rm --cached .env

# 3. コミット
git commit -m "Remove .env from tracking"

# 4. 【重要】履歴から完全に削除
# BFG Repo-Cleanerを使用
bfg --delete-files .env
git push --force

# 5. 【最重要】流出したAPIキーは即座に無効化・再発行!

❌ ケース2: 環境変数が読み込まれない

// よくある原因と解決法

// 1. dotenvの読み込み位置が間違っている
// ❌ 悪い例
const apiKey = process.env.API_KEY;  // undefined
require('dotenv').config();

// ✅ 良い例
require('dotenv').config();  // 最初に読み込む
const apiKey = process.env.API_KEY;

// 2. ファイルパスが間違っている
// デバッグ方法
const result = dotenv.config();
if (result.error) {
  console.error('Error loading .env file:', result.error);
}

❌ ケース3: スペースや特殊文字の扱い

# .envファイルでの注意点

# ❌ 悪い例
API_KEY = sk-123456  # スペースが含まれる
DATABASE_URL=postgresql://user:pass word@localhost  # パスワードにスペース

# ✅ 良い例
API_KEY=sk-123456  # スペースなし
DATABASE_URL="postgresql://user:pass word@localhost"  # クォートで囲む

# 特殊文字を含む場合
PASSWORD="p@$$w0rd!"  # 特殊文字はクォートで囲む
MULTILINE="line1\nline2\nline3"  # 改行も可能

❌ ケース4: 変数の上書き

// .envと環境変数の優先順位

// dotenvのデフォルト動作:既存の環境変数を上書きしない
process.env.API_KEY = 'original';
require('dotenv').config();  // .envのAPI_KEYは無視される

// 強制的に上書きする場合
require('dotenv').config({ override: true });

フレームワーク別の使い方

Next.js

# Next.jsは自動的に.envを読み込む
.env                # すべての環境
.env.local          # ローカル設定(gitignore推奨)
.env.development    # 開発環境
.env.production     # 本番環境
// Next.jsでの使用
// サーバーサイド
console.log(process.env.SECRET_KEY);

// クライアントサイド(NEXT_PUBLIC_プレフィックス必須)
console.log(process.env.NEXT_PUBLIC_API_URL);

Create React App

# CRAも自動的に.envを読み込む
# REACT_APP_プレフィックスが必須
REACT_APP_API_URL=http://localhost:3000
REACT_APP_VERSION=$npm_package_version

Docker

# Dockerfile
FROM node:18
WORKDIR /app
COPY . .

# ビルド時の環境変数
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

# 実行時の環境変数(docker-compose.yml)
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    env_file:
      - .env  # .envファイルから読み込み
    environment:
      - NODE_ENV=production  # 直接指定も可能

CLI ツール(Gemini CLIの例)

# ~/.env(ホームディレクトリ)
GEMINI_API_KEY=AIxxxxxxxxxxxxx

# CLIツールは通常、以下の優先順位で読み込む
# 1. 環境変数(export GEMINI_API_KEY=xxx)
# 2. カレントディレクトリの.env
# 3. ホームディレクトリの.env

ベストプラクティス

1. 命名規則

# 大文字とアンダースコア
DATABASE_URL=xxx        # ✅ 推奨
database-url=xxx        # ❌ 避ける

# プレフィックスでグループ化
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp

AWS_ACCESS_KEY_ID=xxx
AWS_SECRET_ACCESS_KEY=xxx
AWS_REGION=ap-northeast-1

2. 値の検証

// 必須環境変数のチェック
const requiredEnvVars = [
  'DATABASE_URL',
  'API_KEY',
  'JWT_SECRET'
];

requiredEnvVars.forEach(varName => {
  if (!process.env[varName]) {
    console.error(`Error: ${varName} is not set`);
    process.exit(1);
  }
});

3. 型安全な環境変数

// TypeScriptでの型定義
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production' | 'test';
      DATABASE_URL: string;
      API_KEY: string;
      PORT?: string;  // オプショナル
    }
  }
}

// zodを使った検証
import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']),
  DATABASE_URL: z.string().url(),
  PORT: z.string().regex(/^\d+$/).transform(Number).default('3000'),
});

const env = envSchema.parse(process.env);

4. 環境変数のドキュメント化

# 環境変数一覧(README.md)

## 必須環境変数

| 変数名 | 説明 | 例 | デフォルト値 |
|--------|------|-----|------------|
| DATABASE_URL | PostgreSQL接続URL | postgresql://localhost:5432/myapp | なし |
| JWT_SECRET | JWT署名用の秘密鍵 | random-string-here | なし |

## オプション環境変数

| 変数名 | 説明 | 例 | デフォルト値 |
|--------|------|-----|------------|
| PORT | サーバーポート | 3000 | 3000 |
| LOG_LEVEL | ログレベル | debug | info |

トラブルシューティング

Q: .envファイルが読み込まれない

// デバッグ方法
const path = require('path');
const dotenv = require('dotenv');

// 明示的にパスを指定
const result = dotenv.config({
  path: path.resolve(__dirname, '.env')
});

console.log('Loaded:', result.parsed);
console.log('Error:', result.error);

Q: Windowsで環境変数が設定できない

# cross-envを使用
npm install --save-dev cross-env

# package.json
{
  "scripts": {
    "dev": "cross-env NODE_ENV=development node app.js"
  }
}

Q: Dockerコンテナで.envが見つからない

# .dockerignoreに.envが含まれていないか確認
# .dockerignore
node_modules
.git
# .env  ← コメントアウトまたは削除

# またはCOPY命令で明示的にコピー
COPY .env .env

セキュリティチェックリスト

開発を始める前に、以下の項目を確認してください:

  • .gitignore.envが含まれている
  • .env.exampleを作成した
  • APIキーは最小権限になっている
  • 本番環境の秘密情報は別管理
  • チームメンバーに.envの扱い方を共有した
  • 定期的にAPIキーをローテーションする計画がある
  • 流出時の対応手順を決めている

まとめ

.envファイルは、現代の開発において欠かせない環境変数管理ツールです。正しく使えば開発効率が大幅に向上しますが、誤った使い方をするとセキュリティリスクになります。

覚えておくべき3つのポイント

  1. 絶対にGitにコミットしない - .gitignoreに必ず追加
  2. 環境ごとに分ける - 開発/本番で異なる設定を使用
  3. 最小権限の原則 - APIキーは必要最小限の権限のみ

これらの基本を守ることで、安全で効率的な開発環境を構築できます。

参考リンク