Help us understand the problem. What is going on with this article?

Python3 の Docker コンテナで PermissionError: [Errno 13] Permission denied on Docker

Python3 のビルトイン Web サーバの内部ポートを 80 番にすると Docker コンテナが起動しない

Python3 のビルトインサーバのコンテナを 80 番ポート起動すると、「PermissionError: [Errno 13] Permission denied」エラーで起動しない。

TL; DR (今北産業)

  1. python ユーザーなどの非ルート・ユーザーでサーバーを起動していませんか?
  2. 対症療法なら、とりま USER root にする。
    Privileged で起動されない絶対の自信があるなら root のままでおk
  3. 結局 8080 にするのがらく

TS; DR コンテナが故に root でも大丈夫な理由(条件付き)のコマケーこと

非ルート・ユーザーで 80 番ポートが使えないのは Docker の制限ではなく Linux/UNIX 系 OS の仕様制限です。特権ポート番号である 80 番ポートは root ユーザーでないと開けられません。

一番簡単な方法は、USER を明示的に root に指定することです。Dockerfile の下部に USER root ディレクティブを指定すると 80 番ポートが利用可能になります。

セキュリティの観点から非 root ユーザでコンテナを起動する必要がある場合は、ipchainsiptables などで 80 番ポートを 8080 番ポートなどに転送する必要があります。その場合の設定は、コンテナのベースとなる OS に依存します。

特権ポート番号とはPrivileged Ports1(特権ポート)と呼ばれる 0〜1023 の範囲のポート番号のことです。

ウェルノウン・ポート2とも呼ばれます。この範囲のポートを使う場合、実行ユーザーが root でないと Linux/Unix 系 OS の仕様でポートを開けられない3ため「PermissionError: [Errno 13] Permission denied」の権限エラーが出ます。逆に言えば、8000 番や 8080 番ポートなら問題ないと言うことです。

実は、Python3 のビルトイン Web サーバーで Docker のコンテナを作ろうと思い、以下の Qiita 記事を読んで写経をしたところ、ストレートに動作しました。

このコンテナに限らず、Python3 の Web サーバーのデフォルトポートは 8000 番です。(Python v3.7.4rc2 現在)

そのため、今回のこの Python3 コンテナ側の 8000 ポートに http://<コンテナ名>:8000/ で他のコンテナから http アクセスできます。

次にホスト側、つまり Docker ネットワークの外からコンテナにアクセスしたい場合、コンテナを起動時に -p もしくは --publish オプションでポートを公開します。(--ports ではないので注意 → 私)

具体的には docker run -p 8080:8000 -d <YOUR DOCKER IMAGE> でコンテナを起動、もしくは docker-compose.yml の場合は ports: "8080:8000"4 を記載して起動すると、コンテナのポート 8000 番とホストマシンのポート 8080 番をつなげてくれます。(8080 ポートをコンテナの 8000 ポートへポートフォワード)

これにより他のパソコンからも http://<ホスト OS の IP>:8080/ でコンテナにアクセスできます。(もちろん cURLでも)

Docker ネットワーク内では 80 番ポートでコンテナ間通信したかった

🐒   先に最終的に取った方法を言うと、80 番ポートでの統一を諦めて 8080 番ポートで統一させることにしました。コンテナごとに(CentOS, Debian, alpine など) OS が異なるため、設定や管理がむしろ煩雑になってしまったからです。

今回目指したかったのは Docker ネットワーク内で完結、つまりホスト含め外部にさらす必要のない構成です。

docker-compose up すると複数コンテナが同じ Docker ネットワーク内で起動します。そして、exec コマンドでメインのコンテナにアクセスすると、コンテナ間で http 通信を行ったのち処理結果だけをコンソール(ターミナル)の標準出力に表示させたいのです。

各々のコンテナは PHP7、Golang、Python3 などで書かれた「目的別のシンプルな機能だけを提供している RESTful なコンテナ」です。メインのコンテナは、各コンテナに http リクエストした結果をとりまとめて結果だけを吐き出すイメージです。

その際、仕様をシンプルにするため各々のコンテナのポートを 80 番に固定することで、http://<コンテナ名>/ だけでお互いのコンテナがアクセスできるようにしたかったのです。(同じ Docker ネットワークにいるコンテナ同士に限る)

しかし、PHP7 のビルトイン Web サーバーのコンテナへは http://<PHPコンテナ名>:80/ で接続できるのに、Python3 のビルトイン Web サーバーの場合、コンテナの内側ポートを 80 番に設定すると「PermissionError: [Errno 13] Permission denied」エラーでコンテナが起動しません。デフォルトの 8000 番ポートや 1024 番以上のポートだと起動します。

✅起動する設定
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=8000)
✅起動する設定
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=1024)
❌起動しない設定
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=80)
❌起動しない設定
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=1023)

起動しなかったコンテナのログをみると「PermissionError: [Errno 13] Permission denied」が発生しています。

起動したコンテナのlogs
$ docker container logs sample_container
Traceback (most recent call last):
  File "/server_start.py", line 3, in <module>
    http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=81)
  File "/usr/local/lib/python3.6/http/server.py", line 1185, in test
    with ServerClass(server_address, HandlerClass) as httpd:
  File "/usr/local/lib/python3.6/socketserver.py", line 453, in __init__
    self.server_bind()
  File "/usr/local/lib/python3.6/http/server.py", line 136, in server_bind
    socketserver.TCPServer.server_bind(self)
  File "/usr/local/lib/python3.6/socketserver.py", line 467, in server_bind
    self.socket.bind(self.server_address)
PermissionError: [Errno 13] Permission denied

Permission?え?権限?」と思い調べてみると、PHP7 のビルトインサーバーは root ユーザーで実行しているのに対し、Python3 のビルトインサーバーは Dockerfile 内で python ユーザーを指定していました。ユーザーを root に変更したところ普通に動きました。

「えー、python って root じゃないと 80 番ポート使えないのー」と思ったのですが、どうやらプログラム言語の仕様制限ではなく OS の仕様制限で、単純に実行ユーザーが異なっていたのが原因でした。

となると、取る方法は2つです。

  1. root ユーザーでも 80 番ポートを使えるように iptablesipchains でコンテナ内でポートフォワードする。
  2. root ユーザーでコンテナを起動させる。

しかし「root ユーザーで起動させるとセキュリティ的に大丈夫なのか」という心配も出てきました。

You don't need to add a new username and change everything to adapt it to that unless there's a clear reason to do so. But as the root user inside the container only has access to that container, there's is nothing that user can risk, unless you mount a privileged directory or something similar.

(「Permission error on port 80」| Issue #62 | uwsgi-nginx-flask-docker @ GitHub より)5

【筆者訳】(一部加筆)
明確な理由がない限り、新しいユーザーを作成し、それらが動作するように無理をして設定を変える必要はありません。root ユーザーがアクセスできるのは、そのコンテナ内だけだからです。しかし docker.sock などのホスト側にアクセスできるようなセンシティブなファイルやディレクトリをマウントしている場合は、その限りではありません。

つまり「--privileged で起動させない絶対の自信があるなら、root でもいい」と言うことです。確かに、「コンテナ内で完結できる」という Docker のメリットを考えると一理あります。むしろ、そのため(ホストと隔離させるサンドボックス的)にコンテナを使っているわけですから。

筆者の場合、今のところ、すべてのコンテナは自家製でローカル利用が大半なので root でも問題ないと思います。しかし、自分を信用していないし、色々考えるのも面倒なのでポカよけの意味も込めて 8080 ポートで統一させることにしました。


  1. Privileged Ports」@ W3.org 

  2. ウェルノウンポート番号(0–1023)」| TCPやUDPにおけるポート番号の一覧 @ Wikipedia 

  3. PermissionError: [Errno 13] Permission denied Flask.run()」@ StackOverflow 

  4. ports | Docker Compose - docker-compose.yml リファレンス(日本語)」 @ Qiita 

  5. Permission error on port 80」| Issue #62 | uwsgi-nginx-flask-docker @ GitHub 

KEINOS
A Japanese made in Mexico with Mexican quality ;-) Who monkey around the jungle of codes. 記事の日本語がおかしかったら遠慮なく編集リクください。また、記事に「LGTM」が付くたび、なるべく何かしら加筆・修正してブラッシュアップしています。基本的に変更通知はお送りしません。
https://blog.keinos.com/
qiitadon
Qiitadon(β)から生まれた Qiita ユーザー・コミュニティです。
https://qiitadon.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away