環境変数・シークレット管理 - 設定方法からシークレット管理ツールまで

環境変数の基本から、シークレット管理ツールの比較、CI/CDでの扱い、セキュリティのベストプラクティスまでをまとめたリファレンス。

環境変数の基本

環境変数は、OS上のプロセスに設定情報を渡す仕組み。アプリケーションの設定(DB接続先、APIキー、動作モードなど)をコードから分離するために使用する。

OS別の設定方法

Linux / macOS

# 一時的に設定(現在のシェルセッションのみ)
export DATABASE_URL="postgres://localhost:5432/mydb"
export API_KEY="sk-xxxxxxxxxxxx"

# 確認
echo $DATABASE_URL
printenv DATABASE_URL

# 一覧表示
env
printenv

# コマンド実行時のみ有効にする
DATABASE_URL="postgres://localhost/test" node app.js

# 削除
unset DATABASE_URL

永続化する場合はシェルの設定ファイルに記述する。

シェル設定ファイル
bash~/.bashrc または ~/.bash_profile
zsh~/.zshrc
fish~/.config/fish/config.fish
# ~/.bashrc に追加
export NODE_ENV="production"
export PATH="$HOME/.local/bin:$PATH"

Windows

# PowerShell - 一時的(現在のセッションのみ)
$env:DATABASE_URL = "postgres://localhost:5432/mydb"

# PowerShell - 永続化(ユーザー環境変数)
[Environment]::SetEnvironmentVariable("DATABASE_URL", "postgres://localhost:5432/mydb", "User")

# PowerShell - 永続化(システム環境変数、管理者権限が必要)
[Environment]::SetEnvironmentVariable("DATABASE_URL", "postgres://localhost:5432/mydb", "Machine")

# 確認
$env:DATABASE_URL
Get-ChildItem Env:
rem コマンドプロンプト
set DATABASE_URL=postgres://localhost:5432/mydb
setx DATABASE_URL "postgres://localhost:5432/mydb"

プログラム言語からのアクセス

// Node.js
const dbUrl = process.env.DATABASE_URL
const port = parseInt(process.env.PORT || '3000', 10)
# Python
import os
db_url = os.environ.get("DATABASE_URL")
port = int(os.environ.get("PORT", "3000"))
// Go
dbURL := os.Getenv("DATABASE_URL")
port := os.Getenv("PORT")
if port == "" {
    port = "3000"
}
// Java
String dbUrl = System.getenv("DATABASE_URL");

.envファイルとdotenvパターン

.envファイルの形式

.env ファイルにキーバリュー形式で環境変数を記述し、アプリケーション起動時に読み込むパターン。

# .env
NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://user:password@localhost:5432/mydb
API_KEY=sk-xxxxxxxxxxxx

# コメント行
# 値にスペースを含む場合はクォートで囲む
APP_NAME="My Application"

# 変数の参照(dotenvライブラリによってはサポート)
BASE_URL=http://localhost:${PORT}

dotenvライブラリ

言語ライブラリインストール
Node.jsdotenvnpm install dotenv
Pythonpython-dotenvpip install python-dotenv
Rubydotenvgem install dotenv
Gogodotenvgo get github.com/joho/godotenv
PHPvlucas/phpdotenvcomposer require vlucas/phpdotenv
// Node.js - アプリケーションのエントリポイントで読み込む
import 'dotenv/config'
// または
import dotenv from 'dotenv'
dotenv.config()

console.log(process.env.DATABASE_URL)
# Python
from dotenv import load_dotenv
import os

load_dotenv()  # .envファイルを読み込む
db_url = os.environ.get("DATABASE_URL")

環境ごとの.envファイル

.env                # デフォルト値(全環境共通)
.env.local          # ローカル環境用(.gitignoreに含める)
.env.development    # 開発環境用
.env.staging        # ステージング環境用
.env.production     # 本番環境用
.env.test           # テスト環境用

.gitignoreでの除外(必須)

シークレットを含む.envファイルは必ずGitの管理対象から除外する。 一度コミットしてしまうとGit履歴に残り、git rmだけでは完全に削除できない。

# .gitignore

# 環境変数ファイル
.env
.env.local
.env.*.local

# テンプレートはコミットしてよい
!.env.example
!.env.template

.env.example として、実際の値を含まないテンプレートをリポジトリに含めるのが一般的な慣習。

# .env.example(チームメンバーへのガイド用)
NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://user:password@localhost:5432/dbname
API_KEY=your-api-key-here

フロントエンドフレームワークでの環境変数

フロントエンドではビルド時に環境変数が静的にバンドルされる。公開されるJavaScriptに含まれるため、シークレットは絶対に含めてはいけない

Vite

# .env
VITE_API_URL=https://api.example.com    # ← クライアントに公開される
VITE_APP_TITLE=My App                   # ← クライアントに公開される
SECRET_KEY=sk-xxxxxxxxxxxx              # ← クライアントには公開されない
// アクセス方法
const apiUrl = import.meta.env.VITE_API_URL
const mode = import.meta.env.MODE        // "development" | "production"
const isDev = import.meta.env.DEV        // boolean
const isProd = import.meta.env.PROD      // boolean
  • VITE_ プレフィックスが付いた変数のみクライアントコードに公開される
  • プレフィックスなしの変数はサーバーサイド(vite.config.ts等)でのみ利用可能

Next.js

# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com  # ← クライアントに公開
DATABASE_URL=postgres://localhost/mydb        # ← サーバーサイドのみ
// クライアントコンポーネントで利用可能
const apiUrl = process.env.NEXT_PUBLIC_API_URL

// サーバーコンポーネント・API Routeでのみ利用可能
const dbUrl = process.env.DATABASE_URL
  • NEXT_PUBLIC_ プレフィックスでクライアント公開を制御

各フレームワークのプレフィックス比較

フレームワーククライアント公開プレフィックスアクセス方法
ViteVITE_import.meta.env.VITE_XXX
Next.jsNEXT_PUBLIC_process.env.NEXT_PUBLIC_XXX
Create React AppREACT_APP_process.env.REACT_APP_XXX
Nuxt 3NUXT_PUBLIC_useRuntimeConfig().public.xxx
Angularなし(environment.tsで管理)environment.xxx

Dockerでの環境変数

docker runでの指定

# 個別に指定
docker run -e DATABASE_URL="postgres://db:5432/mydb" \
           -e API_KEY="sk-xxxx" \
           myapp

# ホスト環境変数を引き継ぐ(値を省略)
export DATABASE_URL="postgres://db:5432/mydb"
docker run -e DATABASE_URL myapp

# ファイルから読み込む
docker run --env-file .env myapp

# 複数のファイルを指定
docker run --env-file .env --env-file .env.local myapp

Dockerfileでの設定

# ビルド時の変数(イメージに残る場合がある)
ARG NODE_VERSION=20

# 実行時の環境変数(デフォルト値の設定)
ENV NODE_ENV=production
ENV PORT=3000

# ARGからENVへの受け渡し
ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION}

注意: ARGENV にシークレットを設定すると、イメージのレイヤーに残る。ビルド時のシークレットには --secret フラグを使用する。

# BuildKitのsecretマウント(シークレットがイメージに残らない)
docker build --secret id=mysecret,src=./secret.txt .
# Dockerfile内でのsecret参照
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

Docker Compose

# docker-compose.yml
services:
  app:
    image: myapp
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://db:5432/mydb
    env_file:
      - .env
      - .env.local

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword  # 本番では外部シークレット管理を推奨
      POSTGRES_DB: mydb

シークレット管理ツール

本番環境では、.envファイルや環境変数の直接設定ではなく、専用のシークレット管理ツールを使用することが推奨される。

ツール比較

ツール提供元特徴料金目安
AWS Secrets ManagerAWSAWSサービスとの統合が容易、自動ローテーション$0.40/シークレット/月 + API呼び出し
AWS SSM Parameter StoreAWS無料枠あり、シンプルな設定値管理Standard: 無料、Advanced: $0.05/パラメータ/月
Azure Key VaultMicrosoftAzure統合、HSMバックエンドトランザクションベース課金
Google Secret ManagerGoogleGCP統合、シンプルなAPI$0.06/10,000アクセス
HashiCorp VaultHashiCorpマルチクラウド、動的シークレット生成OSS版は無料、Enterprise版は有償
1Password / Doppler各社開発者フレンドリー、CLI統合チーム規模に応じた月額課金

AWS Secrets Manager

# シークレットの作成
aws secretsmanager create-secret \
  --name myapp/production/db \
  --secret-string '{"username":"admin","password":"s3cret","host":"db.example.com"}'

# シークレットの取得
aws secretsmanager get-secret-value \
  --secret-id myapp/production/db \
  --query SecretString --output text
// Node.js (AWS SDK v3)
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'

const client = new SecretsManagerClient({ region: 'ap-northeast-1' })

async function getSecret(secretId) {
  const command = new GetSecretValueCommand({ SecretId: secretId })
  const response = await client.send(command)
  return JSON.parse(response.SecretString)
}

const dbConfig = await getSecret('myapp/production/db')

AWS SSM Parameter Store

# パラメータの登録(SecureString = 暗号化)
aws ssm put-parameter \
  --name "/myapp/production/DATABASE_URL" \
  --value "postgres://admin:s3cret@db.example.com:5432/mydb" \
  --type SecureString

# パラメータの取得
aws ssm get-parameter \
  --name "/myapp/production/DATABASE_URL" \
  --with-decryption \
  --query Parameter.Value --output text

# パスベースで一括取得
aws ssm get-parameters-by-path \
  --path "/myapp/production/" \
  --with-decryption

HashiCorp Vault

# シークレットの書き込み
vault kv put secret/myapp/db \
  username=admin \
  password=s3cret

# シークレットの読み取り
vault kv get secret/myapp/db
vault kv get -field=password secret/myapp/db

# 動的シークレット(DB認証情報を一時的に生成)
vault read database/creds/myapp-role

Vaultの動的シークレット生成機能は、必要な時にだけ一時的な認証情報を作成し、TTL経過後に自動で無効化する。長期間固定のシークレットを持たないため、漏洩リスクが大幅に低減される。

CI/CDでのシークレット管理

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.API_KEY }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          echo "Deploying..."
          # シークレットはログに自動でマスクされる
  • Repository secrets: リポジトリ単位で設定
  • Environment secrets: production / staging など環境別に設定
  • Organization secrets: 組織内の複数リポジトリで共有
  • ワークフローログではシークレットの値が *** に自動マスクされる

GitLab CI/CD

# .gitlab-ci.yml
deploy:
  stage: deploy
  variables:
    DATABASE_URL: $DATABASE_URL  # CI/CD変数から注入
  script:
    - echo "Deploying..."
  environment:
    name: production
  • Settings > CI/CD > Variables から設定
  • Protected フラグで保護ブランチのみに制限可能
  • Masked フラグでログ出力時にマスク

AWS CodeBuild / CodePipeline

# buildspec.yml
version: 0.2
env:
  secrets-manager:
    DB_PASSWORD: "myapp/production/db:password"
    API_KEY: "myapp/production/api:key"
  parameter-store:
    DB_HOST: "/myapp/production/DB_HOST"
phases:
  build:
    commands:
      - echo "DB_HOST=$DB_HOST"

セキュリティのベストプラクティス

シークレットのローテーション

定期的にシークレットを更新し、漏洩時の被害を限定する。

# AWS Secrets Manager - 自動ローテーション設定
aws secretsmanager rotate-secret \
  --secret-id myapp/production/db \
  --rotation-lambda-arn arn:aws:lambda:ap-northeast-1:123456789:function:SecretsRotation \
  --rotation-rules '{"AutomaticallyAfterDays": 30}'

最小権限の原則

  • 各サービス・コンポーネントには必要最小限のシークレットのみアクセス許可を付与する
  • IAMポリシーでシークレットの読み取り範囲を限定する
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789:secret:myapp/production/*"
    }
  ]
}

監査

  • シークレットへのアクセスログを記録する(AWS CloudTrail、Vault Audit Log等)
  • 異常なアクセスパターンを検知するアラートを設定する
  • 誰が、いつ、どのシークレットにアクセスしたかを追跡可能にする

その他の推奨事項

項目推奨
暗号化保存時・転送時ともに暗号化(AES-256、TLS)
アクセス制御RBAC(ロールベースアクセス制御)を適用
バックアップシークレット管理ツール自体のバックアップと復旧手順を整備
有効期限APIキーやトークンには有効期限を設定
二重管理の回避シークレットの正規の保管場所を一か所に定める

アンチパターン

ソースコードへのハードコード

// ❌ NG: シークレットをコードに直接記述
const client = new S3Client({
  credentials: {
    accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
    secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
  },
})

// ✅ OK: 環境変数またはシークレット管理ツールから取得
const client = new S3Client({})  // IAMロールまたは環境変数から自動解決

Gitリポジトリへのコミット

コミット履歴に一度入ったシークレットは、git rm しても git log やリフログから復元可能。万が一コミットした場合は以下を実行する。

  1. 即座にシークレットを無効化・ローテーションする(最優先)
  2. git filter-repo や BFG Repo-Cleaner で履歴から削除する
  3. GitHubの場合、サポートに連絡してキャッシュの削除を依頼する
# git filter-repo による履歴からの削除(要事前バックアップ)
git filter-repo --path .env --invert-paths

ログへの出力

// ❌ NG: デバッグ時にシークレットをログ出力
console.log('DB connection:', process.env.DATABASE_URL)
console.log('Config:', JSON.stringify(config))  // configにシークレットが含まれる可能性

// ✅ OK: シークレットをマスクする
console.log('DB connection: [REDACTED]')
console.log('DB host:', new URL(process.env.DATABASE_URL).hostname)

その他のアンチパターン

アンチパターンリスク対策
全環境で同じシークレットを使い回す1つ漏洩で全環境に影響環境ごとに異なるシークレットを使用
シークレットをURLパラメータに含めるアクセスログに記録されるヘッダーやリクエストボディで送信
チャットやメールでシークレットを共有平文で残り続けるシークレット管理ツール経由で共有
シークレットを変更せず長期間使い続ける漏洩に気付かないリスク定期的なローテーションを実施
.env ファイルを本番サーバーに直接配置管理が属人化、追跡不可シークレット管理ツールを使用

参考リンク