DockerでNext.js＋Django＋MySQLの環境構築

# 環境・前提条件
自分は

- Windows10で
- プロジェクト作成してからDocker化

しました。
ですので前提として、プロジェクト作成してNext.jsやDjangoの画面を表示できていることが必要になります。
コンテナ上でプロジェクト作成からやる方法もあると思いますが、それは他の方の記事など参照してください。


# フォルダ構成
- Dockfile
- docker-compose.yml
- .dockerignore
- requirements.txt
- .env

を下の構成になるように作成していきます。

```
.
├── back/
│   ├── back/
│   │   └── settings.py
│   ├── myapp/
│   ├── manage.py
│   ├── Dockerfile
│   └── requirements.txt
├── front/
│   ├── .next/
│   ├── node_modules/
│   ├── public/
│   ├── src/
│   ├── .gitignore
│   ├── next.config.mjs
│   ├── package.json
│   ├── package-lock.json
│   ├── README.md
│   └── Dockfile
├── .dockerignore
├── .env
├── .gitignore
└── docker-compose.yml
```
ファイルは今回関係がありそうなもののみ記載しています。


# Next.jsのDockfile

```docker:Dockfile
FROM node:20-alpine

WORKDIR /workspace/front

COPY ./package.json ./package-lock.json /
RUN npm install

COPY . .
RUN npm run build
```

`npm run start`で起動しようとしたので、最後に`RUN npm run build`を書いています。
`npm run dev`で起動するなら最後は不要かと思います。


# DjangoのDockfile、requirements.txt

```docker:Dockerfile
FROM python:3.11

ENV PYTHONUNBUFFERED 1

WORKDIR /workspace/back

RUN apt-get update \
    && apt-get install -y default-mysql-client \
    && rm -rf /var/lib/apt/lists/*
RUN pip install --upgrade pip

COPY . .
RUN pip install -r ./requirements.txt
```

requirements.txtに必要なモジュールを記載します。
最低限はこんな感じ。

```text:requirements.txt
django
mysqlclient
pymysql
```

:::note warn
他の記事を見るとmysqlclientだけ記載されていることが多いですが、pymysqlも必要ぽいです。
:::

開発途中で必要なものが増えたら、都度追記していきましょう。
現時点で自分の場合はこのようになりました。

```text:requirements.txt
django
djangorestframework
django-boost
Pillow
mysqlclient
pymysql
```


# docker-compose.yml

front、back、mydbというサービスを作成していきます。

```yml:docker-compose.yml
version: '3'

services:
  front:
    build: ./front/ #Dockerfileのある場所
    container_name: myapp-front #コンテナ名の指定　無くても良い
    tty: true
    volumes:
      - .:/front
    command: sh -c "npm run start" #npm run dev でも良い
    ports:
      - "12000:3000" #ホスト側：コンテナ側でポートフォワーディング　3000は被りやすいので12000に

  back:
    build: ./back/ #Dockfileのある場所
    container_name: myapp-back #コンテナ名の指定　無くても良い
    tty: true
    env_file: #環境変数ファイルの指定
      - .env
    volumes:
      - .:/back
    command: sh -c "python3 manage.py runserver 0.0.0.0:13000"
    depends_on: #起動にDBが必要なので、DBのヘルスチェック完了後にサービスを起動するように設定
      mydb:
        condition: service_healthy
    ports:
      - "13000:13000" # ホスト側：コンテナ側でポートフォワーディング　8000は被りやすいので13000に

  mydb:
    image: mysql:8.0.20
    container_name: my-db #コンテナ名の指定　DBはあった方が良い
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: 'Asia/Tokyo'
    healthcheck:
      test: "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"
      timeout: 5s
      interval: 5s
      retries: 10
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    volumes: #DBのデータは消したくないのでボリュームを作成
      - db_data:/var/lib/mysql
    ports:
      - 3307:3306

volumes:
  db_data:
```

## build
別のディレクトリに複数Dockfileがあるので、場所を間違えないように指定しましょう。

## command
サービスの起動時に実行されるコマンドです。Dockfileに記載した`RUN ~~`はビルド時のみ実行されます。
Dockfileに書いているような内容をこちらに書くと恐らく重いので、最小限のものにしましょう。

## container_name
DBのコンテナ名はDjangoで必要になるので設定しておきましょう。
frontとbackのコンテナ名は絶対必要ではないですが、コンテナに入りたい時に楽なので設定すると良いです。
`docker exec -it <コンテナ名> bash`で入れるようになります。

## environment
DBを作成するために必要となるので必ず書きます。

- MYSQL_ROOT_PASSWORD：rootユーザーのパスワード
- MYSQL_DATABASE：作成するデータベースの名前
- MYSQL_USER：作成するユーザーの名前
- MYSQL_PASSWORD：作成するユーザーのパスワード

.envファイルに記載する変数を`${変数名}`のように指定します。


# .env
必要な環境変数を記載します。
HOSTはDjangoのsettings.pyで使用します。

```.env
# MYSQL
MYSQL_ROOT_PASSWORD=<rootユーザーのパスワード>
MYSQL_DATABASE=<データベース名>
MYSQL_USER=<ユーザー名>
MYSQL_PASSWORD=<ユーザーのパスワード>
MYSQL_HOST=<DBのコンテナ名>
```

# .dockerignore
コンテナ内にコピーしたくないファイルやフォルダを記載します。
書き方は.gitignoreと同じです。こんな感じ。

```.dockerignore
.dockerignore
.git
.gitignore
node_modules
npm-debug.log
README.md
.next
*Dockerfile*
.env
```

# settings.py
プロジェクト名フォルダの配下にあるsettings.pyを変更します。
MySQLのエンジンに変更し、データベース名など必要情報を環境変数から取得します。
ホストはDBコンテナのコンテナ名になります。

```py:settings.py
DATABASES = {
    "default": {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': os.environ.get('MYSQL_DATABASE'),
        'USER': os.environ.get('MYSQL_USER'),
        'PASSWORD': os.environ.get('MYSQL_PASSWORD'),
        'HOST': os.environ.get('MYSQL_HOST'),
        'PORT': 3306,
    }
}
```

# ビルドして起動
docker-composeのあるフォルダへ移動しビルドします。
```
docker compose build
```
frontサービスのビルドが多分時間掛かりますので、お茶でも飲んでまったり待ちましょう。。。

ビルドが完了したらサービスを起動します。
```
docker compose up -d
```

ビルドと起動を1つのコマンドで行っても良いです。
```
docker compose up -d --build
```


# アプリの起動を確認
localhostの、docker-compose.ymlで指定したポートへ接続しましょう。

Next.js　→　https://localhost:12000
Django　→　https://localhost:13000

Docker化する前に確認できていた画面が表示されればOKです！
お疲れ様でした！


# トラブルシューティング？

最後に、自分が特に詰まった部分をば。。。
項番付けてますが自分がやった順番になるので、この通りにやってね、ということではないです。
ご自身の状況に合わせて試してみてください。

## Djangoアプリの起動を確認できない！

### 1. 起動コマンドとポートを間違えていないか確認
ホストを0.0.0.0に指定する必要があるので、
```
python manage.py runserver 0.0.0.0:8000
```
のようになります。
docker-compose.ymlを再確認しましょう。

### 2. コンテナが起動しているか確認
`docker compose up`の後に、`docker ps`を実行してみましょう。
backのコンテナが起動していない場合は表示されません。
DBのヘルスチェックの後の起動ができていないと思われるので、backサービスのdepends_onの部分を再確認しましょう。

### 3. ポートフォワーディングが合っているか確認
コンテナを起動できれば`docker ps`で表示されるはずです。
こんな感じ。
```
CONTAINER ID   IMAGE              COMMAND                   CREATED          STATUS                    PORTS                               NAMES
6498b3520242   myapp-back   "sh -c 'python3 mana…"   35 minutes ago   Up 35 minutes             0.0.0.0:13000->13000/tcp            myapp-back
3d1cab14240b   mysql:8.0.20       "docker-entrypoint.s…"   35 minutes ago   Up 35 minutes (healthy)   33060/tcp, 0.0.0.0:3307->3306/tcp   my-db
```

PORTSの所に`0.0.0.0:13000->13000/tcp`のように記載があれば上手くいっています。
無ければ、一度コンテナやイメージを削除してビルドし直したり、、、してみましょう。。。
きっと直ります。

### 4. environmentの設定が合っているか確認
DBサービスの部分、横着してenv_fileで環境変数として設定しようとすると、DBやユーザーを作成できないようです。
しっかり記載しましょう。

ちなみにこの状態でもヘルスチェックは通るので、2. コンテナが起動しているか確認、とは別の問題になります。