80 番ポートで Permission Denied - Capability 設定
記事に書く内容
Docker コンテナで 80 番ポートをバインドするサーバープログラムを実行すると listen tcp :80: bind: permission denied
というエラーが起きたので、その理由と対処方法を書き残します。
原因
サーバープログラムを非rootユーザーで実行する Alpine ベースのコンテナを立ち上げたとき Permission Denied が表示されていました。
どうやら well-known ポートをバインドするには非rootユーザーでは権限が足りないようです。
FROM alpine
WORKDIR /app
COPY ./serve /app/serve
# 非ルートユーザー appuser を追加する
RUN adduser -u 1000 -D -H appuser \
&& chown appuser:appuser /app/serve
USER appuser
# 80 番ポートで実行するサーバープログラム
CMD ["/app/serve"]
docker image build -t serve .
docker container run --rm -p 80:80 serve
対処方法
rootユーザーで実行してしまえばエラーは解消しますが、なるべく少ない権限で解決するために Capability という Linux のセキュリティ機能を使ってポートをバインディングできる権限を追加することにしました。
Capability を簡単に説明すると、ルートユーザーの権限を細かく分けて割り当てる機能です。より詳しい内容は man コマンドで確認できます。
man capabilities
Capability はファイルに直接割り当てることができるので、今回は CAP_NET_BIND_SERVICE
というポートバインディング権限を与える Capability を /app/serve
プログラムにセットすることにしました。setcap
コマンドで設定できます。
setcap 'cap_net_bind_service=+ep' /app/serve
Alpine Linux で setcap
コマンドを実行するには libcap パッケージを追加します。
最終的に Dockerfile は以下のようになりました。
FROM alpine
WORKDIR /app
COPY ./serve /app/serve
+ # serve ファイルにポートバインディングを許可する
+ # https://jessicadeen.com/posts/2020/how-to-solve-the-listen-tcp-80-bind-permission-denied-error-in-docker/
+ RUN apk add libcap \
+ && setcap 'cap_net_bind_service=+ep' /app/serve
# 非ルートユーザー appuser を追加する
RUN adduser -u 1000 -D -H appuser \
&& chown appuser:appuser /app/serve
USER appuser
# 80 番ポートで実行するサーバープログラム
CMD ["/app/serve"]
参考
How to solve the "listen tcp :80: bind: permission denied" error in Docker
コンテナセキュリティ コンテナ化されたアプリケーションを保護する要素技術
Discussion