Docker学習の終盤、SSL対応とデプロイ


# はじめに
[『Docker学習、私はこう取り組んだ』](https://qiita.com/gmasa/items/8194fb0c58771cb5b4f4)の続きです。
前回は、お手本サイト[『Dockerizing Django with Postgres, Gunicorn, and Nginx』](https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/)に沿って、Djangoのひな形プロジェクトを作るところまで到達しました。アプリ仕様は画像ファイルのアップロード/表示のみ、内部構成は Django + Postgre + Gunicorn + nginx です。
私のゴールは ConoHa VPS の Ubuntu でDocker化したアプリをデプロイすることなので、SSL対応が欠かせません。お手本サイトにSSL対応記事のリンクが貼られてましたが、私には難しそうでした。他の方法を探したところ https-portal の存在を知り、このdockerイメージを使って、Let's encryptによるSSL対応をすることに決めました。


# 進め方
https-portal を知った当初は、優しそうな雰囲気だけは伝わってきましたが、公式リファレンスを読んでもひな形プロジェクトに何をすればいいのか全く想像できませんでした。ひたすらググり、手を付けられそうなところを見つけて操作し、エラーが出たら対処して、ということを繰り返しながら進みました。


# 表記
前回から引き続き、プロジェクトルート（/home/user/works/hoge-on-docker/）は
```bash
~/*$ 
```
と省略して書きます。


# まずは `https://localhost:○○○○` のアクセス成功を目指す

http ではなく、http**s** です。この辺りの知識は、正直理解しきれていません。概要が一番コンパクトにまとめられているのは [IT用語辞典 e-Words『自己署名証明書』](https://e-words.jp/w/%E8%87%AA%E5%B7%B1%E7%BD%B2%E5%90%8D%E8%A8%BC%E6%98%8E%E6%9B%B8.html) かなと思います。その中でも、**オレオレ証明書** と呼ばれている方式で動かすのが、https-portal を利用する最初のステップになります。


### はじめに掃除をしよう
前回記事の続きであれば、本番用 Dockerコンテナを起動したままだったり、余計なイメージが溜まっているかもしれません。まずは掃除しましょう。

本番コンテナが起動していたらダウン。
```bash
~/*$ docker-compose -f docker-compose.prod.yml down -v
```

イメージリストを確認。
```bash
~/*$ docker image ls
```

リストのうち `<none>` になっているイメージを削除。
```bash
~/*$ docker rmi ***** ***** ・・・・
```
rmiの引数に消したいイメージのIDを書きます。対象のIMAGE IDをダブルクリックして右クリックでコピー、続けてプロンプトで右クリックするとペーストできます。

:::note info
不要イメージ削除はこちらの方がいいかもしれません。
```bash
~/*$ docker image prune
```
:::

### 資料を漁り、とにかく https-portal を動かしてみる。

[Qiita『Dockerでローカル開発環境のhttps化』](https://qiita.com/muk-ai/items/413ae83b0a241495dd34) の方法で動作させることができました。
次のステップとして、同記事内のアドバイス **「Dockerでアプリケーションを動かしている場合は host.docker.internal でなくて直接コンテナに繋いでも動くと思います」** を基に、ひな形プロジェクトに適した docker-compose の書き方を探っていきました。
現時点の最小構成 docker-compose を掲載します。（nginx, web, db は記述順を変えただけで内容変更なし）


```yml: docker-compose.prod.yml
version: '3.8'

services:

  https-portal:
    image: steveltn/https-portal:1
    ports:
      - 80:80
      - 3443:443  # local dev  https://localhost:3443
      # - 443:443  # Production
    depends_on:
      - nginx
    restart: always
    environment:
      STAGE: local
      DOMAINS: 'localhost -> http://nginx:80'   # local dev
      # DOMAINS: '****YourDomain**** -> http://nginx:80'   # Production
    volumes:
      - https-portal-data:/var/lib/https-portal

  nginx:
    build: ./nginx
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
    expose:
      - 80
    depends_on:
      - web

  web:
    build:
      context: ./app
      dockerfile: Dockerfile.prod
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - static_volume:/home/app/web/staticfiles
      - media_volume:/home/app/web/mediafiles
    expose:
      - 8000
    env_file:
      - ./.env.prod
    depends_on:
      - db

  db:
    image: postgres:13.0-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - ./.env.prod.db

volumes:
  postgres_data:
  static_volume:
  media_volume:
  https-portal-data:
```

`STAGE: local` とすることで、https-portal がオレオレ認証書を提供してくれます。

:::note info
私は勘違いして、自分で自己認証局と自己署名証明書を用意した上で、https-portal をビルドしなければならないと思い込んでいました。言葉の意味が分からない状態から調査を始めて、ようやく準備が整いつつあるところまで来て、ふと冷静になりました。**あれ？この作業、 https-portal がやってくれるんじゃない？** と思い、資料を読み直し、実際に動かして、予感が当たった、そんな流れで理解できました。早とちりは時間を浪費しますね。。:worried:
:::

:::note info
https-portal を使っているのに、nginx コンテナを用意するの？
https-portal イメージは nginx を含んでいるので、もう一つ nginx を挟む構成は無駄かもしれません。実際、調査活動をしている中で https-portal 内の nginx でアプリ接続の設定をしている例を見た気がします。商品としてリリースする場合に何が良いのかは分かりませんが、私は以下のような判断で、https-portal + nginx の二段構成としました。
- お手本サイトで作ったひな形の状態を可能な限り残したい。この先、Web開発しないブランク期間が必ず発生するので、作業手順は単純な方がいい。
- 容量は気にしなくていいレベル。 https-portalは 8.58kB (virtual 348MB)、nginxは 2B (virtual 23.4MB)。virtualがイメージサイズ。
    - それにしても 2Bって2バイトでいいんでしょうか？ 認識の仕方が間違っている気もするが、ごく小さいことは確かだと思います。 Docker の理解を深めれば、コンテナ容量の小ささも納得できるのでしょう。深いな。。
:::

### Chromeの設定変更
Chromeが標準設定のままではオレオレ認証サイトを表示してくれないので、設定変更が必要です。[Qiita『Dockerでローカル開発環境のhttps化』](https://qiita.com/muk-ai/items/413ae83b0a241495dd34) で紹介されている方法で対処します。
1. URL欄に `chrome://flags/#allow-insecure-localhost` をコピペして実行。設定画面に飛ぶ。
1. allow-insecure-localhost の右側が disable になっているので、Enabled に変更する。
1. 決定ボタンや更新ボタンはないので、設定画面は閉じて大丈夫。


準備ができたら `https://localhost:3443` にアクセス。
最初は **アクセスできません** の表示が出ますが、しばらくすると `ファイル選択` ボタンの画面が表示されます。
ChromeのURL欄が 『 ⚠ 保護されていない通信 | ~~https~~://localhost:3443 』 となっていればOKです。
ところが、ブラウザに表示された `ファイル選択` ボタンを押下し画像データを選択した後、`submit` ボタンを押すとエラーになります。
```txt: Chrome
Forbidden (403)
CSRF verification failed. Request aborted.

More information is available with DEBUG=True.
```
Django4 から必要になった、CSRF関連のお手当てが足りていませんでした。
settings.py に追記。
```python: settings.py への追記
CSRF_TRUSTED_ORIGINS = ['http://localhost:1337', 'https://localhost:3443', ]
                                                        ↑ 追記
```
再ビルド。
これで画像選択後、生成されたリンクを踏んで画像表示できるようになりました。


この時点で、ホスト側のディレクトリ構成は以下の通りです。
```
hoge-on-docker
├── app
│   ├── Dockerfile
│   ├── Dockerfile.prod
│   ├── config
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-310.pyc
│   │   │   ├── settings.cpython-310.pyc
│   │   │   ├── urls.cpython-310.pyc
│   │   │   └── wsgi.cpython-310.pyc
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── db.sqlite3
│   ├── entrypoint.prod.sh
│   ├── entrypoint.sh
│   ├── manage.py
│   ├── mediafiles
│   │   └── icon.png
│   ├── requirements.txt
│   └── upload
│       ├── __init__.py
│       ├── admin.py
│       ├── apps.py
│       ├── migrations
│       │   └── __init__.py
│       ├── models.py
│       ├── templates
│       │   └── upload.html
│       ├── tests.py
│       └── views.py
├── docker-compose.prod.yml
├── docker-compose.yml
└── nginx
    ├── Dockerfile
    └── nginx.conf
```

バインドマウント設定を削除した本番用 docker-compose.prod.yml を実行したのに、ホスト側の mediafiles に動作確認用の画像ファイル（icon.png）があるのは何でだっけ？と疑問に思いましたが、Django の local app `upload` を作る時に開発モードで作業したからだと思い出しました。開発モードで動かしたので db.sqlite3 も復活してます。この辺の不要ファイル削除は後回しとします。

### 今後の流れを考える

これで、オレオレ認証ではありますが、**ローカルPCでSSL対応したアプリの起動に成功した**と言えます。
ここまで来れば後は、
1. ConoHa VPS の Ubuntu に docker をインストール
1. ローカルPCのホストデータを ConoHa VPS の Ubuntu にアップロード
1. https-portal を Production 仕様に書き換えてビルド＆実行
1. 自分のドメインでアクセスできるか確認

この流れで行けるはずです。
・・・
・・
・
しかし私はここで、自分のアプリをDocker化して動かしたい欲求に駆られました。。。すみません:worried:

# Dockerひな形プロジェクトを自作アプリ化
ここまで作ってきたひな形プロジェクトに、過去に作った自作アプリを移植します。
大雑把な作業内容は、

- `python manage.py startapp ****` を実行し、自作アプリと同じ名前のlocal appを新規作成
- 各local app の model.pyやviews.py の中身は、自動生成されたコードの内容を確認しつつ、自作アプリコードをコピペ
- templatesは大胆にフォルダごと追加

という感じです。泥臭い手法しか思いつかなかったので、今回はこの方法で乗り切ることにしました。
なお、編集対象はコンテナ or ホスト の２つありますが、お手本サイト学習で 画像保存/表示機能の local app `upload` を作成した時のように、
- 開発モードでコンテナを起動
- コンテナ側で `python manage.py ****` 等のシェル操作をする
- ソースコードの編集はホスト側で実施

というスタイルで作業することにしました。開発モードは docker-compose.yml の中でバインドマウント設定しているので、コンテナとホストは双方向で編集内容が反映されます。ただし、ファイル追加時に権限変更コマンドの実行が必要です。

:::note info
ネットサーフィン中にいちいちchownしない方法を見かけましたが、私の理解度だと、**都度 chown 実行**の方が混乱しないです。ご自身のLinux理解度に合わせて、やり方を決めればいいのかなと思います。
:::

それでは移植作業に着手します。
ローカルPCで自作アプリ移植が済んだ後、それをサーバーに移行する、という手順を踏みます。


# 移植作業 - 開発モード編

過去の自作アプリを移植するにあたって、たくさんのファイルを移行してくるので、本来なら、イメージに含めないファイルを見極めて.dockerignoreファイルに設定する、ということやらないといけないかもしれませんが、今回その作業をしていません。（学習して使えるようになったら追記します。。）

:::note warn
ここの作業手順は、実際の作業メモの通りに書いています。最適な手順を記しているわけではありません。ご注意ください。
:::

### 手始めに settings.py から
自作アプリの settings.py の INSTALL_APPS の中で、自分で書き足した箇所を、ひな形 settings.py の INSTALL_APPS にコピペします。
```python: settings.py の INSTALL_APPS
INSTALLED_APPS = [
    ・・・・・・,
    ・・・,

    # local
    'mysite',
    'textbook',
    'forum',
]
```

このまま一気進めます。

### mdeditor インストール
Webアプリ内の文章を書くところは全てMarkdownに対応させたいと考えて、mdeditor を使っています。
インストール方法及び使い方について何も覚えていないので、確認しながら対処していきます。

下記サイトを参考に作業スタートした様子が当時の開発メモに残っていたので、今回もここを足掛かりに作業を進めます。
https://yuki.world/django-markdown-implement/ （以下、mdeditor参考サイト と記します）

Djangoインストール時にmdeditorもインストールしてほしいので、ホスト側 requirements.txt に追記
```requirements.txt への追記
・・・・
・・・
django-mdeditor==0.1.20
```


**開発用のコンテナにも staticfiles, mediafiles が必要**になりそう。
本番コンテナをダウンして、開発コンテナを起動します。
```bash
docker-compose -f docker-compose.prod.yml down -v
docker-compose up -d --build
```

そういえば、ホスト側の mediafiles の中に icon.png というテスト用画像が残っていました。
削除しようとしたら permission error が発生。権限変更します。
```bash
~/*$ sudo chown -R $USER:$USER .
```
再度、削除。正常に消せました。

開発コンテナ側の作業を開始します。まずはDjangoが入っているwebコンテナのシェルを起動します。
```bash: ホスト側のbash
$ docker-compose exec web sh
```
開発コンテナ側で app/staticfiles/ 作成。
```sh: コンテナ側のsh
# mkdir staticfiles
```
mdeditor参考サイトによると、app/mediafiles/ に editor/ ディレクトリ作成する必要がありそうです。
（この辺から、ひな形のディレクトリ構成に変更を加えることになります。不安が膨らんで、うまくいくか分からないけど、やるしかない！ という感じで作業していた記憶があります。）

```sh: コンテナ側のsh
# mkdir mediafiles/editor
```

ホスト側で settings.py を編集します。
```python: settings.py 追記箇所
INSTALLED_APPS = [
    ...(省略)
    
    # 3rd Party ( mdeditor 追加で必要 )
    'mdeditor',   # 追記
    'templatetags.markdown_extras',  # 最終的に必要になるのでこれも追記しておく
]

# これはDjangoバージョン3.0以上の場合のみ記載。
X_FRAME_OPTIONS = 'SAMEORIGIN'

```
:::note info
mdeditor参考サイトはlocal app 'blog' の中にtemplatetagsを含めていますが、私の場合は'mysite', 'textbook', 'forum' 全ての local app で Markdownを使う必要があります。この場合、app直下にtemplatetagsディレクトリを用意して、markdown_extras.py を設置することになります。そうすると更に INSTALLED_APPS の追記も必要です。
:::

:::note info
最後の行が気になる方は、[『X-Frame-Options について簡単に調べてみた』](https://qiita.com/gotchane/items/4d31b01381f47100de7f) を参照。
:::

ホスト側 config/urls.py の編集です。
mdeditor参考サイトとは異なる書き方をしました。mediafile の記述はひな形のままとしたかったので、変更箇所として記載していません。

```python: config/urls.py 追記箇所
urlpatterns = [
    ...
    path('mdeditor/', include('mdeditor.urls')),  # 追加
]
```

コンテナ側で app/ 直下に templatetags/ を作成します。 更にその下に、markdown_extras.py を作成します。
内容編集はホスト側で実施します。（作業メモには残っていませんが、ファイル追加したらその都度 chown しているはずです。）

```python: markdown_extras.py
from django import template
from django.template.defaultfilters import stringfilter
 
import markdown as md
 
register = template.Library()
 
@register.filter()
@stringfilter
def markdown(value):
    return md.markdown(value, extensions=['markdown.extensions.fenced_code'])

```

**これにて mdeditorインストール準備 終了**


### テンプレートデータの移植
Djangoが自動生成するファイルは、バージョンが上がることで何か変化があると怖いです。なので、models.py や views.py は中身を確認しながらコピペ作業で移植します。しかし、テンプレートである .htmlファイルは一から用意するものなので、フォルダごとコピーしたいです。そもそもファイル数が多いですし。。
ホスト側で、自作アプリの templates/ 以下諸々のデータを全て、ホストの app/ 直下にコピーします。バインドマウントが効いてコンテナの app/ 直下にも、templates/ 以下諸々のデータが配置されたことを確認しました。

### startapp の実行。コードのコピペ。

ここまでまずは startapp せずにできる作業を進めてきました。
startappコマンドで local app を３つ生成します。
```sh: コンテナ側のsh
# python manage.py startapp mysite
# python manage.py startapp textbook
# python manage.py startapp forum
```

ホスト側の作業に移ります。
- 自作アプリ側 の settings.py をみて、ひな形側 settings.py に足りないところをコピペします。
- 各local app の models.py や views.py の自動生成された内容を確認しつつ、自作アプリ側のコードをコピペします。
- 開発用の entrypoint.sh の変更が必要です。postgres との接続を終えて自動で実行する、migrate と データベースフラッシュ、この２行をコメントアウトします。

準備ができたのでビルド後、makemigrations を実行します。
エラー。。
**～～ここからしばらくエラー対応です～～**
一旦仕切り直ししたかったので、コンテナダウンを実行。
ホスト側で編集した内容が取り込めない現象が発生。これは startapp で local app を生成したのに、バインドマウントで生成されたホスト側のlocal app の権限変更が済んでいなかったのが原因。
ホスト側で chown コマンド実行。
```bash
~/*$ sudo chown -R $USER:$USER .
```
このエラーは解決。

続けて **Userモデルが定義されてません**というエラー。うんうん唸ること〇〇分。
Userモデルを作る時に、モデル定義をファイル分割しディレクトリにまとめた。同ディレクトリに設置した `__init__.py` をひな形側にコピーできていなかったのが原因だった。
Pythonの基本が体に馴染んでいないと実感。。:cry:

今度は config/urls.py でエラー。
mdeditorの記述方法が参考サイトのままだった。
自分で書き換えた時に追加したパッケージの import が足りていなかった。
**～～ エラー対応終わり ～～**

makemigrations と migrate が通った！
次に、自作アプリのデータベースから吐き出していたダンプデータ dump.json を loaddataコマンドでひな形側に取り込みます。app/ 直下に dump.json をコピーしてから書きコマンドを実行します。
```sh: コンテナのsh
# python manage.py loaddata dump.json
```
エラー。。またか。:expressionless:
```bash: エラーの一部
duplicate key value violates unique constraint
```

以下の記事を参照しました。
https://stackoverflow.com/questions/65733744/why-i-obtain-this-error-migrationg-data-from-sql-lite-to-posgres-db-duplicate
自作アプリが動いているサーバーにSSH接続し、dump.json を生成し直します。
```bash: 自作アプリのデータベース情報を取得し直す
(既存アプリ)$ python manage.py dumpdata -o dump.json --exclude=contenttypes
```
再チャレンジ

```sh: コンテナのsh
# python manage.py loaddata dump.json
```
うまくいきました。
これで自作アプリのDocker化が完了したことになる。
ただし開発コンテナで実行できただけの状態なので、ゴールはまだ先です。。

# 自作アプリの本番コンテナ化

早々に本番コンテナ化したいのですが、何に気を付ければいいか考えてみます。
すぐ思い浮かぶのはデータベースのデータ。自作アプリのデータベースから吐き出した dump.json を、開発モードのDockerコンテナで loaddataコマンドを実行して取り込みました。この情報は今どこにあるかというと、ローカル Docker の volumes の postgres_data です。

今後も `https://localhost:3443` でアプリを起動したい場面があるかもしれない。となると ローカル Docker の postgres_data は残しておきたいです。開発コンテナをダウンする時は -v をつけずに
```bash: volumeをクリアせずに、コンテナダウン
~/*$ docker-compose down
```
とします。（後々、このことは気にしないことになります。しばらくは当時の作業を追う感じでお付き合いください）

現在はローカルPCで開発コンテナの中に入り、makemigrations と migrate を実行できた、という段階です。
次のステップとして **ローカルPCで本番コンテナを立ち上げる** のですが、どうすればいいか考えます。思い浮かぶことを列挙してみます。
- 開発コンテナで実行した makemigrations と migrate の実施結果は、ホスト側 及び、ローカルDocker volumesのpostgres_data に反映された状態。（先述の通り）
- docker-compose.prod.yml の command は gunicorn 起動だけでいいのだろうか？
- entrypoint.prod.sh は既に migrate と flush を削除しているので、お手当不要のはず。たぶん。
- 本番の docker-compose.prod.yml は flake8が走る。よくわからないところを引っ掛けそう。。不安。

不安ばかり、認識できていないことも多そう。。だけど、とにかく実行してみるしかないです。
```bash
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
```

不安的中。flake8 がたくさん引っ掛けました。構文エラーの嵐。。settings.py の記述修正。言われたことを粛々と対応します。

ビルドが通ったので `https://localhost:3443/` にアクセスします。

```txt: Chromeの表示
Server Error (500)
```
:::note info
DEBUG=True にすれば詳細情報を確認できるはずですが、とりあえず考えるモードになってしまいました。。。:rocket:
経験が浅いのに一人で開発すると、こういう時にストップできない弊害がありそうです。:cry:
:::

この時、「 migrate と collectstatic をしていないから？ volumes を消していないのでそれらのコマンド実行は必要ないと考えたんだけど。」 と考えていたようです。（過去の自分は他人）
（ 予想が外れているのでしばらく苦しみます。とはいえ、しばらく当時の作業順のまま進行させてください。 当時の考えは「」で書いていきます。）

まずはログを確認。
```bash
~/*$ docker-compose -f docker-compose.prod.yml logs -f

・・・・・
db_1   | 2022-08-31 01:43:13.950 UTC [107] FATAL:  database "hello_django_prod" does not exist
db_1   | 2022-08-31 01:43:13.954 UTC [108] FATAL:  database "hello_django_prod" does not exist
・・・・
```

「 同じボリューム名だけど、データベース名が開発(hello_django_dev) と 本番(hello_django_prod)で異なっているのが原因かな。開発で用意したボリュームは、本番コンテナで使えない前提で考えよう。開発コンテナで利用したボリュームを全消しする。」（これは大丈夫。）
```bash
~/*$ docker-compose -f docker-compose.prod.yml down -v
```

「今の状態でdocker-compose.prod.ymlを実行しても色々足りないはずだが、一旦起動した後コンテナに入って作業することにしよう。Let's Go!」（いきおいは大切）
```bash
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
```

ビルドは通るが、Chrome表示は相変わらず。。
```txt: Chromeの表示
Server Error (500)
```
ログを確認。

```bash
~/*$ docker-compose -f docker-compose.prod.yml logs -f
```
無事、起動できた感じがするけど。。
`https://localhost:3443/admin` にアクセスしてもダメ。

ここでようやく **データベースにモデル情報が反映されていない** と気づく。遅い。

```bash
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py makemigrations --noinput
```
「`No changes detected` って帰ってきた。なんで？」

:::note info
開発コンテナで makemigrations を実行した時に、バインドマウントが効いて、ホスト側のlocal app 毎のmigrations に実行結果が反映されている。その情報は本番ビルドにも引き継がれる。
:::

「 次は migrate してみよう」
```bash
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
```
「 色々動いて、正常に完了した。よし。続けて collectstatic も実行。」
```bash
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
```

「 無事、既存アプリが起動できた！中身のデータが無いので寂しい。dumpデータは app 直下にコピーしているので、loaddataしてみよう。これでいけるかな？ 」
```bash
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py loaddata dump.json
```
「できた！！」

ということで、紆余曲折ありましたが、何とか **ローカルPCで本番コンテナを立ち上げる** という小目標は達成できました。

:::note info
当たり前ですが、dumpデータにはアプリにログインする時のユーザー名とパスワードが含まれています。
試しに検索をかけてハッシュ値になっていることは確認しました。今後何かの拍子でイメージを公開する場面があったら、dumpを含めないよう注意せねば。.dockerignore の使い方を覚えないとダメですね。:sweat_drops:
:::

# いよいよデプロイ
ConoHa VPS Ubuntu で本番コンテナを起動し、独自ドメインによるアクセスに対応します。
始めに何から手をつけるか考えます。

### サーバー環境をどうするか。まっさら vs 現状のまま
今のConoHa VPS Ubuntu は（Dockerじゃない）自作アプリが走っている状態です。
この際 Ubuntu をまっさらにして、何もない状態にdocker をインストールすれば色々気にする必要がないのかも。
ConoHa の再構築機能を使って docker入りの Ubuntu を用意する手があるみたい。[参考サイトリンク](https://gadgelaun.com/?p=32780)
　　↓
まっさらにすると、SSH設定をやり直す面倒さは確実に発生します。できれば避けたい。

現状のままDockerを追加する場合、容量が気になります。Django 部分だけデータ削除/アンインストールすればいいかな。
まずは必要となりそうな容量を確認します。 イメージとコンテナ全部合わせて１GBは超えないと思うけど、それに近くはなるかも。大丈夫かな？
　　↓
調査結果、ConoHa VPS のディスク容量が 100GB 近くもあった。容量は気にしなくていい。既存の自作アプリもそのまま放っておくことにしました。

ひとまず、サーバーで自作アプリを動かしている nginx と gunicorn を停止します。

というわけで、**サーバーは現状のまま Docker環境を追加** します。

### ローカルPCのホストデータを ConoHa VPS Ubuntu にコピーしよう
VScodeでファイルをアップロードできる！ドラッグアンドドロップで。
以前はフリーソフトの WinSCP を使ってファイル転送していました。思い返すと VScodeでSSH接続先からダウンロードする機能は自然に使っていました。。普通、アップロードもできるかなって気になりますよね。鈍いですね。（他人事:relieved:）

### ConoHa VPS Ubuntu に Docker と docker-compose をインストール
色々なサイトを参考にしながらインストールしました。どこを参考にしたのか記録しておらず、、すみません。ググれば情報はたくさん出てくると思いますが、私の場合はインストール後のパーミッション絡みでハマり、その時の記録が残っているので書いておきます。（あやふやな点がありますが。。）

ConoHa VPS Ubuntu に 無事 Dockerインストールできたと思いきや、アプリが走らない状況が続いていました。
つまりimage ls や ps コマンドを実行してくれなかったのです。しばらくして、Docker のヘルプコマンドを走らせたら、ヘルプ情報が表示されて、他のコマンドも正常動作するようになりました。そういうタイミングだったんですかね。。

Dockerは動きましたが、docker-compose が動かない。

```bash
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
```
permission denied がたくさん。。
chown を実施。
```bash
~/*$ sudo chown -R $USER:$USER .
```
ダメみたい。。

```bash: エラーメッセージ
docker.errors.DockerException: Error while fetching server API version: ('Connection aborted.', PermissionError(13, 'Permission denied'))
```

[『docker 「docker.errors.DockerException: Error while fetching server API version:」が発生した場合の対処法』](https://mebee.info/2021/10/13/post-44471/)
このサイトの方法で解決できました。

:::note info
作業メモ紛失
おそらく実は解決できてなくて、さらにエラーが出て、下の対応をすることで解決したと思います。。（将来、再現することがあって、対応方法がハッキリわかったら記事修正します。）
:::

https://stackoverflow.com/questions/65664010/how-to-solve-this-problem-permission-denied-when-i-start-docker-compose-up
このサイトを参考に
```bash
~/*$ chmod +x ./app/entrypoint.prod.sh
```
をやってみました。再ビルドして、、通った！！

:::note info
後日メモ
[お手本サイト](https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/)に entrypoint に chmod 実行する手順が明記されていました。まだまだ自分の血肉になっていなかったようです。がんばろう :muscle:

:::

独自ドメイン `https://〇〇〇〇〇〇〇〇〇.com` にアクセスします。

```txt: Chrome
この接続ではプライバシーが保護されません
〇〇〇〇〇〇〇〇〇.com では、悪意のあるユーザーによって、パスワード、メッセージ、クレジット カードなどの情報が盗まれる可能性があります。詳細
NET::ERR_CERT_AUTHORITY_INVALID
```

https-portal の設定が `STAGE: local` のままでした。 `STAGE: production` に変更します。
（作業メモには残っていませんが、`ports:` と `DOMAINS:` の記述もProduction仕様に切り替えているはずです。）

```bash
~/*$ docker-compose -f docker-compose.prod.yml down -v
~/*$ docker-compose -f docker-compose.prod.yml up -d --build
```

アクセスできません、とのこと。makemigrations, migrate, collectstatic 実施していませんでした。

```bash
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py makemigrations --noinput
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
```

再度アクセス。
```txt: Chrome
Bad Request (400)
```

ログを確認しましょう。
```bash
~/*$ docker-compose -f docker-compose.prod.yml logs -f
・・・・・・・・・・・・
nginx_1         | 172.19.0.5 - - [31/Aug/2022:06:48:20 +0000] "GET /favicon.ico HTTP/1.0" 400 143 "https://mgmetal-app.com/admin" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" "152.165.118.199"
nginx_1         | 172.19.0.5 - - [31/Aug/2022:06:48:44 +0000] "GET / HTTP/1.0" 400 143 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" "152.165.118.199"
nginx_1         | 172.19.0.5 - - [31/Aug/2022:06:48:44 +0000] "GET /favicon.ico HTTP/1.0" 400 143 "https://mgmetal-app.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" "152.165.118.199"
```

エラーは出ていない。
この辺りで、port の 443 が本当に開いているのか疑いました。
```bash
~/*$ sudo ufw status
・・・・
443/tcp                    ALLOW       Anywhere
・・・・
```
443、ちゃんと開いている

（ここで余計なことをたくさんしている。。時間を浪費しまくりました :timer: :timer: :timer:）

ふと **Django側の作業を全くやっていない** ことに気が付きました。
```.env.prod
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] 〇〇〇〇〇〇〇〇〇.com
                                                  ↑追加
```

```python: settings.py
CSRF_TRUSTED_ORIGINS = ['http://localhost:1337', 'https://localhost:3443', '〇〇〇〇〇〇〇〇〇',]
                                                                               ↑追加
```

これでいけるでしょう。
```txt: Chrome
Server Error (500)
```

migrate してませんでした。。
```bash
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py migrate --noinput

ERRORS:
?: (4_0.E001) As of Django 4.0, the values in the CSRF_TRUSTED_ORIGINS setting must start with 
a scheme (usually http:// or https://) but found 〇〇〇〇〇〇〇〇〇.com. See the release notes for details.
```

CSRF_TRUSTED_ORIGINS に追記した時、https://　が抜けてました。
```bash
CSRF_TRUSTED_ORIGINS = ['http://localhost:1337', 'https://localhost:3443', 'https://〇〇〇〇〇〇〇〇〇.com',]
```

migrate 通ったので、続けてcollectstatic。
```bash
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py collectstatic --no-input --clear
```

自作アプリ起動できました！
`https://〇〇〇〇〇〇〇〇〇.com` でアクセスできており、鍵マークも付いてます。:lock:

データがすっからかんなので、dumpデータを loaddata で取り込みます。
```bash
~/*$ docker-compose -f docker-compose.prod.yml exec web python manage.py loaddata dump.json
```

できた！！！
Docker化、完了。感無量。:innocent:


# 最後に

記事を書いている最中に[『実践 Docker - ソフトウェアエンジニアの「Docker よくわからない」を終わりにする本』](https://zenn.dev/suzuki_hoge/books/2022-03-docker-practice-8ae36c33424b59) を発見しました。これ無料なの大丈夫なのかな。。私が php まるで分からないのが残念。
私の場合、こちら [『初心者が絵で理解する Docker』](https://zenn.dev/suzuki_hoge/books/2021-04-docker-picture-60fbe950136be9c7ad85) を先に理解しないとダメな気がする。少しずつ読ませてもらいます。そして、自分が書いた記事の間違いに気が付いたら修正していきます。