言語を切り替える
テーマを切り替える

Docker Secrets完全ガイド:コンテナのパスワードとAPIキーを安全に管理するベストプラクティス

スマホが激しく振動し、届いた24通はすべてデータベース異常アラートでした。SSHでサーバーに入ると、接続数は上限いっぱい。誰かが総当たり攻撃をかけています。

ログを追うと、攻撃者は正しいパスワードをそのまま使っていました——3ヶ月前に docker-compose.yml に書いたMySQLのパスワードです。先月、インターンが練習用に公開リポジトリへフォークしたことで、パスワードが漏洩していました。

コード漏洩がなくても、docker inspect が実行できれば環境変数はすべて平文で見えます。本記事では、コンテナのパスワード管理で避けるべき危険な運用と、機密情報を本当に守るための手段を整理します。

なぜ環境変数は安全ではないのか?

よくある3つの危険なやり方

まず、自分が犯してきたミスを共有します。当てはまるものはありませんか?

第一:Dockerfileに直書き

FROM node:18
ENV DATABASE_PASSWORD=MyS3cr3tP@ssw0rd
ENV API_KEY=sk-1234567890abcdef

これが最悪です。Dockerfileの各命令はイメージレイヤーを作り、後から ENV DATABASE_PASSWORD="" で上書きしても、履歴レイヤーにパスワードが残ります。イメージを手に入れた人は docker history <イメージ名> で取り出せます。

信じられない?私もそうでした。ある同僚が社内イメージを引っ張り、数コマンドでAWSのAccess Keyを見つけました。3年前に退職した人が残したキーで、ずっと削除し忘れていました。

第二:docker-compose.ymlに書く

version: '3.8'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: SuperSecretPassword123
      MYSQL_DATABASE: myapp

私は長くこれを使っていました。理由は単純で、楽だから。docker-compose up -d 一発で立ち上がり、開発が速い。

問題は、このファイルをGitに載せることです。プライベートリポジトリでも、権限管理の穴や、今回のように公開リポジトリへフォークされると、パスワードは一気に漏れます。

第三:.envを使うがバージョン管理に入れる

# .env
DB_PASSWORD=password123
API_SECRET=abcdef123456

一見賢く見えます。機密を .env に分離し、composeでは ${DB_PASSWORD} で参照する。

しかし多くの人(昔の私も)が .env までコミットします。「チーム全員が動かせるように」と。結果は、composeに直書きするのと同じです。

正しくは .env.gitignore に入れ、.env.example をテンプレートとして渡すこと。ただ、最初からそこまで考える人は少ないでしょう。

環境変数の3大リスク

.env.gitignore に入れ、コードも漏れなくても、環境変数は依然として危険です。なぜか。

リスク1:docker inspect で全部見える

次のコマンドを試してください。

docker inspect <コンテナID> | grep -A 20 "Env"

パスワードを含む環境変数がすべて平文で表示されます。

つまり、Dockerデーモンにアクセスできる人——運用、DevOps、サーバー権限のある開発者——はパスワードをそのまま読めます。クラックも高度な技術も不要で、コマンド一つです。

以前、スタートアップで全員が本番SSH権限を持っていました。ある日、新しいフロントエンドが「コンテナに何が入っているか」を docker inspect で見て、DBパスワードが画面に出てきました。幸い内部の人でしたが、悪意があれば?

リスク2:コンテナ内のプロセスも読める

環境変数はDockerだけでなく、コンテナ内のすべてのプロセスからも読めます。Linuxでは /proc/<PID>/environ に保存されます。

依存ライブラリに脆弱性がありコードが注入されると、攻撃者は環境変数からAWS認証情報などを抜き取れます。

昨年、あるNode.jsパッケージにバックドアが入り、環境変数のAWS認証情報を外部へ送る事件があり、数万プロジェクトが影響を受けました。

リスク3:ログに残る

こちらはより見えにくいです。

起動時に設定を出力したり、エラー時に環境変数をダンプしてログに残すことがあります。ログは権限が緩く、ELKなど集中ログシステムへ送られることも多いです。

あるチームでは全コンテナログをElasticsearchに集約し、監査で数百件のDBパスワードがログに残っていることが判明しました。デバッグで console.log(process.env) したまま消し忘れた、という典型です。

ログに入ったパスワードは、バックアップやアーカイブに散らばり、完全削除は難しいです。

実例

こうした事故はネット上にも多く、公開されない会社も少なくありません。身近な例をいくつか。

事例1:GitHub公開リポジトリ

2023年の分析では、GitHub上の100万件超の公開リポジトリにAPIキーやDBパスワードが含まれていました。開発者が愚かというより、テスト設定の消し忘れや .env の誤コミットが多いです。

MLチームの知人は、訓練コードを公開した際にAWS認証情報もpushしました。24時間以内にGPUインスタンスを大量起動され、請求は2万ドル超に跳ね上がりました。

事例2:CI/CDのビルド履歴

JenkinsやGitLab CIなどは、ビルド履歴に環境変数ログを残すことがあります。マスキングが不十分なら、ログがパスワードの宝庫になります。

あるプロジェクトでは、退職前の同僚が外部チームに権限を渡し、過去のビルドログから本番DB接続情報を見つけられました。幸い善意の報告でしたが、悪意があれば?

事例3:Docker Hubのイメージスキャン

Alibaba Cloud(アリババクラウド)のセキュリティレポートでは、公開 Docker イメージをスキャンした結果、76%に脆弱性があり、その多くにハードコードされた認証情報が含まれていました。

「小さい会社のイメージなんて誰も見ない」と公開Hubへpushし、数分で自動スキャナに拾われる例も珍しくありません。

怖がらせるためではなく、実際に起きていて、想像以上に多いということ。そして、避ける手段はある、という話です。

Docker Secretsとは?使い方は?

仕組み

ここから解決策です。

Docker SecretsはDocker公式のキー管理機能です。要点は、キーを平文の文字列ではなく、暗号化されたファイルとして扱うことです。

流れは次のとおりです。

  1. secretを作成すると、クラスタのRaftログに暗号化して保存される
  2. コンテナが必要なとき、TLSで暗号化して転送される
  3. コンテナ内の /run/secrets/ にファイルとしてマウントされる
  4. このディレクトリは tmpfs(メモリ上のファイルシステム)で、ディスクに書かない
  5. コンテナ停止後、メモリから消える

重要な点:secretは環境変数に出ず、docker inspect の出力にも出ず、docker commit でイメージにも入らない

初めて公式ドキュメントを読んだときは難しく感じましたが、手を動かせばすぐ腑に落ちます。

制限として、Docker SecretsはSwarmモードが前提です。ローカル単機開発では「なぜ使えない?」と困りがちです。composeのfile secrets(機能は弱いが使える)や、本番のSwarm/Kubernetesが現実的な選択肢です。

ハンズオン:MySQL + WordPress

WordPressをデプロイし、MySQLのrootパスワードとユーザーパスワードを守る例です。

ステップ1:secret用ファイルを作る

パスワードをローカルファイルに書きます(Gitには載せない)。

echo "MyRootPassword123" > db_root_password.txt
echo "MyUserPassword456" > db_password.txt

ステップ2:Swarmを初期化(未初期化の場合)

docker swarm init

この1コマンドで十分です。単機でもSwarmは使えます。

ステップ3:Docker secretsを作成

docker secret create mysql_root_password db_root_password.txt
docker secret create mysql_password db_password.txt

作成後、すぐローカルの平文ファイルを削除します。

rm db_root_password.txt db_password.txt

Dockerが読み取って暗号化保存したら、ローカル平文は不要です。

確認:

docker secret ls

出力例:

ID                          NAME                  CREATED         UPDATED
abc123...                   mysql_root_password   5 seconds ago   5 seconds ago
def456...                   mysql_password        3 seconds ago   3 seconds ago

中身は見えません。名前とメタデータだけです。作成後は管理者でも平文は取れません。

ステップ4:docker-compose.yml

version: '3.8'

secrets:
  mysql_root_password:
    external: true
  mysql_password:
    external: true

services:
  db:
    image: mysql:8.0
    secrets:
      - mysql_root_password
      - mysql_password
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password
      MYSQL_PASSWORD_FILE: /run/secrets/mysql_password
      MYSQL_USER: wordpress
      MYSQL_DATABASE: wordpress
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: wordpress:latest
    depends_on:
      - db
    ports:
      - "8080:80"
    secrets:
      - mysql_password
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD_FILE: /run/secrets/mysql_password
      WORDPRESS_DB_NAME: wordpress

volumes:
  db_data:

ポイント:

  1. トップレベルの secretsexternal: true は既存secretを参照
  2. services.db.secrets:このサービスが使うsecret
  3. MYSQL_ROOT_PASSWORD_FILE:MySQL公式イメージは _FILE 付き変数でファイルから読める

_FILE はすべてのイメージが対応しているわけではありません。未対応なら起動スクリプトで読みます。

# entrypoint.sh の例
export DB_PASSWORD=$(cat /run/secrets/db_password)
# その後アプリを起動

ステップ5:デプロイ

docker stack deploy -c docker-compose.yml myapp

Swarmでは docker stack deploy を使います(docker-compose up ではありません)。

ステップ6:検証

MySQLコンテナに入り、マウントを確認します。

docker exec -it <コンテナID> sh
ls -la /run/secrets/
total 8
-r--r--r-- 1 root root 18 Dec 18 10:00 mysql_password
-r--r--r-- 1 root root 18 Dec 18 10:00 mysql_root_password

読み取り:

cat /run/secrets/mysql_root_password

アプリはパスワードを読めますが、メモリ上のみでディスクや環境変数には出ません

docker inspect でも平文は出ません。

docker inspect <コンテナID> | grep -i password

MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_root_password のようなパスだけが見え、パスワード本体は見えません

制限と代替

Docker Secretsは万能ではありません。

制限1:Swarmモード必須

ローカル単機でも docker swarm init はできますが、手順が増え、docker stack deploy に慣れる必要があります。

制限2:作成後は内容を変更できない

変更するには削除して作り直します。

docker secret rm mysql_password
echo "NewPassword789" | docker secret create mysql_password -

利用サービスも更新が必要です。

docker service update --secret-rm mysql_password --secret-add mysql_password myapp_db

環境変数のように再起動だけ、とはいきません。

制限3:単体 docker run では使えない

docker service createdocker stack 経由のデプロイが必要です。手軽な一時DBコンテナには向きません。

単機開発の代替:docker-compose file secrets

Swarmなしでも、ローカルファイルをsecretとしてマウントできます。

version: '3.8'

secrets:
  db_password:
    file: ./secrets/db_password.txt

services:
  db:
    image: mysql:8.0
    secrets:
      - db_password
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password
mkdir secrets
echo "MyPassword123" > secrets/db_password.txt

secrets/ は必ず .gitignore に!

環境変数よりは安全(docker inspect に出にくい、イメージにも入りにくい)ですが、本番のDocker Secretsほどではありません。

  • ローカルディスクに平文ファイルが残る
  • 暗号化転送や集中管理はない

ローカル開発なら十分なことが多く、本番はSwarmかKubernetesが無難です。

応用テクニック

テクニック1:マウントパスをカスタム

services:
  myapp:
    image: myapp:latest
    secrets:
      - source: db_password
        target: /app/config/database.pwd
        mode: 0400

mode: 0400 で所有者のみ読み取りにできます。

テクニック2:環境ごとに値を分ける

# 本番
docker secret create db_password prod_password.txt

# テスト(別Swarmクラスタ)
docker secret create db_password test_password.txt

アプリは /run/secrets/db_password だけを見ればよく、コードは共通のままです。

テクニック3:ローテーション

echo "NewPassword" | docker secret create db_password_v2 -

docker service update \
  --secret-rm db_password \
  --secret-add source=db_password_v2,target=/run/secrets/db_password \
  myapp_db

docker secret rm db_password

新secret名は db_password_v2 でも、target でマウント先は /run/secrets/db_password のままにできます。

テクニック4:stdinから作成

echo "MySecretPassword" | docker secret create db_password -

末尾の - はstdinです。一時ファイルも不要です。

対話入力(シェル履歴に残しにくい):

docker secret create db_password -
# パスワードを貼り付け、Ctrl+Dで終了

他のキー管理ツールとの比較

4ツール比較表

ツール向いている場面強み弱み習得難度
Docker SecretsDocker Swarmクラスタネイティブ、追加コストなし、設定が簡単Swarm限定、機能は基本、クロスプラットフォーム不可⭐ 低
Kubernetes SecretsK8s本番K8sネイティブ、Podライフサイクルと連動etcdはデフォルト非暗号化、追加設定が必要⭐⭐ 中
HashiCorp Vaultエンタープライズ、マルチクラウド、厳格なコンプライアンス最も高機能、動的シークレット、細かい権限、監査ログ構成が重い、運用コスト、学習曲線が急⭐⭐⭐ 高
AWS Secrets ManagerAWS中心、RDS/LambdaなどAWSサービスと深く連携、RDSパスワード自動ローテーション、マネージドAWSに縛られる、マルチクラウドは難しい、従量課金⭐⭐ 中

ざっくり:

  • 小規模 + Docker Swarm → Docker Secretsで十分
  • Kubernetes本番 → K8s Secrets + External Secrets OperatorでVault連携も可
  • 大企業・マルチクラウド・高セキュリティ → Vault
  • AWS中心 → Secrets Manager

最適解は一つではなく、進化に合わせて変わります。私の経路は、compose file secrets → Swarm + Docker Secrets → K8s Secrets、という流れでした。

選び方

個人・小規模

  • ローカル:compose file secrets
  • VPS数台:Swarm + Docker Secrets
  • 予算が厳しい:Vaultは重すぎることも

スタートアップ(10〜50人)

  • Docker:Docker Secrets
  • K8s:K8s Secrets + External Secrets Operator
  • AWSのみ:Secrets Manager

大企業

  • マルチクラウド:Vaultが定番
  • 監査・コンプライアンス:Vault
  • 専任セキュリティチーム:Vault

簡易決定木:

K8sを使う?
 └─ はい → K8s Secrets、高度ならVault
 └─ いいえ → Docker Swarm?
      └─ はい → Docker Secrets
      └─ いいえ → AWS?
           └─ はい → AWS Secrets Manager
           └─ いいえ → file secrets(開発)/ Vault(本番)

実際の選定例

事例1:SaaSスタートアップの進化

当初5人、VPS上のDocker。compose + file secretsでサーバー上のファイルを手管理。

6ヶ月後にマイクロサービス化し、Docker Swarm + Docker Secretsへ。クラスタ集中管理のため。

さらにK8s移行後はK8s Secrets + AWS連携部分はSecrets Manager、External Secrets Operatorで同期。今はマルチクラウドでVault検討中。

最初から最難関の構成は不要です。

事例2:金融系の一気導入

銀行のコンテナ化では、コンプライアンスから最初にVault。学習コストは高いが、監査ログ、動的DB認証、LDAP連携が一度で揃い、以降の運用コストは低め、という判断もあります。

本番環境ベストプラクティス・チェックリスト

今すぐやること

Docker Secretsを使わなくても、次は今日から。

✅ Git履歴の機密情報を確認

git log -p -S 'password' -S 'secret' -S 'api_key'
git log --all --full-history -- .env

見つかったら git filter-repoBFG Repo-Cleaner で履歴を消し、漏洩したパスワードはすべて変更してください。

✅ .gitignore を設定

.env
*.env
secrets/
db_password.txt
*_password.txt
*_secret.txt
*.pem
*.key

✅ GitHub Secret Scanning を有効化

Settings → Security → Secret scanning alerts。誤pushを早期検知できます。

✅ 漏洩の可能性がある認証情報をローテーション

  • DBパスワード
  • APIキー(AWS/OpenAI/Stripeなど)
  • JWT secret
  • OAuth client secrets
  • SSL秘密鍵

可能なものはすべて更新を。

Docker Secrets導入手順

ステップ1:機密情報の棚卸し

✓ MySQL rootパスワード
✓ アプリDBパスワード
✓ Redisパスワード
✓ JWT署名キー
✓ 第三者APIキー
✓ SSL秘密鍵
✓ OAuth client secret

ステップ2:ツール選定

Docker Secrets、K8s Secrets、Vaultから選び、後から移行も可。

ステップ3:secrets作成

docker swarm init
docker secret create db_password <(echo "パスワード")
docker secret create api_key <(echo "キー")

# compose file secrets の場合
mkdir secrets
echo "パスワード" > secrets/db_password.txt
echo "キー" > secrets/api_key.txt
chmod 600 secrets/*

ステップ4:設定ファイル修正

# 変更前
services:
  app:
    environment:
      DB_PASSWORD: "hardcoded_password"  # ❌

# 変更後
services:
  app:
    secrets:
      - db_password
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password  # ✅

ステップ5:アプリコード(必要なら)

const fs = require('fs');
const dbPassword = process.env.DB_PASSWORD_FILE
  ? fs.readFileSync(process.env.DB_PASSWORD_FILE, 'utf8').trim()
  : process.env.DB_PASSWORD;

ステップ6:テスト

  • 起動できるか
  • パスワードを読めるか
  • docker inspect に平文がないか
  • 機能が正常か

ステップ7:本番デプロイ

まずは旧環境変数をフォールバックとして残し、安定したら削除。

継続的な運用

90日ごとのローテーション

四半期ごとの変更をカレンダーに。退職者関連のパスワードは即変更。

アクセスログの監視

Vaultならauditログを定期確認。Docker/K8s Secretsは標準で監査が弱く、Falcoなどと併用も検討。

最小権限

services:
  frontend:
    secrets:
      - api_key

  backend:
    secrets:
      - db_password
      - api_key

スキャンツールをCIに

pre-commitでも効果的です。

よくある5つの落とし穴

1. ARGで機密を渡さない

# ❌
ARG DB_PASSWORD=secret
ENV DATABASE_URL=postgres://user:${DB_PASSWORD}@db/myapp

ARGは docker history に残ります。

2. secretをstdoutに出さない

# ❌
password = open('/run/secrets/db_password').read()
print(f"Using password: {password}")

ログに載ります。

3. 環境間で同じsecretを使わない

開発・テスト・本番は分離。テスト漏洩が本番に波及しないように。

4. /run/secrets を他パスにコピーしない

# ❌
cp /run/secrets/db_password /app/config/password.txt

tmpfs外に書くとディスクに残る可能性があります。直接読んでください。

5. 有効期限・ローテーションの監視

Vaultの動的キーなどは自動更新の仕組みを。深夜に期限切れで全停止、を避けます。

まとめ

核心は一言:見える場所にパスワードを置かない

環境変数は楽ですが docker inspect で丸見え。DockerfileのENVは履歴に残ります。多くの事故は「楽だから」という小さな妥協から起きます。

Docker Secretsは完璧ではありません——Swarmが必要で、変更も手間です。ただ、暗号化保存・暗号化転送・環境変数やイメージへの混入回避、という本質的な問題には効きます。Swarmなら最も手軽な選択肢です。

K8sならK8s Secrets、エンタープライズならVault、AWS中心ならSecrets Manager。最適なのは状況次第です。

今日、次を確認してください。

  • Git履歴にパスワードはないか
  • .env.gitignore 済みか
  • docker inspect でパスワードが見えるか

一つでも「はい」なら、時間を使って直す価値があります。いきなりVaultでなく、file secretsへ移すだけでも前進です。

セキュリティは一度きりの設定ではなく、ローテーション、異常監視、コードスキャンの習慣が大事です。

チームで標準を共有し、定期レビューすれば、深夜3時のアラートは減らせます。


参考リンク:

Docker Secretsによる安全な管理の全体フロー

コンテナのパスワードとAPIキーを安全に管理し、Docker/K8s/Vaultなどを比較。本番向けチェックリスト付き

⏱️ 目安時間: 1 時間

  1. 1

    ステップ1: セキュリティ問題と誤ったやり方を理解する

    セキュリティ問題:
    • 攻撃者が正しいパスワードをそのまま使えた——docker-compose.ymlに書いてあったMySQLのパスワード
    • 先月、インターンが練習用に公開リポジトリへフォークした
    • 漏洩の結果、DBが総当たり攻撃を受けた

    誤ったやり方:
    • パスワードを設定ファイルに直書きする
    • 環境変数なら安全だと思い込む
    • 「リポジトリはプライベートだから大丈夫」という発想
    • これらはすべてセキュリティリスク

    よくあるミス:
    • Dockerfileにパスワードを書く
    • docker-compose.ymlにパスワードを書く
    • 環境変数に書いたままGitへコミットする
    • .envを使うが.gitignoreに入れ忘れる
  2. 2

    ステップ2: Docker Secretsでキーを管理する

    Docker Secretsの流れ:
    • docker secret createで作成:docker secret create mysql_password -
    • docker-compose.ymlで参照:secrets: - mysql_password
    • コンテナ内は/run/secrets/から読む:cat /run/secrets/mysql_password
    • キーはDocker Swarm内で暗号化保存

    作成例:
    • echo "your-password" | docker secret create mysql_password -
    • composeで参照:secrets: - mysql_password
    • コンテナ内で読む:cat /run/secrets/mysql_password
  3. 3

    ステップ3: 他方式の比較とベストプラクティス

    他方式の比較:
    • Docker Secrets(Docker Swarm向け、暗号化保存)
    • Kubernetes Secrets(K8s向け、Base64エンコード)
    • HashiCorp Vault(エンタープライズ向け、動的シークレット)
    • AWS Secrets Manager(AWS環境、AWSサービスと連携)
    • 環境変数ファイル(.env、本番では非推奨)

    ベストプラクティス:
    • 本番では必ずSecretsで機密情報を管理
    • 定期的にキーをローテーション
    • trufflehogなどでGitリポジトリをスキャン
    • アクセス制御を設定し漏洩を防ぐ
    • コードにパスワードをハードコードしない

FAQ

なぜDockerfileやdocker-compose.ymlにパスワードを書いてはいけないのですか?
セキュリティ問題:攻撃者が正しいパスワードをそのまま使えました——docker-compose.ymlに書いてあったMySQLのパスワードです。先月、インターンが練習用に公開リポジトリへフォークし、漏洩の結果DBが総当たり攻撃を受けました。

誤ったやり方:
• パスワードを設定ファイルに直書きする
• 環境変数なら安全だと思い込む
• 「リポジトリはプライベートだから大丈夫」
• これらはすべてセキュリティリスクです

よくあるミス:
• Dockerfileにパスワードを書く
• docker-compose.ymlにパスワードを書く
• 環境変数のままGitへコミットする
• .envを使うが.gitignoreに入れ忘れる
Docker Secretsでキーを管理するには?
Docker Secretsの流れ:
• docker secret createで作成(docker secret create mysql_password -)
• docker-compose.ymlでsecretsを設定(secrets: - mysql_password)
• コンテナ内は/run/secrets/から参照(cat /run/secrets/mysql_password)
• キーはDocker Swarm内で暗号化保存されます

作成例:
• echo "your-password" | docker secret create mysql_password -
• composeで参照:secrets: - mysql_password
• コンテナ内で読む:cat /run/secrets/mysql_password
Docker Secretsと他のキー管理方式の違いは?
比較:
• Docker Secrets(Docker Swarm向け、暗号化保存)
• Kubernetes Secrets(K8s向け、Base64エンコード)
• HashiCorp Vault(エンタープライズ向け、動的シークレット)
• AWS Secrets Manager(AWS環境、AWSサービスと連携)
• 環境変数ファイル(.env、本番では非推奨)

選び方:
• Docker SwarmならDocker Secrets
• K8sならKubernetes Secrets
• エンタープライズ要件ならVault
• AWS環境ならSecrets Manager
本番環境でのキー管理のベストプラクティスは?
ベストプラクティス:
• 本番では必ずSecretsで機密情報を管理
• 定期的にキーをローテーション
• trufflehogなどでGitリポジトリをスキャン
• アクセス制御を設定し漏洩を防ぐ
• コードにパスワードをハードコードしない

定期確認:
• trufflehogでGitリポジトリをスキャン
• キー漏洩がないか確認
• CI/CDに自動スキャンを組み込む
• 漏洩があれば直ちにローテーション

6分で読めます · 公開日: 2025年12月18日 · 更新日: 2026年6月8日

関連記事

コメント

GitHubアカウントでログインしてコメントできます