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

Docker セキュリティ設定:root 実行を避ける本番向け完全ガイド

先月、知人の会社のコンテナ設定を見に行き、手元で docker inspect を叩いてみました——全部 root で動いていて、--privileged まで付いていました。2 週間後、コンテナが突破され、ハッカーはコンテナ脱出でホストを直接制御しました。

大げさな話ではありません。2024 年 1 月に公表された CVE-2024-21626 が典型例です。攻撃者はコンテナの「作業ディレクトリ」パラメータを制御するだけで、漏洩したファイル記述子を悪用し、ホストのファイルシステムを自在に操作できました。さらに衝撃的なのが、NSFOCUS(绿盟科技)の調査——Docker Hub 上のイメージの 76% にセキュリティ脆弱性があり、67% は高危険度です。

以前はあまり気にしていませんでした——Dockerfile も FROM ubuntu から RUN apt-get install だけ。コンテナの中だから隔離されているはず、と。負荷試験環境のコンテナが侵入されるまで。ログにハッカーがホストのディスクを mount するコマンドが残っていたとき、背筋が冷えました。

実は、コンテナを root から非 root ユーザーに切り替えるのは、思っているほど難しくありません。今日は次を整理します:なぜデフォルトの root が危険なのか、Dockerfile で非 root ユーザーを作る方法、--user の使い方、Capabilities や AppArmor の設定。読み終えれば、本番コンテナのセキュリティリスクを少なくとも 80% 下げられます。

76%
イメージに脆弱性
Docker Hub 統計
67%
高危険度脆弱性
NSFOCUS 調査
80%
リスク低減
非 root 設定
Source: NSFOCUS 研究報告

なぜ root でコンテナを実行してはいけないのか?

コンテナ脱出:サンドボックスからホストへ一歩

多くの人は「コンテナ=サンドボックス」と考え、中で何をしてもホストに影響しないと思っています。しかし現実は、コンテナの隔離は Linux の namespace と cgroup に依存しており、VM のようなハードウェアレベルの隔離ではありません。設定ミスやカーネル脆弱性があれば、この壁は紙のように破れます。

CVE-2024-21626 は血の教訓です。攻撃者は runc(Docker の低レベルランタイム)が作業ディレクトリを処理する際、ホストファイルシステムを指すファイル記述子を漏洩させることを突き止めました。技術的に言えば、コンテナ内からホストの任意ファイルを読み書きでき、/usr/bin/bash のような重要バイナリを上書きすることも可能でした。Web アプリのコンテナが乗っ取られ、ホスト上の全コンテナがマイニング用に差し替えられる——仮説ではなく、実際に起きたことです。

より一般的な攻撃経路が --privileged モードです。このパラメータは、Docker に「ホストの全権限をこのコンテナに渡せ」と指示するのと同義です。コンテナ内 root はデバイス mount、カーネルモジュールロード、ネットワーク設定変更など、ホスト root の能力をすべて持ちます。NSFOCUS の報告では、攻撃者が特権コンテナから mount /dev/sda1 /mnt 一行でホスト HDD をマウントし、cron job でデータを定期送信——10 分もかかりませんでした。

もう一つ見落としがちなリスクが Docker Socket のマウントです。コンテナ内で Docker コマンドを使いたくて /var/run/docker.sock をマウントすると、ホスト上の全コンテナを制御する鍵を渡すことになります。侵入後、新しい特権コンテナを作って脱出し、ホストを制御する——Tencent Cloud のセキュリティチームもこの攻撃チェーンを記録しています。

なぜ root ユーザーが最大の突破口なのか?

核心はシンプルです:コンテナ内 root(UID 0)とホスト root(UID 0)は同一ユーザー

「namespace で隔離されているのでは?」——確かにありますが、UID namespace はデフォルトで無効(互換性のため)です。カーネル脆弱性や設定ミスで namespace が効かなくなると、コンテナ内 root プロセスはホスト上でも root 権限を持ちます。私も SYS_ADMIN Capability 付きコンテナで root ユーザーがホストの procfs を mount し、/proc/sys/kernel/core_pattern にリバースシェルを書き込むテストを行い、ホスト root を取得——想像よりずっと簡単でした。

Alibaba Cloud のセキュリティ報告でも、コンテナ脱出の主な 5 原因はカーネル脆弱性、設定ミス、不安全なイメージ、権限乱用、不安全なコンテナ間通信——うち 4 つが root 権限と直結しています。非 root で実行するだけで、少なくとも 3 つのリスクを大幅に下げられます。

よくあるシナリオ:Node.js アプリが 80 番ポートをリッスンしたく、1024 未満は root が必要なので root で実行。Express にパストラバーサル脆弱性があれば /etc/passwd を読まれ、SSH でホストへ——コンテナ脱出すら不要です。

怖い話に聞こえますが、解決策は複雑ではありません。最小権限の原則——必要な権限だけ与え、安易に root を渡さない。

非 root ユーザーの設定:Dockerfile から始める

Docker コンテナ非 root ユーザーセキュリティ設定

Dockerfile で非 root ユーザーを作成し、ランタイムセキュリティパラメータを設定する完全ガイド。コンテナのセキュリティリスクを 80% 低減

Estimated time: PT30M

  1. 1

    Step 1: Dockerfile で非 root ユーザーを作成

    専用ユーザーとグループを作成:
  2. 2

    Step 2: ポートバインド問題への対処

    ポートバインド問題への対処:
  3. 3

    Step 3: ランタイムセキュリティパラメータ

    ランタイムセキュリティパラメータ:
  4. 4

    Step 4: Capabilities による権限の最小化

    Capabilities による権限の最小化:
  5. 5

    Step 5: AppArmor/SELinux の有効化

    Debian/Ubuntu は AppArmor(docker-default profile)、RHEL/CentOS は SELinux。デフォルト profile はすでに厳格で、多くの場合カスタム不要。
  6. 6

    Step 6: イメージスキャンと継続監視

    Trivy や Docker scan で定期的に脆弱性スキャン。高危険度は修正必須。異常再起動やリソース使用の監視、設定の定期監査。

非 root ユーザーの正しい作り方

標準的な書き方:

FROM node:18-alpine

# 専用ユーザーとグループを作成(UID/GID を指定)
RUN addgroup -g 5000 appgroup \
    && adduser -D -u 5000 -G appgroup appuser

# 作業ディレクトリ設定
WORKDIR /app

# ファイルをコピーして所有者を設定(重要!)
COPY --chown=appuser:appgroup package*.json ./
RUN npm install
COPY --chown=appuser:appgroup . .

# 非 root ユーザーに切り替え(以降のコマンドは appuser 権限)
USER appuser

# アプリ起動
CMD ["node", "server.js"]

シンプルに見えますが、各行に意味があります。

なぜ UID/GID を指定する? useradd で数字を指定せず自動割り当てにすると、コンテナごとに UID がずれ、データボリュームマウント時に権限不一致が起きやすくなります。固定 UID(例:5000)にすれば、ファイル権限の問題が大幅に減ります。

COPY --chown の利点 普通の COPY のあと RUN chown だと、root でコピーしたレイヤーと chown レイヤーの 2 段になります。--chown はコピー時に所有者を設定——レイヤーが省け、より安全です。chown を忘れて「Permission denied」で 30 分ハマった経験があります。

USER 命令の位置 その前のコマンド(RUN npm install など)は root、その後が appuser です。USER を早く書きすぎるとインストールが全部失敗します。root が必要な操作はすべて USER の前——これだけ覚えてください。

よくある落とし穴と対処

落とし穴 1:ポートバインド

非 root に変更して起動すると:Error: listen EACCES: permission denied 0.0.0.0:80。1024 未満は特権ポートです。

対処:

  1. 高ポートを使う(推奨):3000 や 8080 をリッスンし、Nginx や LB でリバースプロキシ
  2. NET_BIND_SERVICE Capability:後述。非 root でも低ポートをバインド可能
# アプリは 3000 をリッスン
EXPOSE 3000
USER appuser
CMD ["node", "server.js"]  # 内部は 3000

docker-compose や K8s でマッピング:

ports:
  - "80:3000"  # ホスト 80 → コンテナ 3000

落とし穴 2:ログと一時ファイルの書き込み

Python アプリを非 root にしたら、/var/log への書き込み権限がなく起動失敗——半日調査した経験があります。

# アプリ用ログディレクトリを作成して権限付与
RUN mkdir -p /var/log/myapp && \
    chown -R appuser:appgroup /var/log/myapp

USER appuser

より良い方法:stdout/stderr に出力し、Docker や K8s でログ収集。

# ファイルログではなく
logging.basicConfig(stream=sys.stdout, level=logging.INFO)

落とし穴 3:マウントボリュームの権限不一致

ホストの /data が root 所有で、コンテナ内 appuser(UID 5000)が読めない。

# 悪い例
docker run -v /data:/app/data myapp
# コンテナ内 appuser は /app/data を読み書きできない

2 つの解決策:

# 方法 1:ホストで事前に権限設定
sudo chown -R 5000:5000 /data

# 方法 2:名前付きボリューム(Docker が権限管理)
docker volume create appdata
docker run -v appdata:/app/data myapp

ランタイムセキュリティパラメータ:—user とその他

—user でイメージ設定を上書き

サードパーティイメージに USER がなく root だけ——再ビルドが面倒なら、--user で実行時に指定できます。

# 方法 1:UID:GID を直接指定
docker run --user=1001:1001 nginx:latest

# 方法 2:ホストの現在ユーザー(開発環境向け)
docker run --user="$(id -u):$(id -g)" -v "$PWD:/app" node:18 npm test

# 方法 3:既知のユーザー名(イメージ内に存在する場合)
docker run --user=nobody redis:alpine

方法 2 はローカル開発で特に便利。コンテナ内テストで生成したファイルがホストで正しい UID になり、sudo 削除が不要です。

注意:—user は Dockerfile の USER を上書きします。非 root 設定済みイメージを --user=0:0 で root に戻すと意味がありません。使用前にイメージのデフォルト設定を確認してください。

読み取り専用ファイルシステム:書き込みを封じる

侵入されても、ファイルシステムが読み取り専用なら、マルウェア植入や設定改ざんの大半が無効化されます。

# 最もシンプルな読み取り専用設定
docker run -d --read-only nginx:alpine

# 一時ファイルが必要な場合
docker run -d \
  --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  nginx:alpine

--tmpfs はメモリ上のファイルシステム。再起動で消え、一時ファイルに最適。API サービスはログを stdout、セッションを Redis に——永続書き込み不要なら読み取り専用で、シェル取得後もやれることが限られます。

権限昇格禁止:no-new-privileges

setuid/setgid などによる権限昇格を防ぎます。SUID 付き /bin/su があっても root に昇格できません。

docker run --security-opt=no-new-privileges myapp

このオプション付きコンテナで sudo を実行すると「effective uid is not 0」で失敗——権限昇格攻撃への有効な対策です。

本番向け:組み合わせ設定

パラメータを組み合わせた本格的なセキュリティ設定:

docker run -d \
  --name secure-webapp \
  --user=5000:5000 \         # 非 root ユーザー
  --read-only \               # 読み取り専用 FS
  --tmpfs /tmp:size=64M \     # 64MB 一時領域
  --security-opt=no-new-privileges \  # 権限昇格禁止
  --cap-drop=ALL \            # 全 Capabilities 削除
  --cap-add=NET_BIND_SERVICE \ # 必要なポートバインドのみ
  -p 443:8443 \               # ポートマッピング
  -v appdata:/app/data \      # データボリューム(唯一の書き込み先)
  --memory=512m \             # メモリ制限
  --cpus=1.0 \                # CPU 制限
  myapp:1.0.0

パラメータは多いですが、それぞれ目的が明確です。本番のコアサービスはこのテンプレートで 2 年以上、セキュリティ事故ゼロ。トレードオフはデバッグの手間——コンテナ内でファイルを直接編集できない——その代償は十分に見合います。

K8s ユーザーは Pod の SecurityContext で同様の設定が可能:

securityContext:
  runAsNonRoot: true
  runAsUser: 5000
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]

権限の細分化:Capabilities メカニズム

Capabilities とは?root より安全な理由

従来の Linux 権限は「全部かゼロか」——root か一般ユーザーか。Capabilities は root のスーパー権限を 40 以上の独立した「能力」に分割し、必要なものだけ付与できます。

例えるなら、root は全室のマスターキー、Capabilities は必要な部屋の鍵だけ。侵入されても、権限のある部屋にしか入れません。

Docker はデフォルトで 14 個の Capabilities を保持:

  • CHOWN:ファイル所有者変更
  • NET_BIND_SERVICE:1024 未満ポートのバインド
  • SETUID/SETGID:ユーザー/グループ ID 変更
  • KILL:他プロセスへのシグナル送信
  • DAC_OVERRIDE:ファイル読み書き権限チェックのバイパス

多くのアプリには十分ですが、いくつかは特に危険——必ず drop してください。

危険な Capabilities 一覧(絶対に付与するな!)

SYS_ADMIN — root の半分

mount、namespace 変更、カーネルモジュールロードなど。SYS_ADMIN 付きコンテナで unshare してホストディスクを mount されたら終わりです。

# 絶対にやるな!
docker run --cap-add=SYS_ADMIN myapp  # ❌ 危険!

NET_ADMIN — ネットワーク設定の制御

ルーティング、ファイアウォール、パケットキャプチャ。VPN やソフトルータ以外には不要。

SYS_MODULE — カーネルモジュールのロード

カーネルにコードを挿入できる——危険度は言うまでもありません。

最小権限の実践

戦略 1:全削除してから必要分だけ追加(推奨)

docker run -d \
  --cap-drop=ALL \          # 全 Capabilities 削除
  --cap-add=NET_BIND_SERVICE \  # ポートバインドのみ(必要なら)
  --cap-add=CHOWN \         # chown のみ(必要なら)
  myapp

最もよく使う戦略。Capability 不足でエラーが出たら追加。setuid() で「Operation not permitted」なら --cap-add=SETUID

戦略 2:危険なものだけ削除(手早い強化)

docker run -d \
  --cap-drop=SYS_ADMIN \
  --cap-drop=NET_ADMIN \
  --cap-drop=SYS_MODULE \
  --cap-drop=SYS_RAWIO \
  myapp

必要な Capabilities が不明でも、明らかに危険なものを外すだけの手早い方法。

必要な Capabilities の判断方法

方法 1:試行錯誤(愚直だが有効)

# ステップ 1:drop all でエラー確認
docker run --cap-drop=ALL myapp
# エラー:bind: permission denied

# ステップ 2:NET_BIND_SERVICE を追加
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
# 起動成功!

方法 2:capsh で分析

コンテナ内で capsh --print

$ docker run --rm -it --cap-drop=ALL ubuntu capsh --print
Current: =
# 空、Capabilities なし

$ docker run --rm -it ubuntu capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,...
# デフォルト 14 個

方法 3:一般的なアプリ要件表

アプリ種別必要な Capabilities説明
Web アプリ(高ポート)なし3000+ は特別な権限不要
Web アプリ(低ポート)NET_BIND_SERVICE80/443 リッスンに必要
DB(MySQL/Postgres)なしデフォルトは高ポート
Nginx/CaddyNET_BIND_SERVICE80/443 直接リッスン時
VPN/ネットワークツールNET_ADMINルーティング/NIC 設定

ほとんどの業務アプリは drop all だけで動き、必要なら NET_BIND_SERVICE を 1 つ足す程度です。

強制アクセス制御:AppArmor と SELinux

何をするもの?一言で

Capabilities は「プロセスが何をできるか」、AppArmor/SELinux は「どのファイル・リソースにアクセスできるか」を制御。OS レベルの MAC(強制アクセス制御)で、root でも profile ルールを破れません。

例えるなら、社長(root)でもサーバールームには IC カードが必要——AppArmor/SELinux がその IC カードシステムです。

システムの選び方

  • Debian/Ubuntu:AppArmor がデフォルト
  • RHEL/CentOS:SELinux がデフォルト
  • 両方同時は不可(競合する)

AppArmor:シンプルで十分(初心者向け)

Docker は docker-default profile を自動適用。多くの場合、何も設定しなくてもバックグラウンドで保護してくれます。

# コンテナの AppArmor profile を確認
docker inspect mycontainer | grep -i apparmor
# "AppArmorProfile": "docker-default"

docker-default の制限

  • ファイルシステムの mount 不可
  • カーネルパラメータ(/proc/sys/ 以下)の変更不可
  • ホストの機密デバイス(/dev/ の大部分)へのアクセス不可
  • AppArmor 自身の設定変更不可

AppArmor 有効コンテナでは、root でも mount /dev/sda1 /mnt は「Permission denied」。Capabilities + AppArmor の二重防御で、脱出難度は指数関数的に上がります。

SELinux:より強力だが複雑

各ファイル・プロセスに「ラベル」を付け、ポリシーでラベル間のアクセスを定義します。

# コンテナプロセスの SELinux ラベル
docker inspect mycontainer | grep -i selinux
# "ProcessLabel": "system_u:system_r:container_t:s0:c123,c456"

c123,c456 はカテゴリ——コンテナごとに固有の組み合わせで、コンテナ A がコンテナ B のファイルに触れないようにします。

SELinux の設定ハードルは高く、エラーメッセージも分かりにくい。Ubuntu なら AppArmor で十分。RHEL なら SELinux がデフォルトで保護していることが多く、触らなくてよい場合も多いです。

実践:どれを使う?どう使う?

シナリオ 1:開発環境

  • デバッグのため一時無効(apparmor=unconfinedlabel=disable)もあり
  • ただし:開発で無効にしたら、本番で有効に戻すのを忘れないか?

シナリオ 2:テスト環境

  • 必ず有効、デフォルト profile を使用
  • セキュリティ設定とアプリ機能の衝突を早期発見

シナリオ 3:本番環境

  • 必ず有効、妥協なし
  • デフォルト profile、十分な理由がなければカスタムしない
  • ログを定期監査し、違反アクセス試行を確認

経験上、DENIED の 99% は拒否されるべきもの——攻撃か、アプリ設計の問題。権限緩和が本当に必要な場面は極めて稀です。

完全なセキュリティチェックリスト

ここまでを実行可能な Checklist にまとめます。これに従えば、コンテナセキュリティは上位 20% に入れます。

イメージビルド段階

Dockerfile セキュリティチェック

  • ✅ 公式または信頼できるベースイメージ(出所不明は避ける)
  • ✅ イメージバージョン固定(node:latest ではなく node:18.17-alpine
  • ✅ 専用非 root ユーザーを作成し UID/GID を指定
  • COPY --chown でファイル所有者を設定
  • USER はインストール後・起動前
  • ✅ 高ポート(3000+)リッスン、または Capabilities を設定
  • ✅ マルチステージビルドでイメージサイズと攻撃面を削減
  • ✅ イメージに機密情報(鍵・パスワード)を含めない——環境変数や secrets で注入

イメージスキャン段階

必須のセキュリティスキャン

# Docker 標準 scan(Snyk ベース)
docker scan myapp:latest

# Trivy(より高速・包括的、推奨)
trivy image myapp:latest

# Clair(CI/CD 統合)
# Harbor リポジトリで自動スキャン設定

Docker Hub 上 76% のイメージに脆弱性——定期スキャンは必須です。私たちのチーム規則:

  • 高危険度:修正必須でないとデプロイ不可
  • 中危険度:リスク評価と監視が必要
  • 低危険度:記録して定期 review

ランタイム設定チェック

docker-compose 本番テンプレート

services:
  myapp:
    image: myapp:1.0.0
    user: "5000:5000"           # 非 root ユーザー
    read_only: true             # 読み取り専用 FS
    tmpfs:
      - /tmp:size=64M           # 一時ファイル用メモリマウント
    security_opt:
      - no-new-privileges:true  # 権限昇格禁止
      - apparmor=docker-default # AppArmor profile
    cap_drop:
      - ALL                     # 全 Capabilities 削除
    cap_add:
      - NET_BIND_SERVICE        # 必要分のみ追加
    volumes:
      - appdata:/app/data:rw    # 読み書き権限を明示
    deploy:
      resources:
        limits:
          cpus: '1.0'           # CPU 制限
          memory: 512M          # メモリ制限
    ports:
      - "8080:8080"

本番運用チェック

日常監視

  • ✅ コンテナの異常再起動(攻撃によるクラッシュの可能性)
  • ✅ リソース使用の異常(マイニングは CPU を食い尽くす)
  • ✅ 監査ログでコンテナ操作を記録

定期監査

  • ✅ 実行中イメージも毎月スキャン(新規だけでなく)
  • --privileged や危険 Capabilities を使うコンテナがないか
  • ✅ ネットワークポリシーと公開ポートの見直し

よくある質問と解決策

Q1: 非 root にしたら起動時に権限エラーが出る

診断手順

  1. エラー内容を確認(ファイル権限かポートバインドか)
  2. ファイル権限なら Dockerfile の --chown とディレクトリ権限を確認
  3. ポートバインドなら高ポートに変更、または NET_BIND_SERVICE を追加

よくあるエラーと修正

# エラー:Error: EACCES: permission denied, open '/app/logs/app.log'
# 原因:ログディレクトリに appuser の書き込み権限がない
# 修正:
RUN mkdir -p /app/logs && chown appuser:appgroup /app/logs

# エラー:Error: listen EACCES: permission denied 0.0.0.0:80
# 原因:非 root は低ポートをバインドできない
# 修正 1:3000 をリッスンしポートマッピング
EXPOSE 3000
# 修正 2:Capability 追加
docker run --cap-add=NET_BIND_SERVICE myapp

Q2: データボリュームマウント後に権限が合わない

最頻出の問題。3 つの解決策:

方法 1:ホストで UID/GID を事前設定(推奨)

# ホストで 5000:5000 に設定(コンテナユーザー UID と一致)
sudo chown -R 5000:5000 /data
docker run -v /data:/app/data myapp

方法 2:名前付きボリュームで Docker に権限管理

docker volume create --opt o=uid=5000,gid=5000 appdata
docker run -v appdata:/app/data myapp

Q3: root が本当に必要な場面は?ほぼない

「root が必要」と思われがちな場面にも代替があります:

シナリオroot 不要の方法
80/443 ポートバインドNET_BIND_SERVICE、または高ポート + LB マッピング
システムパッケージインストールDockerfile の USER にインストール。実行時インストールは避ける
システム設定変更環境変数や設定ファイルで注入。実行時変更しない
Docker Socket アクセス極めて危険!必要なら Docker API や K8s API を検討

唯一妥当だった root ケース:レガシー DB 移行ツールがベンダー都合で root 必須で、コード変更不可——使い捨ての隔離コンテナで実行し、完了後即削除。長期稼働は避けました。

Q4: サードパーティイメージが root の場合

優先順位

  1. 公式の非 root 版を探す(-rootless-nonroot タグ)
  2. --user で上書き
docker run --user=65534:65534 third-party-image  # 65534 は nobody
  1. 元イメージをベースに Dockerfile で USER を追加
FROM third-party-image:latest
RUN adduser -D -u 5000 appuser
USER appuser
  1. メンテナに非 root 版を依頼(コミュニティへの貢献!)

結論

核心は一言:コンテナを root で動かす=ハッカーにバックドアを残す

要点のおさらい:

  • コンテナ脱出は理論ではない——CVE-2024-21626、特権コンテナ mount は実際の攻撃経路
  • Dockerfile に USER、実行時に —user——コストは低く、効果は大きい
  • Capabilities で権限を精密制御、drop all + 必要分追加がベストプラクティス
  • 読み取り専用 FS、no-new-privileges、AppArmor の組み合わせで多層防御
  • 76% のイメージに脆弱性——定期スキャンは必須

今日から 3 つ:

  1. Dockerfile を確認——USER がなければ追加
  2. 本番環境を監査——--privileged や root 実行のコンテナを特定し、変更可能なものは即修正
  3. イメージスキャンを CI/CD に組み込む——セキュリティチェックを自動化

セキュリティは一度きりではなく継続プロセス。まず root から非 root へ——その一歩で、すでに大多数より前に進めます。突破されてから後悔する前に——知人の会社の教訓は、そこにあります。

FAQ

なぜ root で Docker コンテナを実行してはいけないのですか?
コンテナ内の root(UID 0)とホストの root(UID 0)は同一ユーザーです。UID namespace はデフォルトで無効です。カーネル脆弱性や設定ミスで namespace が効かなくなると、コンテナ内 root プロセスはホスト上でも root 権限を持ちます。

CVE-2024-21626 などのコンテナ脱出脆弱性が示すように、コンテナ隔離は絶対安全ではありません。Docker Hub 上のイメージの 76% に脆弱性があり、67% は高危険度です。非 root ユーザーで実行すれば、コンテナのセキュリティリスクを 80% 低減できます。
Dockerfile で非 root ユーザーを設定するには?
専用ユーザーとグループを作成し(UID/GID を指定、例:5000)、COPY --chown=appuser:appgroup でファイル所有者を設定、USER 命令はインストールコマンドの後・起動コマンドの前に置きます。

root 権限が必要な操作はすべて USER の前に実行します。固定 UID/GID を指定すれば、データボリュームマウント時の権限問題を避けられます。
非 root ユーザーが 80/443 ポートをバインドできない場合は?
2 つの方法があります:

1) 高ポート番号を使う(推奨):
• アプリは 3000 や 8080 をリッスン
• Nginx やロードバランサでリバースプロキシ
• ポートマッピングでホスト 80 をコンテナ 3000 に転送

2) NET_BIND_SERVICE Capability を使う:
• 非 root でも低ポートをバインド可能
• root で 80/443 を直接リッスンするのを避けられる
Capabilities とは?最小権限をどう設定する?
Capabilities は root のスーパー権限を 40 以上の独立した「能力」に分割したもので、プロセスに必要なものだけ付与できます。

ベストプラクティス:
• まず --cap-drop=ALL で全 Capabilities を削除
• 必要に応じて追加(例:ポートバインド用 NET_BIND_SERVICE)
• SYS_ADMIN、NET_ADMIN、SYS_MODULE など危険な Capabilities は絶対に付与しない

ほとんどの業務アプリは drop all だけで動き、必要なら NET_BIND_SERVICE を 1 つ足す程度です。
本番環境ではどのセキュリティパラメータを設定すべき?
本番レベルのセキュリティ設定の組み合わせ:
• --user=5000:5000 で非 root ユーザー
• --read-only で読み取り専用ファイルシステム + --tmpfs で一時ディレクトリをマウント
• --security-opt=no-new-privileges で権限昇格を禁止
• --cap-drop=ALL で全 Capabilities を削除し、必要分だけ追加
• AppArmor/SELinux による強制アクセス制御を有効化

K8s ユーザーは Pod の SecurityContext で同様のポリシーを設定できます。
データボリュームマウント後に権限が合わない場合は?
3 つの解決策:

1) ホスト側で UID/GID を事前設定(推奨):
• sudo chown -R 5000:5000 /data
• コンテナユーザーの UID と一致させる

2) 名前付きボリュームで Docker に権限管理を任せる:
• docker volume create --opt o=uid=5000,gid=5000 appdata

3) Dockerfile でアプリユーザー用ディレクトリを作成して権限付与
アプリに必要な Capabilities をどう判断する?
3 つの方法:

1) 試行錯誤:
• まず --cap-drop=ALL で実行し、エラーを確認
• エラー内容に応じて追加

2) capsh ツールで分析:
• docker run --rm -it ubuntu capsh --print で現在の Capabilities を確認

3) 一般的なアプリ要件を参照:
• Web アプリ(高ポート):特別な権限不要
• Web アプリ(低ポート):NET_BIND_SERVICE が必要
• VPN/ネットワークツール:NET_ADMIN が必要

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

関連記事

コメント

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