Dockerコンテナ内Node.jsアプリの環境変数トラブル解決法

Dockerコンテナ内で環境変数が読み込まれない原因
Dockerコンテナ内でNode.jsアプリケーションを実行する際、環境変数が正しく読み込まれないという問題は意外と多くの開発者が遭遇するトラブルです。環境変数は設定情報やAPIキーなどの機密情報を管理するために重要な仕組みですが、Docker環境では特有の問題が発生します。
主な原因として以下のようなケースが考えられます:
環境変数の設定場所の問題:
- Dockerfileで設定した環境変数
- docker-compose.ymlで設定した環境変数
- ホストマシンの環境変数 これらが優先順位によって上書きされ、期待した値が適用されないことがあります。
環境変数の読み込みタイミング: Node.jsアプリケーションは起動時に環境変数を読み込みますが、コンテナ実行後に動的に変更された環境変数は自動的に反映されません。
.envファイルの扱い方の誤解: 多くの開発者が.envファイルをDocker環境で正しく扱えていないことが問題を引き起こします。
実際に発生する典型的な問題のコード例を見てみましょう:
// app.js
console.log('DATABASE_URL:', process.env.DATABASE_URL);
console.log('API_KEY:', process.env.API_KEY);
// この環境変数が正しく設定されていないと、以下のコードは失敗します
const dbConnection = require('./database').connect(process.env.DATABASE_URL);
以下のようなDockerfileとdocker-compose.ymlの組み合わせで問題が発生することがあります:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# ここで環境変数を設定
ENV NODE_ENV=production
ENV DATABASE_URL=postgres://user:password@db:5432/mydb
CMD ["node", "app.js"]
# docker-compose.yml
version: '3'
services:
app:
build: .
environment:
- NODE_ENV=development
# DATABASE_URLが指定されていない
この場合、docker-compose.ymlでNODE_ENVは上書きされますが、DATABASE_URLはDockerfileの値が使用されます。また、API_KEYは全く設定されていないため、アプリケーションからは参照できません。
これらの問題を解決するには、環境変数の取り扱いについて Docker と Node.js の両方の観点から正しく理解する必要があります。次のセクションでは、docker-compose.ymlでの効果的な環境変数設定の方法について詳しく解説します。
docker-compose.ymlでの環境変数設定と問題点
docker-compose.ymlファイルは、複数のDockerコンテナを一括で管理するための設定ファイルですが、環境変数の設定に関していくつか注意すべき点があります。
基本的な環境変数の設定方法
docker-compose.ymlで環境変数を設定する基本的な方法は以下の通りです:
version: '3'
services:
node-app:
build: .
environment:
- NODE_ENV=development
- PORT=3000
- DATABASE_URL=mongodb://mongo:27017/myapp
- API_KEY=your_api_key_here
ただし、この方法にはいくつかの問題点があります:
環境変数の値がコード内に直接記述される: 機密情報がバージョン管理システムに保存されてしまう可能性があります。
環境ごとの切り替えが難しい: 開発環境、ステージング環境、本番環境で異なる値を使いたい場合に設定が煩雑になります。
変数の参照とデフォルト値
環境変数を安全に扱うために、docker-composeでは以下のようにホストマシンの環境変数を参照することができます:
version: '3'
services:
node-app:
build: .
environment:
- NODE_ENV=${NODE_ENV:-development}
- PORT=${PORT:-3000}
- DATABASE_URL=${DATABASE_URL}
- API_KEY=${API_KEY}
この方法では、以下のような動作になります:
${変数名}
: ホストマシンの環境変数の値を使用${変数名:-デフォルト値}
: ホストマシンの環境変数が設定されていない場合、デフォルト値を使用
この方法を使うと、機密情報をdocker-compose.ymlに直接記述せずに済みます。
実際のトラブルケースと解決策
よくある問題として、以下のようなケースがあります:
ケース1: 環境変数が空になる問題
# 問題のある設定
environment:
- API_KEY # 値が指定されていない!
この場合、API_KEY
には空の値が設定されます。正しくは以下のようにします:
# 正しい設定
environment:
- API_KEY=${API_KEY}
ケース2: 変数展開が正しく行われない問題
# 問題のある設定
environment:
- CONNECTION_STRING=mongodb://${MONGO_USER}:${MONGO_PASS}@mongo:27017
変数の一部だけを置換する場合、以下のように単一引用符ではなく二重引用符を使用する必要があります:
# 正しい設定
environment:
- "CONNECTION_STRING=mongodb://${MONGO_USER}:${MONGO_PASS}@mongo:27017"
ケース3: コンテナ間の環境変数共有
関連するコンテナ間で同じ環境変数を共有したい場合は、以下のようなアプローチが有効です:
version: '3'
# トップレベルで環境変数を定義
x-common-variables: &common-variables
DATABASE_HOST: mongodb
DATABASE_PORT: 27017
NODE_ENV: ${NODE_ENV:-development}
services:
node-app:
build: .
environment:
<<: *common-variables # 共通変数を展開
APP_SECRET: ${APP_SECRET}
worker:
build: ./worker
environment:
<<: *common-variables # 同じ共通変数を展開
WORKER_THREADS: 4
このように、YAML のアンカーとエイリアス機能を使うことで、複数のサービス間で環境変数を共有できます。
.envファイルとの連携
docker-compose.ymlの環境変数をさらに効率的に管理するには、次のセクションで解説する.envファイルとの連携が重要になります。docker-composeは自動的にカレントディレクトリにある.env
ファイルを読み込み、そこで定義された変数を${変数名}
の形式で参照できます。
次のセクションでは、.envファイルを使った環境変数管理の正しい方法について詳しく解説します。
.envファイルを使った環境変数管理の正しい方法
.envファイルは環境変数を管理するための便利な方法ですが、Docker環境で使用する場合には特有の注意点があります。このセクションでは、Node.jsアプリケーションをDockerコンテナで実行する際の.envファイルの正しい活用方法を解説します。
.envファイルの基本
.envファイルは、単純なキーと値のペアで環境変数を定義するテキストファイルです:
# .env ファイルの例
NODE_ENV=development
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp
API_KEY=your_secret_api_key
このファイルは通常、プロジェクトのルートディレクトリに置かれます。
Node.jsでの.envファイルの読み込み
Node.jsアプリケーションから.envファイルを読み込むには、一般的にdotenvパッケージを使用します:
// app.js
require('dotenv').config();
console.log('NODE_ENV:', process.env.NODE_ENV);
console.log('API_KEY:', process.env.API_KEY);
# インストール
npm install dotenv
Docker環境での.envファイルの扱い方
Docker環境で.envファイルを使う場合、2つの主要なアプローチがあります:
1. docker-compose.yml で env_file オプションを使用する
version: '3'
services:
node-app:
build: .
env_file:
- ./.env
このアプローチでは、.envファイルに定義された全ての環境変数がコンテナ内に読み込まれます。
2. .envファイルをコンテナにコピーし、dotenvで読み込む
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
# .envファイルをコピー(開発環境用)
COPY .env.example ./.env
COPY . .
CMD ["node", "app.js"]
このアプローチでは、アプリケーションコード内でdotenv
パッケージを使用して.envファイルを読み込みます。
よくある問題と解決策
問題1: .envファイルがGitにコミットされてしまう
機密情報を含む.envファイルをバージョン管理システムにコミットしてはいけません。
解決策:
.gitignore
ファイルに.env
を追加する
# .gitignore
.env
.env.local
.env.*
!.env.example
.env.example
という名前のサンプルファイルを用意し、実際の値ではなくプレースホルダーを含めておく
# .env.example
NODE_ENV=development
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp
API_KEY=your_api_key_here
問題2: Docker環境で.envファイルが読み込まれない
解決策:
- パス指定を確認する:
# docker-compose.yml
services:
node-app:
env_file:
- ./.env # 相対パスが正しいことを確認
- 複数の環境向けに.envファイルを使い分ける:
# 環境に応じて異なる.envファイルを使用
env_file:
- ./.env.${NODE_ENV:-development}
- ボリュームマウントで.envファイルを共有する:
volumes:
- ./.env:/app/.env
問題3: 優先順位の問題
環境変数の値が複数の場所で定義されている場合、どの値が使用されるかが混乱することがあります。
優先順位(高いものから順に):
- コマンドラインで設定された環境変数:
docker-compose run -e API_KEY=value node-app
- docker-compose.ymlの
environment
セクション - docker-compose.ymlの
env_file
セクションで指定されたファイル - Dockerfileの
ENV
命令 - アプリケーション内で
dotenv
が読み込む.envファイル
この優先順位を理解しておくことで、環境変数の値の予期しない上書きを防ぐことができます。
本番環境での.envファイルの扱い方
本番環境では、.envファイルの代わりに、より安全な環境変数の管理方法を検討すべきです:
- Kubernetes Secretsを使用する:
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: node-app
envFrom:
- secretRef:
name: node-app-secrets
- Docker Swarm Secretsを使用する:
# docker-compose.yml (swarm mode)
services:
node-app:
secrets:
- db_password
environment:
- DATABASE_PASSWORD_FILE=/run/secrets/db_password
- クラウドプロバイダの環境変数管理サービスを使用する:
- AWS Parameter Store
- Google Cloud Secret Manager
- Azure Key Vault
これらの方法を使用することで、機密情報をファイルシステムに平文で保存することなく、安全に管理できます。
次のセクションでは、実行時に環境変数を注入する高度なテクニックについて説明します。
実行時の環境変数注入テクニック
環境変数を効果的に管理するためには、コンテナの実行時に適切な値を注入するテクニックも重要です。このセクションでは、Node.jsアプリケーションをDockerコンテナで実行する際に利用できる、実行時の環境変数注入テクニックを紹介します。
コマンドラインからの環境変数の注入
Docker実行時に環境変数を直接指定することで、Dockerfileや設定ファイルを変更せずに環境変数を上書きできます。
# docker run コマンドで環境変数を指定
docker run -e NODE_ENV=production -e API_KEY=secret123 my-node-app
# docker-compose run での環境変数指定
docker-compose run -e NODE_ENV=production -e API_KEY=secret123 node-app
# docker-compose up での環境変数指定
NODE_ENV=production API_KEY=secret123 docker-compose up
この方法は、一時的な設定変更やテスト時に特に便利です。
シェルスクリプトを使った環境変数の動的設定
より複雑な環境変数の設定が必要な場合、シェルスクリプトを使うことができます:
#!/bin/bash
# run-with-env.sh
# 現在の日時をタイムスタンプとして設定
export BUILD_TIMESTAMP=$(date +%Y%m%d%H%M%S)
# 環境に応じた設定ファイルの読み込み
if [ "$ENV" = "production" ]; then
source .env.production
else
source .env.development
fi
# 追加の計算された環境変数を設定
export DATABASE_URL="mongodb://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
# Dockerコンテナを起動
docker-compose up
この方法では、複数の環境変数を事前に処理し、その結果をコンテナに渡すことができます。
起動時に環境変数を処理するエントリポイントスクリプト
Docker起動時に環境変数を処理するためのエントリポイントスクリプトを使用することもできます:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# エントリポイントスクリプトをコピー
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# エントリポイントを設定
ENTRYPOINT ["docker-entrypoint.sh"]
# デフォルトコマンド
CMD ["node", "app.js"]
エントリポイントスクリプトの例:
#!/bin/sh
# docker-entrypoint.sh
set -e
# データベースURLが指定されていない場合は構築する
if [ -z "$DATABASE_URL" ] && [ ! -z "$DB_HOST" ]; then
export DATABASE_URL="mongodb://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
echo "DATABASE_URL constructed from individual variables: $DATABASE_URL"
fi
# API_KEYが指定されておらず、ファイルパスがある場合は読み込む
if [ -z "$API_KEY" ] && [ ! -z "$API_KEY_FILE" ] && [ -f "$API_KEY_FILE" ]; then
export API_KEY=$(cat $API_KEY_FILE)
echo "API_KEY loaded from file: $API_KEY_FILE"
fi
# NODE_ENVに基づいて追加の設定を行う
if [ "$NODE_ENV" = "production" ]; then
export LOG_LEVEL=${LOG_LEVEL:-"info"}
export ENABLE_CACHE=${ENABLE_CACHE:-"true"}
else
export LOG_LEVEL=${LOG_LEVEL:-"debug"}
export ENABLE_CACHE=${ENABLE_CACHE:-"false"}
fi
# コマンドラインから渡された引数で元のコマンドを実行
exec "$@"
このスクリプトでは、コンテナの起動時に以下のような処理を行っています:
- 個別の変数から複合的な接続文字列の構築
- ファイルからの機密情報の読み込み
- 環境に応じた設定の自動調整
Node.jsアプリケーション内での環境変数の検証と設定
Node.jsアプリケーション自体で環境変数を検証し、適切なデフォルト値を設定することも重要です:
// config.js
const dotenv = require('dotenv');
// .envファイルを読み込む(存在する場合)
dotenv.config();
// 必須の環境変数を検証
const requiredEnvVars = ['DATABASE_URL', 'API_KEY'];
const missingEnvVars = requiredEnvVars.filter(
envVar => !process.env[envVar]
);
if (missingEnvVars.length > 0) {
throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
}
// 設定オブジェクトを作成
const config = {
nodeEnv: process.env.NODE_ENV || 'development',
port: parseInt(process.env.PORT || '3000', 10),
databaseUrl: process.env.DATABASE_URL,
apiKey: process.env.API_KEY,
logLevel: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
enableCache: process.env.ENABLE_CACHE === 'true',
timeout: parseInt(process.env.TIMEOUT || '5000', 10),
};
// 設定値のサニタイズと変換
if (config.databaseUrl.includes('@') && config.databaseUrl.includes(':')) {
// ログ表示時にパスワードをマスク
const maskedUrl = config.databaseUrl.replace(
/(mongodb:\/\/[^:]+:)([^@]+)(@.+)/,
'$1*****$3'
);
console.log('Using database URL:', maskedUrl);
}
// 不正な設定値をチェック
if (config.port < 1024 || config.port > 65535) {
throw new Error(`Invalid port: ${config.port}. Must be between 1024 and 65535.`);
}
// オブジェクトを凍結して変更を防止
module.exports = Object.freeze(config);
この方法では、アプリケーションの起動時に環境変数の存在と値を検証し、問題がある場合は早期に失敗させることができます。また、型変換やデフォルト値の設定も一元的に管理できます。
環境変数を使ったスケーラブルな設定管理
大規模なアプリケーションでは、環境変数を階層的に整理することで、より管理しやすくなります:
// config/database.js
module.exports = {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '27017', 10),
name: process.env.DB_NAME || 'myapp',
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: process.env.DB_SSL === 'true',
maxPoolSize: parseInt(process.env.DB_MAX_POOL_SIZE || '10', 10),
connectionTimeout: parseInt(process.env.DB_CONNECTION_TIMEOUT || '30000', 10),
};
// config/api.js
module.exports = {
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL || 'https://api.example.com',
timeout: parseInt(process.env.API_TIMEOUT || '5000', 10),
version: process.env.API_VERSION || 'v1',
};
// config/index.js
module.exports = {
env: process.env.NODE_ENV || 'development',
port: parseInt(process.env.PORT || '3000', 10),
database: require('./database'),
api: require('./api'),
logging: {
level: process.env.LOG_LEVEL || 'info',
format: process.env.LOG_FORMAT || 'json',
},
};
このアプローチを使うと、設定が論理的なグループに整理され、アプリケーションの成長に合わせて拡張しやすくなります。
これらのテクニックを組み合わせることで、Dockerコンテナ内のNode.jsアプリケーションの環境変数を柔軟かつ堅牢に管理できます。次のセクションでは、本番環境と開発環境での環境変数の使い分けについて詳しく説明します。
本番環境と開発環境での環境変数の使い分け
開発環境と本番環境では、異なる設定が必要になることがほとんどです。このセクションでは、Docker環境でNode.jsアプリケーションを実行する際に、開発環境と本番環境で環境変数を効果的に使い分ける方法を解説します。
環境変数を使った環境の識別
アプリケーションがどの環境で実行されているかを判断するために、通常はNODE_ENV
環境変数を使用します:
// app.js
const isDevelopment = process.env.NODE_ENV !== 'production';
const isProduction = process.env.NODE_ENV === 'production';
const isStaging = process.env.NODE_ENV === 'staging';
// 環境に応じた設定
if (isDevelopment) {
console.log('Running in development mode');
// 開発環境固有の設定
} else if (isProduction) {
console.log('Running in production mode');
// 本番環境固有の設定
} else if (isStaging) {
console.log('Running in staging mode');
// ステージング環境固有の設定
}
環境別の.envファイル
異なる環境に合わせて複数の.envファイルを用意することができます:
# .env.development
NODE_ENV=development
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp_dev
LOG_LEVEL=debug
ENABLE_CACHE=false
# .env.production
NODE_ENV=production
PORT=80
DATABASE_URL=mongodb://db.example.com:27017/myapp_prod
LOG_LEVEL=info
ENABLE_CACHE=true
# .env.staging
NODE_ENV=staging
PORT=8080
DATABASE_URL=mongodb://db-staging.example.com:27017/myapp_staging
LOG_LEVEL=debug
ENABLE_CACHE=true
そして、Docker Composeで環境に合わせたファイルを選択します:
# docker-compose.yml
version: '3'
services:
node-app:
build: .
env_file:
- ./.env.${NODE_ENV:-development}
実行時に環境を指定するには:
# 開発環境(デフォルト)
docker-compose up
# 本番環境
NODE_ENV=production docker-compose up
# ステージング環境
NODE_ENV=staging docker-compose up
環境別のDocker Composeファイル
より複雑な環境の違いを管理するには、環境ごとに異なるDocker Composeファイルを使用することもできます:
# docker-compose.yml(基本設定)
version: '3'
services:
node-app:
build: .
ports:
- "${PORT:-3000}:3000"
environment:
- NODE_ENV=${NODE_ENV:-development}
# docker-compose.dev.yml(開発環境の追加設定)
version: '3'
services:
node-app:
volumes:
- .:/app
- /app/node_modules
environment:
- LOG_LEVEL=debug
- ENABLE_CACHE=false
command: npm run dev
# docker-compose.prod.yml(本番環境の追加設定)
version: '3'
services:
node-app:
restart: always
deploy:
replicas: 3
environment:
- LOG_LEVEL=info
- ENABLE_CACHE=true
command: npm start
そして、これらのファイルを組み合わせて使用します:
# 開発環境
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
# 本番環境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
環境ごとに異なるDockerfile
ビルド時の違いが大きい場合は、環境ごとに異なるDockerfileを用意することもできます:
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
# 開発ツールをインストール
RUN npm install -g nodemon ts-node
# ホットリロード用の設定
ENV CHOKIDAR_USEPOLLING=true
# ソースコードはボリュームマウントで提供されるため、コピーは不要
CMD ["npm", "run", "dev"]
# Dockerfile.prod
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# TypeScriptをコンパイル
RUN npm run build
# 本番用イメージを軽量化
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
# 本番環境変数を設定
ENV NODE_ENV=production
CMD ["npm", "start"]
そして、docker-compose.ymlでビルドに使用するDockerfileを指定します:
# docker-compose.yml
services:
node-app:
build:
context: .
dockerfile: ${DOCKERFILE:-Dockerfile.dev}
以下のように実行します:
# 開発環境(デフォルト)
docker-compose up
# 本番環境
DOCKERFILE=Dockerfile.prod docker-compose up
環境変数をパラメータ化したk8s設定
Kubernetesを使用する場合は、ConfigMapsとSecretsを使って環境変数を管理できます:
# configmap-dev.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: node-app-config
labels:
env: development
data:
NODE_ENV: "development"
PORT: "3000"
LOG_LEVEL: "debug"
ENABLE_CACHE: "false"
# configmap-prod.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: node-app-config
labels:
env: production
data:
NODE_ENV: "production"
PORT: "80"
LOG_LEVEL: "info"
ENABLE_CACHE: "true"
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-app
spec:
replicas: 3
template:
spec:
containers:
- name: node-app
image: my-node-app:latest
envFrom:
- configMapRef:
name: node-app-config
- secretRef:
name: node-app-secrets
パラメータ化された秘密情報の管理
機密情報を環境ごとに管理する方法も重要です:
# 開発環境の秘密情報を管理
./scripts/create-secrets.sh development
# 本番環境の秘密情報を管理
./scripts/create-secrets.sh production
スクリプトの例:
#!/bin/bash
# create-secrets.sh
ENV=$1
case "$ENV" in
"development")
kubectl create secret generic node-app-secrets \
--from-literal=API_KEY=dev_api_key \
--from-literal=DATABASE_URL=mongodb://localhost:27017/myapp_dev
;;
"production")
# 本番環境では外部のシークレット管理ツールから値を取得
API_KEY=$(aws secretsmanager get-secret-value --secret-id my-app/api-key --query SecretString --output text)
DB_URL=$(aws secretsmanager get-secret-value --secret-id my-app/database-url --query SecretString --output text)
kubectl create secret generic node-app-secrets \
--from-literal=API_KEY="$API_KEY" \
--from-literal=DATABASE_URL="$DB_URL"
;;
*)
echo "Usage: $0 [development|production]"
exit 1
;;
esac
これらの方法を組み合わせることで、開発環境から本番環境まで一貫性のある方法で環境変数を管理できます。また、各環境の特性に合わせた設定を柔軟に行うことができます。
次のセクションでは、環境変数のセキュリティを確保するためのベストプラクティスについて説明します。
環境変数のセキュリティを確保するベストプラクティス
Dockerコンテナ内でNode.jsアプリケーションを実行する際、環境変数を通じて機密情報を扱うことになるため、セキュリティの確保は非常に重要です。このセクションでは、環境変数のセキュリティを保護するためのベストプラクティスを紹介します。
機密情報をDockerイメージに含めない
機密情報(APIキー、パスワードなど)をDockerfileに直接含めると、そのイメージを持つ誰もがその情報にアクセスできてしまいます。
やめるべき方法:
# 危険な方法
ENV API_KEY=1234567890abcdef
ENV DATABASE_PASSWORD=super_secret_password
推奨される方法:
- 実行時に環境変数を注入する:
docker run -e API_KEY=$(aws secretsmanager get-secret-value --secret-id api-key --query SecretString --output text) my-app
- シークレット管理ツールを使う:
# docker-compose.yml
services:
app:
environment:
- API_KEY=${API_KEY}
- DATABASE_PASSWORD=${DATABASE_PASSWORD}
そして、これらの値を安全に管理・設定します。
イメージ履歴からの情報漏洩を防ぐ
Dockerイメージの履歴には、以前のレイヤーで設定された環境変数が残ります。
問題のある方法:
# 最初に機密情報を設定
ENV SECRET_KEY=my_secret_key
# 後で変更しても、履歴に残る
ENV SECRET_KEY=placeholder
推奨される方法: マルチステージビルドを使って履歴をリセットする:
FROM node:18-alpine AS builder
# ビルド処理
FROM node:18-alpine
# 必要なファイルだけをコピー
COPY --from=builder /app/dist /app/dist
# ランタイム設定のみを行う
実行時の環境変数の保護
コンテナ実行中の環境変数も保護する必要があります:
- proc情報の保護:
# コンテナ内での/proc/self/environへのアクセスを制限
RUN chmod 700 /proc/self/environ
- 非rootユーザーでの実行:
# 専用ユーザーを作成
RUN addgroup -g 1001 nodejs && \
adduser -u 1001 -G nodejs -s /bin/sh -D nodejs
# アプリケーションディレクトリの所有権を変更
WORKDIR /app
RUN chown -R nodejs:nodejs /app
# 非rootユーザーに切り替え
USER nodejs
暗号化キーと認証情報の管理
特に重要な暗号化キーや認証情報は、より強固な保護が必要です:
外部シークレット管理ツールの使用:
- AWS Secrets Manager
- HashiCorp Vault
- Google Cloud Secret Manager
シークレットをファイルとして提供:
# docker-compose.yml
services:
app:
volumes:
- ./secrets:/run/secrets
environment:
- API_KEY_FILE=/run/secrets/api_key
アプリケーションコード:
// ファイルから機密情報を読み込む
const fs = require('fs');
const apiKey = fs.readFileSync(process.env.API_KEY_FILE, 'utf8').trim();
環境変数の検証とサニタイズ
環境変数の値は常に検証し、サニタイズすることが重要です:
// 環境変数の検証関数
function validateEnv(name, pattern, defaultValue = null) {
const value = process.env[name];
// 値が存在するか確認
if (!value) {
if (defaultValue !== null) {
return defaultValue;
}
throw new Error(`Required environment variable ${name} is missing`);
}
// パターンに一致するか確認
if (pattern && !pattern.test(value)) {
throw new Error(`Environment variable ${name} does not match required pattern`);
}
return value;
}
// 使用例
const port = parseInt(validateEnv('PORT', /^\d+$/, '3000'), 10);
const apiKey = validateEnv('API_KEY', /^[a-zA-Z0-9]{32}$/);
const dbUri = validateEnv('DATABASE_URI', /^mongodb:\/\/.+/);
ロギングと出力での機密情報の保護
環境変数をログに出力する際は、センシティブな情報をマスクする必要があります:
// 安全なログ出力関数
function safeLog(obj) {
// 機密情報フィールドのリスト
const sensitiveFields = ['password', 'secret', 'key', 'token', 'auth'];
// オブジェクトをディープコピー
const safeObj = JSON.parse(JSON.stringify(obj));
// 機密情報をマスク
function maskSensitiveInfo(obj, parentKey = '') {
Object.keys(obj).forEach(key => {
const fullKey = parentKey ? `${parentKey}.${key}` : key;
if (sensitiveFields.some(field => fullKey.toLowerCase().includes(field))) {
// 機密情報をマスク
obj[key] = typeof obj[key] === 'string' ? '******' : null;
} else if (obj[key] && typeof obj[key] === 'object') {
// ネストされたオブジェクトを再帰的に処理
maskSensitiveInfo(obj[key], fullKey);
}
});
}
maskSensitiveInfo(safeObj);
console.log(safeObj);
return safeObj;
}
// 使用例
safeLog({
apiEndpoint: 'https://api.example.com',
apiKey: 'secret-key-12345',
config: {
databasePassword: 'db-password',
adminToken: 'admin-token'
}
});
// 出力: { apiEndpoint: 'https://api.example.com', apiKey: '******', config: { databasePassword: '******', adminToken: '******' } }
コンテナ間通信の保護
複数のコンテナ間で環境変数を安全に共有する方法:
- Docker Networkを使用:
# docker-compose.yml
services:
app:
networks:
- backend
db:
networks:
- backend
networks:
backend:
driver: bridge
- 環境固有のコンテナ間認証:
// アプリケーションレベルでの認証
const internalServiceToken = process.env.INTERNAL_SERVICE_TOKEN;
const headers = {
'Authorization': `Bearer ${internalServiceToken}`
};
fetch('http://internal-service/api/data', { headers });
セキュリティ監査とスキャン
定期的なセキュリティチェックも重要です:
- イメージスキャン:
# Dockerイメージの脆弱性スキャン
docker scan my-node-app:latest
- 依存関係チェック:
# Node.jsの依存関係の脆弱性チェック
npm audit
- シークレットスキャン:
# gitリポジトリ内のシークレットをスキャン
git-secrets --scan
CI/CDパイプラインでのセキュリティ対策
CI/CDパイプラインでも環境変数のセキュリティを考慮する必要があります:
# .gitlab-ci.yml の例
stages:
- build
- test
- deploy
build:
stage: build
script:
- docker build -t my-node-app .
test:
stage: test
script:
- docker run --rm my-node-app npm test
deploy:
stage: deploy
environment:
name: production
script:
# 安全にシークレットを取得
- API_KEY=$(aws secretsmanager get-secret-value --secret-id api-key --query SecretString --output text)
# シークレットを使用してデプロイ
- docker run -e API_KEY="$API_KEY" my-node-app
これらのベストプラクティスを組み合わせることで、Dockerコンテナ内のNode.jsアプリケーションにおける環境変数のセキュリティを大幅に向上させることができます。また、定期的にセキュリティを見直し、最新の脅威に対応することも重要です。
環境変数の安全な管理は、アプリケーションのセキュリティ全体の重要な一部です。これらのプラクティスを実施し、チームのセキュリティ意識を高めることで、より安全なDockerベースのNode.jsアプリケーションを構築・運用できるでしょう。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
おすすめコンテンツ
おすすめDocker2025/5/20Dockerコンテナ内Node.jsアプリのソースマップデバッグが効かない問題の解決法
Dockerコンテナ内でTypeScriptやBabelでトランスパイルされたNode.jsアプリケーションをデバッグする際、ソースマップが正しく機能せず元のコードでデバッグできない問題の具体的な解決...
続きを読む Docker2025/5/20Dockerコンテナ内Node.jsアプリをChromeDevToolsでリモートデバッグする方法
Dockerコンテナ内で動作するNode.jsアプリケーションをChromeDevToolsでリモートデバッグする具体的な手順と設定例を紹介します。コードの実行を一時停止して変数を調査したい開発者に役...
続きを読む Docker2025/5/20Docker環境でNode.jsのホットリロードが効かない問題の解決法
Docker環境でNode.jsアプリケーションを開発中にホットリロードが効かない問題に悩んでいませんか?この記事では、この特定の問題を解決するための具体的な対処法をシンプルに解説します。
続きを読む Docker2025/5/20Docker環境でNodeモジュールが同期されない問題の解決法
Docker開発環境でのNode.jsプロジェクトでnode_modulesが正しく同期されない問題に悩んでいませんか?このよくある問題の具体的な解決策と実践的なコード例を紹介します。
続きを読む Node.js2025/5/20Node.jsアプリケーションのCORSエラー解決法:5分で実装できる完全ガイド
フロントエンドとバックエンドの連携で頻繁に遭遇するCORSエラーの原因と解決策を具体的なコード例とともに解説します。Node.js環境でのCORSポリシーの正しい設定方法から、開発環境と本番環境での違...
続きを読む Docker2025/5/20Dockerコンテナ内TypeScriptプロジェクトのデバッグ技法
Dockerコンテナ内でTypeScriptプロジェクトを効率的にデバッグする方法を解説します。VSCodeの設定からコンテナ内部のツールを活用したトラブルシューティングまで、具体的なコード例と共に詳...
続きを読む IT技術2025/5/4【初心者向け】Dockerで開発環境を簡単構築!ウェブ開発のトラブルを一発解決
異なる環境での「動かない…」という悩みを解決するDocker。環境構築の手間を劇的に減らし、チーム開発をスムーズにする方法を初心者向けに解説します。「設定がうまくいかない」「ローカルでは動くのに本番で...
続きを読む JavaScript2025/5/5Node.jsとWebSocketで作る!初心者でも実装できるリアルタイムWebアプリケーション開発チュートリアル
'Webの世界は常に進化しています。かつては「リクエストを送信して応答を待つ」という単方向の通信が一般的でしたが、現代のユーザーはよりダイナミックな体験を求めています。リアルタイムWebアプリケーショ...
続きを読む