【Docker】Next.js × Nest.js ×PostgreSQL × pgadmin での環境構築

## 導入
個人開発にハマり中の新卒のエンジニアです。
簡単な自己紹介は以下に

- 私立文系卒
    - 大学2年の冬からプログラミング
    - 学生時代は1年半ほどプログラミングスクールでインターン
- めちゃめちゃ柏レイソルサポーター
    - 本当に今年は最高
- アニメ / 漫画
    - かぐや様は告らせたい
    - ガンダム
    - 弱虫ペダル
    - カグラバチ

本記事では、タイトルにある通り、
- フロントエンドのNext.js
- バックエンドのNest.js
- DBのPostgreSQL
- DB管理用GUIのpgadmin

これらをDockerで構築していきます。

## ディレクトリ構成
ディレクトリ構成をマークダウンに綺麗に書ける神ツール

https://tree.nathanfriend.com/
```
.
└── Edit me to generate/
    ├── api/
    │   ├── nest-api/ ※nestjsプロジェクト
    │   └── Dockerfile
    ├── frontend/
    │   ├── next-front/ ※nextjsプロジェクト
    │   └── Dockerfile
    ├── docker/
    │   └── postgres/
    │       ├── script/
    │       │   └── initialize.sql
    │       ├── postgresql.conf
    │       ├── preferences.json
    │       └── servers.json
    ├── .env
    └── docker-compose.yml
```

## 書いていこう
### ./docker-compose.yml
サービスごとに分けます。
#### nextjsコンテナ
```
nextjs:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    volumes:
      - ./frontend/next-front:/app
      - nextjs_node_modules:/app/node_modules
    ports:
      - '4000:3000'
    networks:
      - app-networks
```

ここで見てほしいのは、nextjs_node_modulesボリュームです。
node_modulesをボリュームにマウントしています。
これにより、パッケージ管理ディレクトリの永続化、イメージを軽量にしています。
#### nestjsコンテナ
```
nestjs:
    build:
      context: ./api
      dockerfile: Dockerfile
    volumes:
      - ./api/nest-api:/app
      - nestjs_node_modules:/app/node_modules
    ports:
      - '3000:3000'
    networks:
      - app-networks
```

nestjs_node_modulesに関しては、上述と変わりません。
ここでは、app-networksに注目してほしいです。
マイグレーションの際に躓きました。

::: note alert
バックエンドのコンテナとデータベースのコンテナが同じnetworkに乗っていないと、データベースのホスト名を識別できず、直接通信ができない
:::
僕が書いた過去の記事です。参考にしてください。

https://qiita.com/JonyTask/items/2908373f11fe69333a08

#### dbサービス(postgreSQLイメージ)
```
db:
    image: postgres:latest
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - postgres_volume:/var/lib/postgresql/data
      - ./docker/postgres/script:/docker-entrypoint-initdb.d
      - ./docker/postgres/postgresql.conf:/etc/postgresql/postgresql.conf
    ports:
      - '5432:5432'
    networks:
      - app-networks
    env_file:
      - .env
```

データベースの環境変数は.envファイルから読み込んでいます。
また、postgres_volumeにマウントすることによって、データを永続化しています。
docker-entrypoint-initdb.は、最初にイメージが起動した際に動くSQLです。
#### pgadminサービス
```
pgadmin:
    image: dpage/pgadmin4:latest
    ports:
      - 8080:80
    environment:
      PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
      PGADMIN_DEFAULT_PASSWORD: ${POSTGRES_PASSWORD}
      PGADMIN_CONFIG_SERVER_MODE: "False"
      PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
      PGADMIN_PREFERENCES_JSON_FILE: /pgadmin/preferences.json
    volumes:
      - pgadmin-data:/var/lib/pgadmin
      - ./docker/postgres/servers.json:/pgadmin4/servers.json
      - ./docker/postgres/preferences.json:/pgadmin/preferences.json
    depends_on:
      - db
    networks:
      - app-networks
    env_file:
      - .env
```

大きくはpostgreと変わりません。
後ほど記載する設定ファイル等をマウントするだけです。
### ./frontend/Dockerfile
```
FROM node:20.10.0

WORKDIR /app

COPY next-front/package*.json ./

RUN npm install

CMD ["npm", "run", "dev"]
```

DockerfileRUNとCMDの違いについて、以下の記事が良いと思います。

https://qiita.com/subretu/items/31a387510f84daaa4c1c

### ./api/Dockerfile
```
FROM node:20.10.0

WORKDIR /app

COPY package*.json ./

RUN npm i -g @nestjs/cli

RUN yarn install

COPY . .

CMD ["npm", "run", "start:dev"]
```
nestの場合はnest用のCLIをインストール

### .env
```
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
PGADMIN_EMAIL=
```
docker-compose.ymlで使われている環境変数を設定してください。
### ./docker/postgres/servers.json
```
{
    "Servers": {
        "1": {
            "Name": "db",
            "Group": "Servers",
            "Host": "db",
            "Port": 5432,
            "MaintenanceDB": "postgres",
            "Username": "ユーザ名"
        }
    }
```

### ./docker/postgres/postgresql.conf
```
max_connections = 200
max_wal_size = 2GB
timezone = 'Asia/Tokyo'
```

### ./docker/postgres/script/initialize.sql
```
-- 作成したDBへ切り替え
\c app_database

-- Timezoneの設定
SET timezone TO 'Asia/Tokyo';

-- スキーマ作成
CREATE SCHEMA スキーマ名;

-- ロールの作成
CREATE ROLE ロール名 WITH LOGIN PASSWORD 'password';

-- 権限追加
GRANT ALL PRIVILEGES ON SCHEMA appschema TO ロール名;
```

コンテナの初回起動時に動くSQLです。 

## 終わり
設定ファイルの説明が面倒なので、かなり省きました。
参考になれば、嬉しいです。

ここまで読んでくださってありがとうございます！
いいねしてくれると、僕の自己肯定感が上がります。