
Falco Feedsは、オープンソースに焦点を当てた企業に、新しい脅威が発見されると継続的に更新される専門家が作成したルールにアクセスできるようにすることで、Falcoの力を拡大します。

本文の内容は、2026年2月10日にMatt Kim, Víctor Jiménez Cerradaが投稿したブログ(https://www.sysdig.com/blog/how-to-run-rootless-containers)を元に日本語に翻訳・再構成した内容となっております。
rootless コンテナ、つまり非特権コンテナを使用することは、コンテナセキュリティにおける最も効果的なベストプラクティスの一つです。
実際のところ、大半の場合において、プロセスを root として実行する必要はありません。ベアメタルサーバーでは、ほとんどのサービスは root として実行されていません。それではなぜ、コンテナの 80% が root として実行されているのでしょうか。
すべての悪の根源(しゃれではありますが)は、Docker のデフォルト設定にあります。特に指定しない限り、Docker はコンテナ内のプロセスを UID 0、つまり root ユーザーとして実行します。開発者はこれに慣れてしまい、自身のイメージの非特権バージョンを提供しなくなり、他のコンテナランタイムも互換性を維持するためにこの挙動を維持しました。
幸いなことに、非特権での実行は時間の経過とともに容易になってきました。
本記事では、次のことを扱います。
- root として実行することのリスクを解説します。
- 非特権ユーザーとして実行できるようにイメージを適応させる方法を紹介します。
- 特権コンテナを分離するために User Namespaces を活用します。
root として実行することの危険性
要するに、コンテナが侵害された場合、攻撃者にとって状況をより容易にしてしまうことになります。
その場合、攻撃者はコンテナ内で root アクセス、つまり完全な制御権を持つことになります。これにより、攻撃者は次のことが可能になります。
- プロセスを実行し、バイナリを変更します。
- 保護されていると思っていたファイル内の認証情報を検索して、横方向の移動を可能にします。
全体として、以下のブログ記事でわかるように、脆弱性を悪用してホストに侵入する方が簡単です。
そのため、コンテナを rootless として実行することは非常に有効なプラクティスです。。これにより、ワークロードをさらに分離して、コンテナが侵害されたことによる被害を最小限に抑えることができます。
rootless で実行することのトレードオフ
時間の経過とともに容易になってきているとはいえ、コンテナを非特権で実行することは簡単ではありません。
プロセスが非特権で実行できるようにイメージを適応させる必要があり、そのためにはポートの変更やファイル権限の見直しが必要になります。このプロセスについては次のセクションで取り上げます。
また、一部のリソースに対する低レベルアクセスを失うことにもなります。Sysdig Agent のようなセキュリティプローブは、ホスト上で実行されている内容の詳細なコンテキストを取得するためにこのアクセスを必要とします。これらのワークロードを root として実行することは問題ありません。なぜなら、それらはコンテナ全体のごく一部に過ぎず、追加の制御を実装することも容易だからです。
さらに、User Namespaces や Capabilities を使用して、制限された特権でコンテナを実行することも可能です。これらの方法については後述します。ただし、これらの機能にはトレードオフがあり、特に CAP_SYS_ADMIN や CAP_NET_ADMIN のケーパビリティを過度に許可している場合、プロセスは依然としてある程度の特権を持つことになります。このトピックについては、「Falcoでコンテナエスケープの振る舞いを検出する方法」という記事で取り上げました。
イメージを rootlessrootless にする準備をする
理論的には、イメージを非特権で実行できるように準備するのは簡単です。
- 1024 より大きいポートのみを使用してください。
- 非特権ユーザーが必要なものにアクセスできるように、ファイル権限を見直してください。
実際には、これを実装するのは必ずしも容易ではないため、実例を使って学びましょう。コードが公開されており、十分なドキュメントも用意されているため、nginx-unprivileged イメージを使用します。
nginx-unprivileged イメージは、/etc/nginx/conf.d/default.confconfig ファイルにいくつかの変更を加えることで、rootless を実現しています。
# implement changes required to run NGINX as an unprivileged user
RUN sed -i 's,listen 80;,listen 8080;,' /etc/nginx/conf.d/default.conf \
&& sed -i '/user nginx;/d' /etc/nginx/nginx.conf \
&& sed -i 's,\(/var\)\{0\,1\}/run/nginx.pid,/tmp/nginx.pid,' /etc/nginx/nginx.conf \
&& sed -i "/^http {/a \ proxy_temp_path /tmp/proxy_temp;\n client_body_temp_path /tmp/client_temp;\n fastcgi_temp_path /tmp/fastcgi_temp;\n uwsgi_temp_path /tmp/uwsgi_temp;\n scgi_temp_path /tmp/scgi_temp;\n" /etc/nginx/nginx.conf \
&& sed -i 's,PIDFILE=${PIDFILE:-/run/nginx.pid},PIDFILE=${PIDFILE:-/tmp/nginx.pid},' /etc/init.d/nginx \それらを確認しましょう。
まず、 8080ポートがデフォルトとして設定されています どのユーザーも1024以上のポートでリッスンできますが、ポート80でリッスンできるのは特権ユーザーだけです。
sed -i 's,listen 80;,listen 8080;,' /etc/nginx/conf.d/default.confこれで、コンテナランタイムにポートを適切にマップできます。
docker run -p 8080:80 nginx-unprivileged次に、user ディレクティブを削除して、ユーザーに関する変更がないことを確認します。
&& sed -i '/user nginx;/d' /etc/nginx/nginx.conf \さらに、いくつかのフォルダーを「tmp」に変更し、非特権ユーザーからアクセスできるようにします。
&& sed -i 's,\(/var\)\{0\,1\}/run/nginx.pid,/tmp/nginx.pid,' /etc/nginx/nginx.conf \
&& sed -i "/^http {/a \ proxy_temp_path /tmp/proxy_temp;\n client_body_temp_path /tmp/client_temp;\n fastcgi_temp_path /tmp/fastcgi_temp;\n uwsgi_temp_path /tmp/uwsgi_temp;\n scgi_temp_path /tmp/scgi_temp;\n" /etc/nginx/nginx.conf \
&& sed -i 's,PIDFILE=${PIDFILE:-/run/nginx.pid},PIDFILE=${PIDFILE:-/tmp/nginx.pid},' /etc/init.d/nginx \変更されたファイルは、PID ファイルおよび一時ファイルです。
最後に、設定フォルダーおよびキャッシュフォルダーをユーザーが書き込み可能にします。
# nginx user must own the cache and etc directory to write cache and tweak the nginx config
&& chown -R $UID:0 /var/cache/nginx \
&& chmod -R g+w /var/cache/nginx \
&& chown -R $UID:0 /etc/nginx \
&& chmod -R g+w /etc/nginxイメージを非特権で実行できるように変更することは容易だとすでにお気づきかもしれませんが、それはコンテナ内のサービスがその動作に対応できるよう準備されている場合に限られます。NGINX サーバーは高度にパラメータ化されており、ソースコードを変更することなく、設定ファイルからその動作を変更できます。
幸いなことに、ほとんどのソフトウェアはこれらの原則に従っています。重要なのは、/tmp ディレクトリに移動する必要があるファイルの一覧を把握することです。
root 権限に関する追加のセキュリティヒント
イメージを非特権で実行できるように準備することは、最初のステップに過ぎません。いくつかの手順を踏むことで、コンテナのセキュリティをさらに強化できます。
まず、コンテナが侵害された場合でも攻撃者がバイナリを変更できないように、バイナリの所有者を root に設定してください。Dockerfile が次のようになっている場合:
...
WORKDIR $APP_HOME
COPY --chown=app:app app-files/ /app
USER app
ENTRYPOINT /app/my-app-entrypoint.sh
Code language: JavaScript (javascript)バイナリに対する –chown=app:app フラグを削除するか、RUN chown コマンドの使用を避けてください。ユーザーがバイナリを所有する必要はなく、実行権限のみが必要です。
もう一つのヒントとして、可能であれば、読み書きが必要なファイルの許可リストを設定したうえで、コンテナを読み取り専用モードで実行してください。–readonly フラグを使用してコンテナのボリュームを読み取り専用でマウントし、その後 -v を使用して変更可能なファイルを指定します。
$ docker run --read-only -v /tmpnginx-unprivileged の場合、次のように実行します。
$ docker run -d -p 8080:80 --read-only -v $(pwd)/nginx-cache:/var/cache/nginx -v $(pwd)/nginx-pid:/var/run nginx-unprivileged最後に、ビルド段階で apt のようなコマンドを実行する必要がある場合は、マルチステージビルドを使用して root としてビルドし、その後、メインプロセスを通常のユーザーとして実行できます。
# This is the builder stage
FROM gcr.io/distroless/static-debian10 as builder
[…]
RUN apt-get [your apt and build commands here]
[…]
# Final stage, copy required artifacts from builder
FROM gcr.io/distroless/static-debian10 as builder
[…]
COPY --from=builder /file/to/copy
USER myuser
[…]コンテナを安全に保護するためのさらなるヒントについては、当社の「Dockerfile のベストプラクティス トップ 20」記事をご覧ください。
Capabilities と User Namespaces
前述のとおり、一部のコンテナは、ネットワークスタックへ直接アクセスしたり、ptrace を使用したりするなど、特権操作を実行する必要があります。
Capabilities
これを許可する最も安全な方法は、Linux の capabilities を使用することです。capabilities を使うことで、通常のユーザーに特定の特権を付与できます。
Docker では、–cap-add フラグを使用してこれを設定できます。
$ docker run --cap-add=SYS_PTRACE […]Kubernetes では、securityContext 内で Pod の capabilities を定義できます。
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo-4
spec:
containers:
- name: sec-ctx-4
image: gcr.io/google-samples/hello-app:2.0
securityContext:
capabilities:
add: ["NET_ADMIN", "SYS_TIME"]User namespaces
しかし、コンテナイメージを非特権で実行できるように適応させたり、capabilities を使用したりする時間がない場合もあります。
そのような場合には、user namespaces を使用して、コンテナ内で実行されるユーザーと、ホスト上でワークロードを実行するユーザーを抽象化できます。これにより、コンテナ内では root としてプロセスを実行しつつ、ホスト上では通常のユーザーとして実行できます。
user namespaces を使用すると、攻撃者がコンテナから脱出した場合でも、ホスト上で特別な特権を持つことはなく、引き起こせる被害を制限できます。
Docker で user namespaces を有効にするには、/etc/subuid および /etc/subgid ファイルでマッピングを設定する必要があります。これらの手順は他のコンテナランタイムでも同様であり、この例の直後にそれぞれのドキュメントへのリンクがあります。
次の ID を持つユーザーがいるとします。
$ id testuser
uid=1001(testuser) gid=1001(testuser) groups=1001(testuser)/etc/subuid および /etc/subgid の両方に、次のようなエントリを追加することで、user namespace を作成できます。
testuser:231072:65536この namespace は、231072 から始まる 65536 個のユーザー ID を予約します。この範囲内では、すべてのユーザー ID が testuser にマッピングされ、231072 が namespace 内の UID 0 にマッピングされます。
その後、コンテナを実行する際のデフォルトのホストユーザーを指定するために、–userns-remap フラグを付けて dockerd を起動します。
$ dockerd --userns-remap="testuser:testuser"ほとんどのコンテナランタイムは Linux における user namespaces をサポートしており、Podman、CRI-O、runc、containerd などが含まれます。
Kubernetes も Pod に対して user namespaces をサポートしています。Pod の deployment yaml で hostUsers: false を設定することで、この機能を有効にできます。
apiVersion: v1
kind: Pod
metadata:
name: userns
spec:
hostUsers: false
[…]Kubernetes における user namespaces のサポートは引き続き改善されています。たとえば、Kubernetes 1.35 におけるアルファ機能の拡張では、user namespaces がホストのネットワークスタックへのアクセスから分離されています。
まとめ
rootless コンテナを使用することで、コンテナのいずれかが侵害された場合に備えた防御線を構築できます。
非特権での実行は時間の経過とともに容易になっており、rootless が選択できない場合でも、capabilities や user namespaces のようなツールを活用できます。
これを試すのに、これ以上ないほど良いタイミングです。
詳しくは、当社の Container security ホワイトペーパーをご覧ください。