「コンテナの中に入ってenvコマンドを実行すると、パスワードを含む環境変数が全部見えてしまう。もっと安全な管理方法があるはずだが、何を使えばいいかわからない」
環境変数でパスワードを渡すと、
docker inspectコマンドでコンテナの詳細情報を表示した際に機密情報が平文で表示されます。さらに、docker-compose.ymlをgitで管理していれば、リポジトリの履歴にパスワードが永久に残るリスクも伴います。この記事では、Docker Composeが提供するsecrets機能を使い、パスワードやAPIキーなどの機密情報をコンテナへ安全に渡す設計を解説します。ファイルベースのsecrets設定、PostgreSQLへの実践適用、複数シークレットの管理、よくあるエラーの対処まで、実際の設定ファイルと動作確認の出力例を交えて網羅します。動作確認環境はUbuntu 24.04 LTS、Docker Engine 27.1、Docker Compose v2.29です。
この記事のポイント
・docker compose secretsはパスワードを /run/secrets/ にtmpfsでマウントする
・docker inspectで機密情報が見えない安全な設計になっている
・compose.ymlのsecretsセクションにfile:でパスを指定するだけで使い始められる
・POSTGRES_PASSWORD_FILE等の_FILE変数対応イメージとそのまま連携できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜenvironmentにパスワードを直書きしてはいけないのか
docker-compose.ymlのenvironmentセクションにパスワードを書くと、起動はできますが、次の3つのリスクが生まれます。
・docker inspectで丸見えになる:コンテナの実行中はもちろん、停止後もdocker inspectコマンドを実行した際に「Env」フィールドに環境変数が平文で表示されます
・gitの履歴に残る:docker-compose.ymlをバージョン管理していると、一度コミットしたパスワードはgit logで永久に参照できる状態になります
・docker exec経由で見える:コンテナに入ってenvコマンドを実行するだけで全環境変数が表示されるため、コンテナにアクセスできれば誰でもパスワードを取得できます
実際にdocker inspectの出力を見てみましょう。以下はenvironmentにパスワードを書いた場合の例です。
# NG: environmentにパスワードを直書きした場合 # docker inspect
の "Env" セクション出力例(実行時の表示) "Env": [ "POSTGRES_USER=app_user", "POSTGRES_PASSWORD=StrongP4ssw0rd!", "POSTGRES_DB=myapp", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], # パスワードが平文で記録されている
この状態では、docker inspectコマンドを実行できる権限があれば誰でもパスワードを読み取れてしまいます。これを根本から解決するのがsecrets機能です。
Docker ComposeのSecretsの仕組みと基本設定
Docker ComposeのSecretsは、機密情報をtmpfs(メモリ上の一時ファイルシステム)に/run/secrets/シークレット名というパスでマウントする仕組みです。環境変数として渡すのではなく「ファイル」として渡すため、docker inspectのEnvセクションには機密情報が一切出てきません。
1. シークレットファイルを作成する
まず、機密情報を格納するファイルを作成します。
# secrets ディレクトリを作成してパスワードファイルを作成 $ mkdir -p ./secrets $ echo "StrongP4ssw0rd!" > ./secrets/db_password.txt $ chmod 600 ./secrets/db_password.txt # .gitignore に追加してgit管理対象から除外する(必須) $ echo "secrets/" >> .gitignore # ファイル作成後の確認 $ ls -la ./secrets/ total 12 drwxr-xr-x 2 tomohiro tomohiro 4096 Aug 1 09:15 . drwxr-xr-x 5 tomohiro tomohiro 4096 Aug 1 09:15 .. -rw------- 1 tomohiro tomohiro 17 Aug 1 09:15 db_password.txt
ファイルのパーミッションを600に設定するのが重要です。他のユーザーが直接読み取れないようにします。必ず.gitignoreへの追加も忘れずに行ってください。
【注意】secretsファイルの内容は絶対にgitにコミットしないでください。誤ってコミットした場合は、git filter-repoやgit-secrets等のツールで履歴から完全に削除する必要があります。パスワードが一度でもリモートリポジトリに到達した場合は、必ずパスワードを変更してください。
2. compose.ymlでsecretsを定義する
compose.ymlの最上位レベルにsecretsセクションを追加し、各サービスからそれを参照します。
# compose.yml(PostgreSQL + secrets の基本設定) services: db: image: postgres:16 secrets: - db_password environment: POSTGRES_USER: app_user # POSTGRES_PASSWORD ではなく _FILE サフィックス付きの変数を使う POSTGRES_PASSWORD_FILE: /run/secrets/db_password POSTGRES_DB: myapp # 最上位レベルで secrets を定義する(ここがないと動かない) secrets: db_password: file: ./secrets/db_password.txt
PostgreSQLの公式イメージはPOSTGRES_PASSWORD_FILEという変数に対応しており、指定したパスのファイルを読み込んでパスワードとして使います。この_FILEサフィックスの規約は、MySQL(MYSQL_ROOT_PASSWORD_FILE)やMariaDBでも採用されています。
3. コンテナ内でSecretsが正しく渡されているか確認する
docker compose up -dで起動後、次のコマンドで動作を確認します。
$ docker compose up -d [+] Running 2/2 ✔ Network myapp_default Created 0.1s ✔ Container myapp_db_1 Started 0.8s # /run/secrets/ にファイルが存在することを確認 $ docker compose exec db ls -la /run/secrets/ total 4 drwxr-xr-x 2 root root 60 Aug 1 09:20 . drwxr-xr-x 1 root root 20 Aug 1 09:20 .. -r--r--r-- 1 root root 17 Aug 1 09:15 db_password # ファイル内容を確認(読み取れる) $ docker compose exec db cat /run/secrets/db_password StrongP4ssw0rd! # 環境変数にはパスワード本体が出てこないことを確認 $ docker compose exec db env | grep -i pass POSTGRES_PASSWORD_FILE=/run/secrets/db_password # パスワード本体は Env に出てこない(ここが重要)
続いてdocker inspectでもパスワードが環境変数に出てこないことを確認します。
# docker inspect の "Env" セクション確認(実運用での出力例) $ docker inspect myapp_db_1 | python3 -c "import sys,json; data=json.load(sys.stdin); print(data[0]['Config']['Env'])" ['POSTGRES_PASSWORD_FILE=/run/secrets/db_password', 'POSTGRES_USER=app_user', 'POSTGRES_DB=myapp', 'GOSU_VERSION=1.17', 'LANG=en_US.utf8', 'PG_MAJOR=16', 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', 'PGDATA=/var/lib/postgresql/data'] # POSTGRES_PASSWORD は存在しない # パスワードは Secrets で管理されており、Env には出てこない
POSTGRES_PASSWORDがEnvセクションに一切登場していません。secretsで渡したパスワードはtmpfsにマウントされており、docker inspectの出力には反映されない設計です。
Docker ComposeのSecretsを含めたコンテナセキュリティ設計を体系的に学びたい方は、Docker実践講座(docker.linuxmaster.jp)をご覧ください。現役エンジニアが現場目線で教えるDockerの基礎から本番セキュリティ設計まで、ハンズオン形式で学べます。
→ Dockerマスター講座の詳細はこちら >>
実践:PostgreSQLとWebアプリの完成形Compose設計
実際の現場では、DBパスワードだけでなく複数の機密情報をサービス間でやり取りするケースがほとんどです。以下はWebアプリとPostgreSQLで複数のsecretsを使う完成形の設計例です。
1. 複数のシークレットを持つCompose設計
# compose.yml(WebアプリとDBで複数secretsを使う完成例) services: db: image: postgres:16 secrets: - db_password environment: POSTGRES_USER: app_user POSTGRES_PASSWORD_FILE: /run/secrets/db_password POSTGRES_DB: myapp volumes: - db_data:/var/lib/postgresql/data web: image: myapp:latest depends_on: - db secrets: - db_password - jwt_secret environment: DB_HOST: db DB_USER: app_user DB_NAME: myapp # パスワードの実体は /run/secrets/ 以下のファイルで渡す DB_PASSWORD_FILE: /run/secrets/db_password JWT_SECRET_FILE: /run/secrets/jwt_secret ports: - "8080:8080" volumes: db_data: # 複数の secrets を定義する secrets: db_password: file: ./secrets/db_password.txt jwt_secret: file: ./secrets/jwt_secret.txt
2. アプリ側でSecretsファイルを読み込む実装パターン
WebアプリがPythonの場合、環境変数ではなくファイルを読み込む形で実装します。
# Python でのSecrets読み込みパターン(app.py の例) import os def read_secret(secret_name): # _FILE サフィックス変数からファイルを読み込む汎用関数 file_path_env = f"{secret_name}_FILE" file_path = os.environ.get(file_path_env) if file_path and os.path.exists(file_path): with open(file_path, 'r') as f: return f.read().strip() # フォールバック: 直接環境変数から読む(開発環境向け) return os.environ.get(secret_name) # 使用例 db_password = read_secret("DB_PASSWORD") jwt_secret = read_secret("JWT_SECRET")
この実装パターンのポイントは、本番環境では_FILE変数からファイルを読み取り、開発環境(Secrets未設定の場合)は直接環境変数にフォールバックする点です。環境によってコードを切り替えずに済みます。
3. secretsディレクトリのファイル一式を作成する
# secrets ディレクトリとファイルをまとめて作成する $ mkdir -p secrets $ echo "StrongP4ssw0rd!" > secrets/db_password.txt $ echo "myJwtSuperSecret2024!" > secrets/jwt_secret.txt $ chmod 600 secrets/*.txt # 作成後の確認 $ ls -la secrets/ total 16 drwxr-xr-x 2 tomohiro tomohiro 4096 Aug 1 10:05 . drwxr-xr-x 5 tomohiro tomohiro 4096 Aug 1 10:05 .. -rw------- 1 tomohiro tomohiro 17 Aug 1 10:05 db_password.txt -rw------- 1 tomohiro tomohiro 23 Aug 1 10:05 jwt_secret.txt # git管理から除外されているか確認(必須) $ grep secrets .gitignore secrets/
.envファイルとSecretsの使い分け基準
Docker Composeでは.envファイルとsecretsの両方が使えます。使い分けの基準は「その情報が漏れた場合のリスク」です。
・.envファイルで管理すべきもの(非機密):ポート番号、ホスト名、ログレベル、APIのエンドポイントURL、タイムゾーン設定
・secretsで管理すべきもの(機密):データベースパスワード、JWTシークレット、外部APIキー(決済・メール・クラウドサービス)、TLS秘密鍵、OAuth2クライアントシークレット
判断に迷った場合のシンプルな基準は「その値がgitリポジトリに混入してもいいか?」です。答えがNoならsecretsで管理します。
# .env(非機密・gitにコミット可) DB_HOST=db DB_PORT=5432 DB_NAME=myapp APP_PORT=8080 LOG_LEVEL=info # secrets/*.txt(機密・gitignoreで除外) # db_password.txt ← データベースパスワード # jwt_secret.txt ← JWTシークレットキー # stripe_api_key.txt ← 決済APIキー
この分離により、.envはgit管理でチーム内で共有でき、secretsはサーバー上にのみ存在する状態を保てます。
よくあるエラーと対処法
1. 「service 'xxx' refers to undefined secret 'yyy'」エラー
サービスレベルでsecretsを参照しているにもかかわらず、最上位のsecretsセクションに定義がない場合に発生します。
$ docker compose up service "web" refers to undefined secret "jwt_secret" # 原因: compose.yml の最上位に secrets セクションが不足 # 修正前(NG) services: web: secrets: - jwt_secret # ← 最上位の secrets セクションがない # 修正後(OK) services: web: secrets: - jwt_secret secrets: jwt_secret: file: ./secrets/jwt_secret.txt
2. シークレットファイルが見つからないエラー
secretsセクションに記載したファイルパスが存在しない場合に発生します。
$ docker compose up Error response from daemon: open /home/tomohiro/myapp/secrets/db_password.txt: no such file or directory # 設定の確認(compose.yml のパス解釈を確認する) $ docker compose config | grep -A 5 secrets # ファイルの存在確認 $ ls -la secrets/ ls: cannot access 'secrets/': No such file or directory # 対処: secrets ディレクトリとファイルを作成する $ mkdir -p secrets $ echo "パスワード" > secrets/db_password.txt $ chmod 600 secrets/db_password.txt
3. _FILE変数に対応していないイメージへのSecret渡し
PostgreSQLやMySQLの公式イメージは_FILE変数に対応していますが、カスタムアプリやサードパーティイメージでは対応していない場合があります。その場合は、エントリーポイントスクリプトでファイルを読み込んで環境変数に設定するラッパーを用意します。
# docker-entrypoint.sh(_FILE変数非対応イメージへの対応例) #!/bin/sh set -e # secrets ファイルを読み込んで環境変数として設定する if [ -f "$APP_SECRET_FILE" ]; then APP_SECRET=$(cat "$APP_SECRET_FILE") export APP_SECRET fi # 元のコマンドを実行する exec "$@"
このスクリプトをDockerfileのENTRYPOINTに設定することで、任意のイメージにSecrets連携を後付けできます。
本記事のまとめ
docker compose secretsでパスワードを安全に管理する方法をまとめます。
| やりたいこと | 設定・コマンド |
|---|---|
| secretsファイルを作成する | echo "パスワード" > ./secrets/name.txt && chmod 600 ./secrets/name.txt |
| gitから除外する | echo "secrets/" >> .gitignore |
| compose.ymlでsecretを定義する | 最上位のsecrets: {名前}: file: ./secrets/name.txtで指定する |
| サービスにsecretを割り当てる | services以下の各サービスにsecrets: [名前]を追加する |
| PostgreSQLに渡す | 環境変数にPOSTGRES_PASSWORD_FILE: /run/secrets/名前を使う |
| コンテナ内でsecretを確認する | docker compose exec サービス名 cat /run/secrets/名前 |
| Envにパスワードが出ていないことを確認する | docker compose exec サービス名 env | grep -i pass |
Docker ComposeのSecretsを現場で使いこなすには、Linux基盤の理解が土台になります
secretsの仕組みはLinuxのtmpfsとファイルシステム権限設計に基づいています。/run/secrets/がなぜ安全なのか、docker inspectに出てこない理由は何かを理解しておくと、本番環境でのトラブルシュートが格段に速くなります。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:docker contextで複数サーバーのDockerをローカルから一元管理する方法|ssh contextの設定とTLS接続の実践手順
- この記事の属するカテゴリ:Dockerへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます