目次
Webアプリをコンテナへ入れても、アプリの不具合から実行された処理が何を変更できるかは、実行ユーザーとマウント設定によって変わります。ソースコードを書き換えられ、ホストの広い領域へアクセスできる構成なら、コンテナという形式だけでは十分な制限になりません。
本稿は2026年10月24日にDocker公式資料を確認して整理した恒久対策の解説で、新しい脆弱性や侵害事件のニュースではありません。Linux上のDocker EngineとDocker Composeで動かすWebアプリを対象に、非root実行と読み取り専用化を段階導入する方法を示します。設定例は本サイトの提案であり、この執筆環境ではDockerを実行できていないため、動作検証済みの構成とは扱いません。
スポンサーリンク
最初に決めるのはアプリが書き込む場所
まず通常運用中に発生する書き込みを洗い出します。起動時の設定生成、キャッシュ、アップロード、セッション、PIDファイル、ログ、テンポラリーファイルを、実際のアプリと起動スクリプトから調べてください。ホームページが表示されるだけでは、管理画面や夜間ジョブの書き込みまで確認できません。
- アプリ本体と依存ライブラリー:ビルド時に確定し、実行時は変更しない
- 一時ファイルと再生成できるキャッシュ:消失しても復旧できる保存先へ限定する
- 画像・添付ファイルなどの永続データ:必要な領域だけを永続化し、バックアップ対象にする
- ログ:標準出力・標準エラーへ送る構成を検討し、保存先で保持期間と容量を管理する
これは保存方式を決めるための分類です。たとえばアップロードした原本まで/tmpへ移すと、再起動後に失われる可能性があります。一方、キャッシュをコードと同じディレクトリへ書くフレームワークは、そのまま読み取り専用にすると停止します。書き込みが失敗した場所を記録し、必要性を確認してから個別に許可します。
非root実行とRootless Dockerは別の設定
Docker公式のビルドのベストプラクティスは、権限を必要としないサービスにUSERで非rootユーザーを指定することを勧めています。Composeのuserでも実行ユーザーを指定できますが、UIDとGIDを変えるだけで、ファイル所有者やアプリの前提まで自動調整されるわけではありません。
本稿ではUID・GIDを10001に固定したアプリを仮定します。実際にはイメージの既存ユーザー、ファイル所有者、永続領域の権限に合わせて選びます。コードは読み取れても、不要な書き換えはできない所有関係にし、データ用ディレクトリだけに必要な書き込み権限を与えます。権限エラーを解消するために全体をchmod 777へ変更すると、この分離を失います。
なお、アプリのUSERを変えることと、Dockerデーモン自体を非rootで動かすDockerのRootless modeは別です。前者を設定してもRootless Dockerを導入したことにはなりません。ホスト側の構成まで変更する場合は、ネットワークやストレージを含めた別の移行検証が必要です。
スポンサーリンク
Composeの例は書き込み先を/tmpへ絞る
次の例は、非rootで8080番ポートを待ち受け、永続データを外部へ保存し、一時ファイルだけを/tmpへ作るアプリを想定しています。APP_IMAGEには自分でレビューしたイメージ参照を設定してください。実在する配布イメージ名や安全性を確認したダイジェストを示す例ではありません。
services:
web:
image: ${APP_IMAGE:?set APP_IMAGE to a reviewed image}
user: "10001:10001"
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=64m,mode=1777
ports:
- "127.0.0.1:8080:8080"
主な項目はDocker公式のComposeサービス仕様に基づきます。read_onlyはルートファイルシステムへの書き込みを抑え、userはプロセスの実行ユーザーを指定します。cap_dropはLinux capabilitiesを削減し、no-new-privilegesはプロセスが新たな権限を得る動作を制限します。これらは別々の制御なので、一項目を設定しただけで他も有効になったとは判断できません。
例ではcap_drop: ALLから始め、Webアプリに不要な追加権限を与えない方針にしています。専用機能のために権限が必要なら、処理と理由を確認して最小限だけ追加します。エラーの回避策としてprivilegedを付けたり、seccompやホストの保護機構をまとめて無効化したりする方法は、同じ対策の延長ではありません。
127.0.0.1:8080:8080は、同じホストのリバースプロキシなどから接続するための例です。アプリはコンテナ内の外部接続を受けられるアドレスで待ち受ける必要があります。別コンテナのプロキシやロードバランサーから接続する構成では、そのネットワーク設計に合わせて変更します。公開用TLSや認証をこの例だけで構成できるわけではありません。
読み取り専用でも書ける場所と読める秘密は残る
Dockerのdocker container runの説明では、読み取り専用のルートファイルシステムとボリュームを併用できます。つまりread_onlyを設定しても、書き込み可能でマウントした領域まで一括で読み取り専用にはなりません。コードの置き場へ書き込み可能なbind mountを重ねれば、意図した制限を崩すことがあります。
設定レビューでは、Composeファイルだけでなく、追加のoverrideファイル、実際のMounts、イメージに定義されたボリュームも確認します。アプリにホストのルート領域やDockerソケットを渡さないことも重要です。Dockerソケットは通常のデータファイルとは異なり、デーモンへ操作を要求する経路になります。
また、読み取り専用化は秘密情報の読み出しや外部通信を防ぐ制御ではありません。侵害されたプロセスが正規のDB資格情報を読めるなら、許可された範囲でDBへ接続し得ます。APIの権限、送信先、到達できるネットワークも別途絞ります。更新が必要な脆弱性を、この設定だけで解消したと扱わないでください。
スポンサーリンク
tmpfsは容量と消失条件まで設計する
例の/tmpはtmpfsです。Docker公式のtmpfs資料によると、停止すると内容は保持されず、使用量はコンテナのメモリー制限にも算入されます。スワップへ書かれる可能性もあるため、「メモリー上だからディスクへ絶対に残らない」という秘密情報の保護策にはできません。
64MiBという値はサンプルの仮定で、推奨容量や計測結果ではありません。画像変換、圧縮展開、PDF生成などの最大処理を調べて決めます。メモリーと一時領域の両方が不足した場合の応答、後片付け、再試行も確認してください。大きなアップロードを受ける機能では、ファイルサイズ制限だけでなく、並行処理による合計量も考えます。
noexecはマウント先からの直接実行を制限する設定ですが、読み取れるスクリプトを別のインタープリターへ渡す行為まで一律に禁止するものではありません。万能なコード実行防止策ではなく、用途に不要な実行方法を減らす補助策です。ネイティブライブラリーの展開などが失敗する場合は、必要な作業をビルド時へ移せないか検討します。
設定ファイルと実際に動くコンテナを照合する
本番の複製ではなく、秘密情報と実データを外した検証環境から始めます。変更前のイメージ参照、実行設定、機能テストを記録し、非root実行、書き込み先の分離、読み取り専用化の順で差分を小さくします。イメージ側の起動スクリプトがrootでのchownを前提にしているなら、最初の段階から修正が必要です。
docker compose config
docker compose up -d web
docker inspect "$(docker compose ps -q web)"
configで展開後の設定を見てから起動し、docker container inspectで生成されたコンテナを確認します。Config.User、HostConfig.ReadonlyRootfs、CapDrop、SecurityOpt、TmpfsとMountsを点検し、意図したUID・GID、読み取り専用化、権限削減、マウント先が反映されたか照合します。出力には環境変数などの機密情報が含まれ得るので、ログや質問サイトへそのまま貼り付けないでください。
シェルとid・touchがあり、コードの保存先として/appが存在する検証用イメージなら、次の確認もできます。最初のtouchは失敗し、次は成功することを期待する確認例です。エラー理由が読み取り専用なのか、単なる権限不足や存在しないパスなのかも区別してください。実際のアプリに合わせ、既存ファイルと衝突しない名前を使います。
docker compose exec web id
docker compose exec web sh -c 'touch /app/.write-probe'
docker compose exec web sh -c 'touch /tmp/.write-probe && rm /tmp/.write-probe'
これらの結果は本稿で実行して得た実測ではありません。シェルを含まない本番イメージではコマンドを追加するためだけに構成を変えず、検証用ビルドやアプリの結合テストで同じ条件を確認します。idで実行ユーザーを確認しても、すべての子プロセスや起動時の権限まで説明できるとは限らない点にも注意します。
公開前は正常系と失敗時の両方を見る
- ログイン、更新、アップロード、キャッシュ再生成、定期ジョブを実行し、権限エラーがないか確認する
- コンテナを再作成し、必要なデータだけが残り、一時データは消えても復旧できるか確認する
- 大きな一時ファイルや同時要求で、容量不足を適切に処理できるか確認する
- ヘルスチェック、ログ収集、停止処理、監視が追加権限なしで成立するか確認する
- 以前の構成へ戻す場合も、未修正のイメージや広すぎる権限へ安易に戻さない
ここまで確認してから段階的に本番へ反映し、起動失敗率、HTTPエラー、書き込みエラー、一時領域とメモリー使用量を見ます。失敗した機能を特定できれば、全体の制限を解除する代わりに、必要な保存先や起動処理を修正できます。採用の判断基準は「設定項目が増えたか」ではなく、業務機能を維持しながら、変更可能な範囲を説明できるかです。
関連記事
Webサーバー侵害時の証拠保全と信頼できる環境からの再構築では、更新や再起動だけで侵害が消えるとは限らない場合の初動を扱っています。本稿の制限は平時の被害範囲を抑えるためのもので、侵害確認や復旧手順の代わりにはなりません。
スポンサーリンク
参考資料
- Docker:EngineのセキュリティとLinux capabilities
- Docker:ビルドのベストプラクティスとUSER
- Docker:Composeのサービス設定仕様
- Docker:コンテナ実行のオプション
- Docker:tmpfsの制約とマウント
- Docker:コンテナ設定の読み取り
- Docker:Rootless mode
資料確認日:2026年10月24日。新着確認ではIPA・JPCERT/CC・JVNと提供元の告知を参照しましたが、CISAのアドバイザリとKEVは403で取得できず、その範囲は未確認です。常時監視や全情報源の網羅を行ったものではありません。本文と概念イラストの作成に生成AIを使用し、公式資料との照合と保存内容のレビューを行っています。









