Dockerコンテナを利用したアプリケーション開発や運用において、APIキー、データベースのパスワード、秘密鍵などの「秘密情報(Secrets)」をどのように扱うかはセキュリティ上の重要な課題です。
本記事では、環境変数を経由した秘密情報の漏洩リスクを整理し、実務で採用できる具体的な対策パターン、設定例、およびデプロイ前のセキュリティチェックリストを提供します。
1. 読者が抱える課題とこの記事で分かること
抱える課題
-
.envファイルを誤ってGitリポジトリにコミットしてしまうリスクがある。 -
docker inspectコマンドやコンテナ内のプロセス一覧(psコマンド)から環境変数に設定したパスワードが平文で見えてしまう。 - 開発環境、ステージング環境、本番環境で安全に秘密情報を切り替える標準的な方法が分からない。
この記事で分かること
- Dockerにおける秘密情報管理の「悪い例」と「良い例」
- Docker Composeやマルチステージビルドを活用した具体的な実装コード
- 実務で使えるセキュリティチェックリスト
2. 秘密情報管理における「アンチパターン」と「推奨パターン」
まずは、避けるべき実装方法と推奨されるアプローチを比較します。
| 項目 | アンチパターン(避けるべき方法) | 推奨パターン(安全な方法) |
|---|---|---|
| Dockerfileへの記述 |
ENV DATABASE_PASSWORD="hardcoded_password" のように直接ハードコードする。 |
Dockerfileには環境変数の「キー」のみを定義し、値は実行時に外部から注入する。 |
| イメージのビルド |
docker build --build-arg で秘密情報を渡し、イメージ内に残す。 |
マルチステージビルドの利用、または実行時(Runtime)にマウントや環境変数として渡す。 |
| コンテナ実行時 |
-e オプションでコマンドラインから平文のパスワードを直接渡す(履歴に残る)。 |
環境変数ファイル(.env)の適切な制限、またはDocker Secretsや外部のシークレットマネージャーを利用する。 |
3. 具体的な実装例と設定方法
実務でよく使われる2つのアプローチについて、具体的な設定例を示します。
アプローチA:Docker Composeと.envファイルによる管理(開発・小規模環境向け)
この方法では、秘密情報をコード(docker-compose.yml)から分離し、ローカル環境の.envファイルで管理します。
1. docker-compose.yml の設定
環境変数の値部分を空にすることで、ホスト環境または同ディレクトリの.envファイルから値が自動的に読み込まれます。
version: '3.8'
services:
web-app:
image: node:20-alpine
ports:
- "3000:3000"
environment:
# 値を空にすることで、ホストの環境変数または.envファイルの値が注入されます
- DATABASE_URL
- API_KEY
command: ["npm", "start"]
2. .env ファイルの作成(ローカル開発環境用)
プロジェクトのルートディレクトリに作成します。
# .env (このファイルは絶対に変更管理ツールにコミットしないでください)
DATABASE_URL="postgresql://db_user:secure_password_here@db_host:5432/mydb"
API_KEY="your_actual_api_key_here"
3. .gitignore の設定
.env ファイルがGitリポジトリに追跡されないよう、必ず設定します。
# .gitignore
.env
アプローチB:Docker Composeの secrets 属性を利用したファイルマウント(より安全な管理)
環境変数(env)として値を保持すると、コンテナ内で env コマンドを実行した際や、エラーログに値が出力されてしまうリスクがあります。これを防ぐため、秘密情報を「ファイル」としてコンテナ内に一時的にマウントする方法が推奨されます。
1. docker-compose.yml の設定
version: '3.8'
services:
backend:
image: my-backend-app:latest
secrets:
- db_password
environment:
# アプリケーション側で「ファイルから読み込む」ためのパスを指定します
- DATABASE_PASSWORD_FILE=/run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
2. アプリケーション側での読み込み処理(Node.jsの例)
環境変数 DATABASE_PASSWORD_FILE が指定されている場合、そのパスからファイルを読み込んでパスワードとして使用します。
const fs = require('fs');
function getDatabasePassword() {
const passwordFilePath = process.env.DATABASE_PASSWORD_FILE;
if (passwordFilePath) {
try {
// ファイルからパスワードを読み込み、前後の不要な改行コード等を除去します
return fs.readFileSync(passwordFilePath, 'utf8').trim();
} catch (err) {
console.error(`Failed to read secret file at ${passwordFilePath}:`, err);
throw err;
}
}
// フォールバックとして通常の環境変数を参照(非推奨環境用)
return process.env.DATABASE_PASSWORD;
}
const dbPassword = getDatabasePassword();
4. 導入時の注意点とよくある失敗
1. docker inspect による環境変数の露出
docker run -e MY_PASSWORD=secret や docker-compose の environment に直接平文を記述してコンテナを起動すると、以下のコマンドで誰でも秘密情報を閲覧できてしまいます。
docker inspect <container_id>
対策: 本番環境では、コンテナ起動時に直接環境変数を渡すのではなく、オーケストレーションツール(AWS ECSのParameter Store/Secrets Manager連携、Kubernetes Secretsなど)を利用して、実行時にのみメモリ上に展開する仕組みを検討してください。
2. ビルド引数(ARG)に秘密情報を渡してしまう
docker build --build-arg API_KEY=xxxx でビルドしたイメージは、イメージのレイヤー履歴にその値が残ります。docker history <image_id> コマンドを実行することで、第三者に秘密情報が漏洩する原因になります。
対策: ビルド時にどうしても秘密情報が必要な場合は、Docker 18.09以降で導入された BuildKit のマウント機能(--mount=type=secret)を利用してください。
5. 実務用セキュリティチェックリスト
デプロイ前やコードレビュー時に以下の項目を満たしているか確認してください。
-
1. コードベースの確認
- Gitリポジトリ内に
.envや本番用の秘密情報ファイルがコミットされていないか。 -
.gitignoreに.envや*.pemなどの拡張子が正しく登録されているか。
- Gitリポジトリ内に
-
2. Dockerfileの確認
-
ENV命令でパスワードやAPIキーなどの機密情報がハードコードされていないか。 -
ARG命令で機密情報をビルド時に受け取る設計になっていないか(BuildKitのsecretマウントを使用しているか)。
-
-
3. 実行環境の確認
- 本番環境において、コンテナ内のプロセスがroot権限以外(一般ユーザー)で実行されているか(万が一コンテナに侵入された際、
/run/secretsなどのファイルへのアクセス権限を制限するため)。 - クラウド環境(AWS, GCP, Azure等)を利用する場合、各プラットフォームが提供するマネージドなシークレット管理サービスと連携できているか。
- 本番環境において、コンテナ内のプロセスがroot権限以外(一般ユーザー)で実行されているか(万が一コンテナに侵入された際、
6. まとめ
Dockerコンテナにおける秘密情報の管理は、利便性とセキュリティのトレードオフになりがちです。開発環境では .env ファイルの適切な運用(Git管理からの除外)を徹底し、本番環境ではコンテナ内に平文を残さない「ファイルマウント方式(Docker Secrets等)」や「外部シークレットマネージャーとの連携」を採用することが推奨されます。
※システム構成や利用するクラウドプロバイダによって最適な実装方法は異なるため、導入の際は各プラットフォームの公式セキュリティガイドラインも併せてご確認ください。