Djangoの開発環境をdocker-composeを利用してコンテナで起動し、runserverコマンドでホスト側からアクセスする方法

# やってみたこと
* djangoチュートリアルで制作したpolls appを、dockerで立ち上げてみようとふと思ったのでやってみました。
    * pollsアプリは、`python3 manage.py runserver 8000`によって、`localhost:8000`で起動します。それをdockerコンテナからやってみようという話です。

## 前提
* 今回は敢えて、`Dockerfile`と`docker-compose.yml`の両方を最低限レベルで使ってdjangoのアプリケーションを構築します。これらのファイルを使った方が、今後汎用的に覚えられる（いじってみれる）だろうという期待も込めて(笑)
* dockerコンテナの中から`python manage.py runserver`で開発サーバを起動してみる、という実験をします。実際にはおそらくあまりこんな使い道はないのでは、、、その辺はお気にせずお読みください笑
    * 開発用サーバーは、Django付属のWebサーバーです。開発中に便利なツールであって、運用環境では使用しないことが推奨されています。
* djangoチュートリアルでpollsアプリケーションを制作したmysiteプロジェクトを、以下ディレクトリ構造で配置。

```
.
├── docker
│   ├── docker-compose.yml
│   └── Dockerfile
└── web
    ├── mysite
    │   ├── mysite
    │   ├── polls
    │   ├── db.sqlite3
    │   └── manage.py
    └── requirements.txt
```

* 詳細ディレクトリ構造はこちら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/9beb8e37-5178-fdf1-430d-e59bea12ac0d.png)

* mysiteとpollsディレクトリ以下は、チュートリアルにも詳細な記載があります。何がどう関係してくるのか把握したい方はぜひ。
    * [はじめての Django アプリ作成、その 1](https://docs.djangoproject.com/ja/4.1/intro/tutorial01/)
* チュートリアル作成における記事はこちら：
    * [【初めてのDjango】インストール手順：MVTモデル、ルーティング、DBマイグレーション、views.py、テンプレート、名前空間について](https://qiita.com/Ryo-0131/items/ded7590e372f2d8e796c)

# 手順
* docker関連ファイルの準備
* イメージ作成・コンテナ起動
* コンテナの中に入って`manage.py`を確認
* Dockerコンテナから開発サーバを起動

## docker関連ファイルの準備
* まずは`Dockerfile`に以下のように記載します。

```Dockerfile
# python:3.8の公式 image をベースの image として設定
FROM python:3.8

# 作業ディレクトリの作成
RUN mkdir /mysite

# 作業ディレクトリの設定（以後の RUN は WORKDIR で実行）
WORKDIR /mysite

# カレントディレクトリにある資産をコンテナ上の指定のディレクトリにコピーする
ADD web /mysite

# pipでrequirements.txtに指定されているパッケージを追加する
RUN pip install -r requirements.txt

# 起動(今回はなし)
# CMD python3 manage.py runserver 0.0.0.0:8000
```

* 今回はpython3.8のイメージをベースとしています。マイナーバージョンを指定しないとビルド時点の最新python stable versionがインストールされます。
    * ちなみに、何も指定しないと以下のエラーが出てきます。

```terminal
failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition: no build stage in current context
```

* `requirements.txt`には以下のように記載します。ビルド時にインストールしたいライブラリ(python package)を記述します。
    * こちらもdjangoアプリで使う必要最低限のものだけ記述します。

```text
Django==4.1
requests
```

* そして`docker-compose.yml`には以下のように記載します。
    * 今回は必要最低限のものしか記載していません。

```yml
version: '3'
services:
  mysite:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile # Dockerfileのディレクトリを指定します
    volumes:
      - '../web:/mysite'
    ports:
      - "8000:8000" # 左側が外部からのアクセス時のポート番号:右側がdockerコンテナからアクセスする時のポート番号。ホストの8000番からコンテナの8000番へポートフォワードしています
    container_name: docker_mysite
    tty: true　 # ポート待受とかしていないコンテナの場合、trueに設定しておくとコンテナが起動し続けます
    working_dir: '/mysite'
```


## イメージ作成・コンテナ起動
* 以下のコマンドでイメージ作成＆コンテナ起動します。

```terminal
$ docker compose up -d
```

* `-d`オプションはデーモンモードで起動し、プロセスをバックグラウンドで動作させることができます。
    * アクセスログの内容やエラーなどを確認したい場合は、`-d`を外して実行します。
* 起動しているコンテナを以下のコマンドで確認できます。UPなので起動していますね。


```terminal
$ docker ps
CONTAINER ID   IMAGE           COMMAND     CREATED              STATUS              PORTS                    NAMES
7f730b3c105f   docker-mysite   "python3"   About a minute ago   Up About a minute   0.0.0.0:8000->8000/tcp   docker_mysite
```

* `docker-compose.yml`で指定した8000ポートにつながっていることがわかります。
* `$ docker compose ps`でもコンテナがRUNNINGかどうか分かります。
* また、以下のコマンドにより、`requirement.txt`に記述したモジュールがインストールされているかどうか確認できます。


```terminal
$ docker exec -it docker_mysite pip list
Package            Version
------------------ ---------
asgiref            3.6.0
backports.zoneinfo 0.2.1
certifi            2022.12.7
charset-normalizer 3.1.0
Django             4.1
idna               3.4
pip                22.0.4
requests           2.28.2
setuptools         57.5.0
sqlparse           0.4.3
urllib3            1.26.14
wheel              0.38.4
```


## コンテナの中に入って`manage.py`を確認
* 以下のコマンドで`docker_mysite`コンテナに入ります。

```terminal
$ docker exec -it docker_mysite bash
```

* CONTAINER IDでも入れるので、以下のコマンドでもOKです。

```terminal
$ docker exec -it 7f730b3c105f bash
```

* まだコンテナが立ち上がっていない場合は、以下が返ってきます。その場合は、`docker compose up -d`を行いましょう。

```
Error response from daemon: Container xxxxxxxｘ is not running
```

* また、そもそもコンテナが存在していない時は、以下のメッセージが返ってきます。こちらの場合も、`docker-compose.yml`があるところで`docker compose up -d`を行いましょう。

```
Error: No such container: xxxxxxxｘ
```

* コンテナの中に入れたら、以下のようなプロンプトが出てきます。

```
root@7f730b3c105f:/mysite#
```
 
* `ls -a`で直下を確認します。ローカル環境で作成したディレクトリがCOPYされていることがわかります。

```terminal
root@7f730b3c105f:/mysite# ls -a
.  ..  .DS_Store  mysite  requirements.txt
```

* `manage.py`があるディレクトリに移動します。

```terminal
$ cd mysite
```

* ここで`ls -a`をすれば同じくローカルと同様の状況が確認できるはずです。

## Dockerコンテナから開発サーバを起動
* 以下のコマンド実施により、8000ポートで立ち上げることができます。

```terminal
root@7f730b3c105f:/mysite# cd mysite
root@7f730b3c105f:/mysite/mysite# python3 manage.py runserver 0:8000
```

* ポイントは`0`です。以下の参考記事に記載がありますが、0を指定することにより、ホスト側から`localhost:8000`でアクセスが可能になります。`0.0.0.0`でも同じです。
    * 参考：[Djangoの開発サーバーをDockerコンテナで起動し、ホスト側からアクセスする方法](https://zukucode.com/2020/08/django-server-docker.html)
* ローカル環境から普通に開発サーバを起動する場合、`python manage.py runserver`コマンドで可能ですので、例えば8000ポートで開きたい場合は`python3 manage.py runserver 8000`とすれば以下のように出力されて開発サーバーが立ち上がると思います。

```terminal
Django version 4.1.3, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[09/Mar/2023 08:51:01] "GET / HTTP/1.1" 200 145
```

* ただしdockerコンテナでDjangoの開発サーバーを起動する場合は、ホスト側からアクセスする必要があるので、`0:8000`のようになります。
* また、当然ですが、`docker-compose.yml`で指定したポートでコンテナが立ち上がるので、`python3 manage.py runserver 0:8000`の8000を8001とかにしてアクセスしてもつながりません。逆に、`docker-compose.yml`で8001を指定していれば、`python3 manage.py runserver 0:8001`としてあげる必要があります（コンテナが起動している場合は一度downして、書き換えてからupします）。
* ということで、`python3 manage.py runserver 0:8000`を実行すると以下のように返ってきます。HTTPステータスコードも200なので正常です。

```terminal
Django version 4.1, using settings 'mysite.settings'
Starting development server at http://0:8000/
Quit the server with CONTROL-C.
[09/Mar/2023 08:55:32] "GET / HTTP/1.1" 200 145
```

* dockerコンテナが起動してある状態で`http://localhost:8000/`にアクセスすると無事に繋がったことがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/54d1b1aa-2733-b860-02a9-908cd7cc0b88.png)

* 「dockerからじゃなくて、普通にローカルでいじれば良いのでは？ｗ」って感じですが、ふと「これってできるのかな？」と思ったのでやってみました(笑)。せっかく作ったので、これを色々いじってみる実験もしてみます。

## 参考記事
* [Docker で作る postgres 環境](https://crudzoo.com/blog/docker-postgres)
* [DockerとDocker ComposeでPythonの実行環境を作成する](https://zuma-lab.com/posts/docker-python-settings)
* [DokerでDjangoプロジェクトを作って、Dockerで動かす。](https://qiita.com/tubutubu_mustard/items/9dea37856042f91d700e)
* [Docker ComposeでDjangoの開発環境を構築した](https://hodalog.com/run-django-app-using-docker-compose/)