初心者がDocker ComposeでFlask+uWSGI+NginxをWindows上で完璧に動かせるように

# はじめに
同じことを解説している記事やサイトは他にもいくつもありましたが、最初から最後まで動くものが（私の環境では）なかったので、自分用にもまとめました。
私はWindows上でDocker Desktopを使って動かしています。

# この記事の流れ
1. DockerとDocker Composeを入れてコンテナを作成してアタッチ（簡単に）
2. コンテナの中にNginxを入れる
2. uWSGIをインストールする
2. Flaskをインストールする
2. NginxとかuWSGIがちゃんと動くようにいろいろセッティング

という流れです。

最後は以下のコマンド

```
docker-compose up -d
```
を打つだけで環境が作れるようにします。

# Dockerでのコンテナづくり
Dockerの解説は、[さくらのナレッジの解説](https://knowledge.sakura.ad.jp/13265/)がわかりやすくておすすめです。

さて、最初に以下のようなディレクトリを作ります。
```
qiita/
　 ├ docker-compose.yml
　 ├ Dockerfile
　 └ requirements.txt
```
この3つのファイルはそれぞれ以下の通りです。
```docker-compose.yml
version: '3'
services:
  main:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: my_flask_Nginx_uWSGI
    volumes:
      - ./:/workspace
    stdin_open: true
    tty: true
    ports:
    - "80:80"
```
```Dockerfile:Dockerfile
FROM python:3.8

RUN apt update
COPY ./requirements.txt ./config/requirements.txt
RUN pip install --upgrade pip
RUN pip install -r config/requirements.txt
```
```requirements.txt
pandas
```
※pandasはとりあえず書いただけです。
はい、そしたら適当なコンソールで先ほどのフォルダに入って、

```
docker-compose up -d
```
と打ちましょう。

うまくコンテナができたでしょうか？

次にコンテナに入ります。
VSCodeを使っている方は、「Attach Shell」で入りましょう。
そうでない方はコンソールで、
```
docker-compose exec main bash
```
を打って入りましょう。
今作られたコンテナの中に「workspace」というオリジナルの自作ディレクトリがありますが、最初に用意したファイル達がこの中に格納されています。

# コンテナの中にNginxを入れる
コンテナの中で、
```
apt-get install -y nginx
```
を打ちましょう。
なんと、これだけでNginxが入りました。

# uWSGIをインストールする
コンテナの中で、
```
pip install uwsgi
```
を打ちましょう。
なんと、これだけでuWSGIが入りました。

# Flaskをインストールする
コンテナの中で、
```
pip install flask
```
を打ちましょう。
なんと、これだけでFlaskが入りました。

# NginxとかuWSGIがちゃんと動くようにいろいろセッティング
さて、ディレクトリ構成をちょっと増やします。
```
qiita/
　 ├ myapp/
 　│   └ app.py
　 ├ docker-compose.yml
　 ├ Dockerfile
　 └ requirements.txt
```
「myapp」というフォルダを作って、その中に「app.py」というファイルを入れました。
「app.py」は、

```app.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello world Flask"
    
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80, debug=True)
```
です。Flaskの文法ですね。
それでは試しに

```
cd /workspace/myapp
python app.py
```
と打ってみましょう。
なにやらごちゃごちゃと出てきたと思います。

そしたら適当なブラウザで
```
http://127.0.0.1:80
```
にアクセスしてみましょう。
以下のように表示されたらちゃんとFlaskとDockerが動いているので、ひとまず一段階の成功です。

![80.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451017/7848ca52-2ee3-1fe8-d200-c71e1d093d2f.png)

確認したら、Ctrl+CでFlaskを止めましょう。

さて次にNginxが動くかを確認します。

```
service nginx start
```
というコマンドを打って、また同じ場所にアクセス（更新）してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451017/bdc161b0-be53-f54b-fea7-ca1400c337f5.png)
これがうまく表示されていれば成功です。

成功したら

```
service nginx stop
```
で停止しましょう。
※nginx failed! と出たときは、もう一回コマンド打つと停止できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451017/b94b88cf-2c3c-9cee-ab7f-5bf82257aa0b.png)

停止できましたね。

さて、ここからちょっと面倒な作業をします。
※後で全部自動化します。今は勉強のため手動でやってますので悪しからず...

コンテナのディレクトリの「/etc/nginx/」という場所に「nginx.conf」というファイルがあると思います。
すなわち、「/etc/nginx/nginx.conf」。

これを開くとちょっと長いテキストの中に

```/etc/nginx/nginx.conf
##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
```
という部分があると思いますがこれを、

```/etc/nginx/nginx.conf
##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
```
と書き換えます。

よく分かりませんが、「/etc/nginx/sites-enabled/*.conf」をインクルードして下さいって事が書いてありそうですね？

では次に、その「/etc/nginx/sites-available」というディレクトリの中に「app_nginx.conf」というファイルを作ります。
読み込ませたいファイルです。

```/etc/nginx/sites-available/app_nginx.conf
# Virtual Host configuration for example.com
server {
        listen 80;
        server_name example.com;        
        location / {
          include uwsgi_params;
          uwsgi_pass unix:/tmp/uwsgi.sock;
        }
}
```

とりあえずポート番号80番でlistenしててねってことが書いてありそうですね。
次に、シンボリックリンクの設定ということをします。
深く考えず以下のコマンドを打ちましょう。

```
cd /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/app_nginx.conf app_nginx.conf
```

さてさて、次はuWSGIの設定です。
「uwsgi.ini」というファイルを、最初のディレクトリ直下に作ります。

```
qiita/
　 ├ myapp/
 　│   └ app.py
　 ├ docker-compose.yml
　 ├ Dockerfile
　 ├ requirements.txt
　 └ uwsgi.ini
```

```uwsgi.ini
[uwsgi]
base = /workspace/myapp
module = app
pythonpath = %(base)
callable = app
socket = /tmp/uwsgi.sock
chmod-socket = 666
master = true
threads = 1
processes = 1
vacuum = true
die-on-term = true
wsgi-file = /workspace/myapp/app.py
daemonize = /var/log/uwsgi/%n.log
pidfile = /tmp/flask_app.pid
```
下から2行目で、何やらログファイルに関する記述がありますね。
現時点ではこのディレクトリが存在しないので、次に「/var/log/uwsgi」というディレクトリを作ります。

```
mkdir /var/log/uwsgi
```

いよいよ大詰めです。
以下のコマンドを打つことで、uWSGIをデーモンとして起動してみましょう。

```
cd /workspace
uwsgi --ini uwsgi.ini
```
最後に、もう一回Nginxを起動しましょう！
```
service nginx start
```
![80.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451017/06a2b1e5-5e77-92df-cbd1-2a92b4a1e715.png)


...どうでしたか？
うまくHello Worldできたでしょうか？

ここまでできれば、後はFlaskを使って色々できますね！

# docker-composeコマンド一発で動き出すための準備
さて、以下のコマンド
```
docker-compose up -d
```
によって、今まで手動で作ってきた環境が一発で作れるようにしていきます。

最終的なディレクトリ構造は、

```
qiita/
　 ├ myapp/
 　│   └ app.py
　 ├ app_nginx.conf
　 ├ docker-compose.yml
　 ├ Dockerfile
　 ├ nginx.conf
　 ├ requirements.txt
　 └ uwsgi.ini
```
になるようにファイルを配置して下さい。

まず、今まで打ってきたコマンドをDockerfileとrequirements.txtに追加していきましょう。
Dockerfileは以下の通りです。

```Dockerfile:Dockerfile
FROM python:3.8

RUN apt update
COPY ./requirements.txt ./config/requirements.txt
RUN pip install --upgrade pip
RUN pip install -r config/requirements.txt

### ここから下が新たに追加した部分
RUN apt-get install -y nginx 
RUN mkdir /var/log/uwsgi
COPY ./app_nginx.conf /etc/nginx/sites-enabled/app_nginx.conf
COPY ./nginx.conf /etc/nginx/nginx.conf
```

コマンドをDockerfileの形式で書いているだけですね。
（効率的な書き方してなくてすみません）
COPYコマンドは、手動でやったNginxの設定ファイルの書き換えに相当します。
書き換えるのが面倒なので、あらかじめ用意したファイルのコピーによって対応しているということです。

それらのファイルは、今まで見てきたファイルと全く同じで、
```app_nginx.conf
server {
        listen 80;
        server_name example.com;        
        location / {
          include uwsgi_params;
          uwsgi_pass unix:/tmp/uwsgi.sock;
        }
}
```
と、
```nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	types_hash_max_size 2048;
	# server_tokens off;

	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*.conf;
}

```
です。コピペしましょう。

requirements.txtは、
```requirements.txt
flask
uwsgi
```
で大丈夫です。
その他のファイルは、今まで書いてきたまんまで大丈夫です。

さて、配置が終わったら、
```
docker-compose up -d
```
で、コンテナを作ってみましょう！
無事作れたら、
```
docker-compose exec main bash
```
でコンテナに入って、
```
cd /workspace
uwsgi --ini uwsgi.ini
service nginx start
```
と、uWSGIとNginxを起動できます！

お疲れ様でした！！

今回のソースコードは[GitHubに置きました](https://github.com/nouyakko/Flask_uWSGI_Nginx)。
良ければご参考にどうぞ。

# 起動や停止などのコマンド
```:uWSGIの起動
uwsgi --ini uwsgi.ini
```

```:uWSGIの停止
uwsgi --stop /tmp/flask_app.pid 
```

```:uWSGIの再起動
uwsgi --reload /tmp/flask_app.pid 
```

```:Nginxの起動
service nginx start
```

```:Nginxの停止
service nginx stop
```

```:Nginxの再起動
service nginx restart
```

# 名前を書き換えてもよい部分（おまけ）
一番上のディレクトリの名前は現在 「qiita」となっていますが、これは勿論好きな名前に設定しても大丈夫です。
```
qiita/
　 ├ myapp/
 　│   └ app.py
　 ├ app_nginx.conf
　 ├ docker-compose.yml
　 ├ Dockerfile
　 ├ nginx.conf
　 ├ requirements.txt
　 └ uwsgi.ini
```
「uwsgi.ini」も、「hogehoge.ini」とかにしても問題ありません。
「myapp」というディレクトリ名や、「app.py」を変更したい場合は、「uwsgi.ini」内の記述をそれに応じて書き換える必要があります。

例えば、「myapp」ディレクトリを「ourapp」に、「app.py」を「hogehoge.py」にしたときは、

```uwsgi.ini
[uwsgi]
base = /workspace/ourapp
module = hogehoge
pythonpath = %(base)
callable = app
socket = /tmp/uwsgi.sock
chmod-socket = 666
master = true
threads = 1
processes = 1
vacuum = true
die-on-term = true
wsgi-file = /workspace/ourapp/hogehoge.py
daemonize = /var/log/uwsgi/%n.log
pidfile = /tmp/flask_app.pid
```
として下さい。
「callable = app」となっていますが、このappは
```app.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello world Flask"
    
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80, debug=True)
```
の中の
app = Flask(\_\_name\_\_)
という部分のappです。

また、クライアントを待機しているポートを8888にしていますが、ここも書き換え可能です。
例えばポートを50000番にしたい場合はdocker-compose.ymlを
```docker-compose.yml
version: '3'
services:
  main:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: flask_Nginx_uWSGI_sample
    volumes:
      - ./:/workspace
    stdin_open: true
    tty: true
    ports:
    - "127.0.0.1:50000:80"
```
と書き換えましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451017/a426c2d8-7277-7a83-ccd3-52d3b6d40f82.png)


# 参考サイト
https://serip39.hatenablog.com/entry/2020/07/06/070000
https://www.ravness.com/posts/flasknginx