Docker+NextJS+Django+PostgreSQLで環境構築してみる

# はじめに
自分用の備忘録として環境構築の過程を残しておきたいと思い記事にすることにした。
以下の項目を実現することを目標とする。
- Docker上にフロントにReact(NextJS)、バックにDjango、DBにPostgreSQLを構築する
- pgadminでGUI管理できるようにする
- vscode remote containerでフロントとバックをそれぞれ独立でリモート開発できるようにする

# 前提
- M1 Macbook Pro macOS Monterey 12.1
- Docker Desktop 4.12.0
- Visual Studio Code

# 使用技術
- フロントエンド
    - node: 18.9.0
    - next: 12.3.0
    - typescript: 4.8.3
- バックエンド
    - python: 3.10.7
    - django: 4.1.1
    - django rest framework: 3.13.1
    - psycopg2: 2.9.3
- データベース
    - posgreSQL: 14.5

# プロジェクト構成
```
web_app/                      ... プロジェクトルート
    backend/                  ... バックエンド関連フォルダ
        mysite/               ... Djangoプロジェクトフォルダ
        .devcontainer.json    ... remote container設定
        Dockerfile            ... 環境設計書
        requirements.txt      ... pipでインストールするライブラリ
    frontend/                 ... フロントエンド関連フォルダ
        web/                  ... NextJSプロジェクトフォルダ
        .devcontainer.json    ... remote container設定
        Dockerfile            ... 環境設計書
    docker-compose.yml        ... 環境設計書
```

# 構築手順
1. バックエンド
    1. Dockerfile設定
    1. docker-compose.yml設定(バックエンド、DB)
    1. Django起動確認
    1. docker-compose.yml設定(pgadmin)
    1. pgadmin接続確認
1. フロントエンド
    1. Dockerfile設定
    1. docker-compose.yml設定(フロントエンド)
    1. NextJS起動確認
1. remote container設定
    1. devcontainer設定
    1. フロントエンド、バックエンドの独立起動確認

# 1. バックエンド

### 1. Dockerfile設定
設定する内容:
- python3の最新版を取得
- 開発フォルダとしてbackendフォルダを作成
- django、psycopg2をコンテナ環境にpipを通してインストール

実際のDockerfileの内容:
```
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /backend
WORKDIR /backend
ADD requirements.txt /backend/
RUN pip install -r requirements.txt
```

requirements.txtの内容:
```
Django                ... Django本体
psycopg2              ... postgreSQLへの接続ライブラリ
djangorestframework   ... API構築用フレームワーク
```

補足説明:
- この設定だとpython、Djangoなどのライブラリが最新バージョンで取得されると思われる。バージョンを固定したい場合はバージョンを指定する(python3.10.7など)。
- ホスト側のrequirements.txtがコンテナ側にコピー(ADD)されてコンテナ側でpipが走る(RUN)ことになる

以上でバックエンド開発に必要な設定は完了。

### 2. docker-compose.yml設定(バックエンド、DB)
データベースの設定を加えた全体設定をdocker-compose.ymlに設定する。

設定する内容:
- バックエンド
    - サービス名はbackend
    - ビルドに使用するファイルは./backend/Dockerfile
    - ttyを起動しておくことでコンテナがExitしないようにする
    - 開発用にホストのbackendフォルダをコンテナのbackendフォルダと共有する
    - ホストの8000番ポートからコンテナの8000番にポートフォワーディング
        - Djangoのサーバが待ち受けているポートのため
- DB
    - サービス名はdb
    - DB名、ユーザー名、パスワードはお好みで設定しておく
    - データベースのデータは永続化し、コンテナが終了しても失われないようにする

実際のdocker-compose.ymlの内容:
```
version: '3'

services:
  backend:
    build:
      context: backend
    tty: true
    volumes:
      - ./backend:/backend
    ports:
      - 8000:8000
    depends_on:
      - db

  db:
    image: postgres
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
```

### 3. Django起動確認

ターミナルを開き、docker-compose.ymlファイルがあるディレクトリで以下のコマンドを実行する。
```
docker-compose up -d
```

起動の流れが気になる場合は以下のコマンドで確認する。
```
docker-compose logs -f
```

**次に、バックエンドのコンテナに入り、Djangoプロジェクトの作成を試みる。**

まずはコンテナの特定のために以下のコマンドを実行する。
```
docker ps
```

バックエンドのコンテナIDをコピーし、以下のコマンドで対象コンテナに入る。
```
docker exec -it <コンテナID> bash
```

以下のコマンドでDjangoプロジェクトを作成する(mysiteはプロジェクト名)。
```
django-admin startproject mysite .
```

プロジェクトフォルダ(今回はmysite)内のsettings.pyを以下のように編集する。
```
...(略)

LANGUAGE_CODE = 'ja'

...(略)

TIME_ZONE = 'Asia/Tokyo'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',
        'USER': 'root',
        'PASSWORD': 'password',
        'HOST': 'db',
        'PORT': '5432',
    }
}

```

補足説明:
- DATABASESの情報はdocker-compose.ymlのファイルの内容に合致していることを確認すること
- 接続情報など機密情報を直書きしたくない場合、別ファイルに情報を記述してimportする形式にするなど工夫すること(参考: https://chigusa-web.com/blog/django-secret/)

manage.pyファイルがあるディレクトリ上で以下のコマンドを実行し、djangoサーバを起動する。
```
python manage.py runserver 0.0.0.0:8000
```

ブラウザを開き、localhost:8000にアクセス。
以下の画面が表示されればOK。

![スクリーンショット 2022-09-11 18.10.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501516/6200533b-062b-550a-8354-bc7b24845ffe.png)

### 4. docker-compose.yml設定(pgadmin)

postgreSQLのGUIクライアントであるpgadminを導入する。
設定内容:
- サービス名はpgadmin4
- ホスト側の8888番ポートからコンテナ側の80番ポートにフォワーディング
- データは永続化する

実際のdocker-compose.ymlの内容:
```
version: '3'

services:
  backend:
    build:
      context: backend
    tty: true
    volumes:
      - ./backend:/backend
    ports:
      - 8000:8000
    depends_on:
      - db

  db:
    image: postgres
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  pgadmin4:
    container_name: pgadmin4
    image: dpage/pgadmin4
    ports:
      - 8888:80
    volumes:
      - pgadmin4_data:/var/lib/pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: root@gmail.com
      PGADMIN_DEFAULT_PASSWORD: password
    hostname: pgadmin4
    depends_on:
      - db

volumes:
  postgres_data:
  pgadmin4_data:
```

補足説明:
- PGADMIN_DEFAULT_EMAILはメールアドレス形式でなければ起動時エラーとなるため注意

### 5. pgadmin接続確認

再度docker-composeをビルドしてpgadminの起動を確認する。
```
docker-compose up -d --build
```
localhost:8888にアクセスして以下の画面が表示されればOK。

![スクリーンショット 2022-09-11 18.18.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501516/9f13c350-92bb-0b41-7f7b-0551a0f0a18f.png)

# 2. フロントエンド
### 1.Dockerfile設定
設定内容:
- nodeの最新バージョンを取得する
- 開発フォルダとしてfrontendフォルダを作成する
- npmを最新化し、nextをインストールする

実際のDockerfileの内容
```
FROM node
RUN mkdir /frontend
WORKDIR /frontend
RUN npm install -g npm@latest && npm install create-next-app
```

### 2. docker-compose.yml設定(フロントエンド)

フロントエンドの設定をdocker-compose.ymlに加える。
設定内容:
- サービス名はfrontend
- ビルドに使用するファイルは./frontend/Dockerfile
- ttyをtrueとすることでコンテナが落ちないようにする
- ホスト側のfrontendフォルダをコンテナ側と共有する
- ホスト側の3000番ポートをコンテナ側の3000番ポートにフォワーディングする
- WATCHPACK_POLLINGをtrueにすることでホットリロードを使用できるようにする

実際のdocker-compose.ymlの内容:
```
version: '3'

services:
  frontend:
    build:
      context: frontend
    tty: true
    volumes:
      - ./frontend:/frontend
    ports:
      - 3000:3000
    environment:
      - WATCHPACK_POLLING=true

  backend:
    build:
      context: backend
    tty: true
    volumes:
      - ./backend:/backend
    ports:
      - 8000:8000
    depends_on:
      - db

  db:
    image: postgres
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  pgadmin4:
    container_name: pgadmin4
    image: dpage/pgadmin4
    ports:
      - 8888:80
    volumes:
      - pgadmin4_data:/var/lib/pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: root@gmail.com
      PGADMIN_DEFAULT_PASSWORD: password
    hostname: pgadmin4
    depends_on:
      - db

volumes:
  postgres_data:
  pgadmin4_data:

```

### 3. NextJS起動確認

再度docker-composeをビルドする。
```
docker-compose up -d --build
```

フロントエンドのコンテナに入る。
```
docker ps
docker exec -it <コンテナ名> bash
```

NextJSプロジェクトを作成する(webはプロジェクト名)。
```
npx create-next-app@latest web --typescript
```

作成したプロジェクト(今回はweb)ディレクトリ上で以下のコマンドを実行し、サーバを起動する。
```
npm run dev
```

ブラウザを開き、localhost:3000にアクセスして以下の画面が表示されればOK。

![スクリーンショット 2022-09-11 18.39.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501516/901aa5b1-7e95-5787-8592-890112832e1c.png)

# 3. remote container設定

vscodeの拡張機能にremote containerというものがあり、dockerコマンドを毎度叩く必要なくコンテナ上で作業ができるということで導入。
また、フロントエンドとバックエンドそれぞれのコンテナに同時に入りたい。

### 1. devcontainer設定

バックエンド側(backend/.devcontainer.json)の設定内容:
- 名前はDjango Sample
- リモート環境に入る前に立ち上げるdocker-compose.ymlはフロントとバックで同一のファイル
- 作業ディレクトリはbackend/
    - これはコンテナのどのディレクトリで作業するかの指定
- バックエンドだけでなく、DBとpgadminも起動する
    - ただし、フロントエンドは別々にリモート開発したいため起動しない

実際の内容:
```
{
	"name": "Django Sample",
	// Dockerfileでイメージ・コンテナを作成
	"dockerComposeFile": "../docker-compose.yml",
	// VS Codeのワークスペースフォルダ
	"workspaceFolder": "/backend",
	// 使用するサービス
	"service": "backend",
	"runServices": [
		"backend",
		"db",
		"pgadmin4"
	],
	// リモート先のVS Codeにインストールする拡張機能
	"extensions": [
		"ms-python.python"
	],
	"shutdownAction": "stopCompose"
}
```

フロントエンド側(frontend/.devcontainer.json)の設定内容:
- 名前はNext Sample
- リモート環境に入る前に立ち上げるdocker-compose.ymlはフロントとバックで同一のファイル
- 作業ディレクトリはfrontend/
- フロントエンドのコンテナのみ起動する

実際の内容:
```
{
	"name": "NextJS Sample",
	// Dockerfileでイメージ・コンテナを作成
	"dockerComposeFile": "../docker-compose.yml",
	// VS Codeのワークスペースフォルダ
	"workspaceFolder": "/frontend",
	// 使用するサービス
	"service": "frontend",
	"runServices": [
		"frontend"
	],
	// リモート先のVS Codeにインストールする拡張機能
	// "extensions": [
	// 	"",
	// ],
	"shutdownAction": "stopCompose"
}
```

### 2.フロントエンド、バックエンドの独立起動確認

プロジェクトルート(web_app)ディレクトリをvscodeで開く。
左下の緑色のアイコン(><みたいな)をクリックし、「Open Folder in Container ...」をクリックする。
フロントエンドで作業する場合はfrontend/、バックエンドで作業する場合はbackend/を選択してOpenする。
それぞれのコンテナにリモートで入ることができれば完了。