PodmanでReactアプリケーションの環境構築『初心者向け』

# はじめに

私は IT未経験の新卒として入社し、アプリケーションをコンテナでデプロイする機会がありました。先輩方が用意した手順書を参考にしながら、Dockerの設定などを進めることが多いのですが、その際に「なぜこの手順が必要なのか」「各設定項目が何を意味するのか」といった疑問を感じることがよくあります。

そこで今回は、Reactアプリケーションの作成から、ローカルでコンテナを立ち上げてデプロイするまでの手順について、まとめてみました。

初学者の皆さんにとって、この内容がお役に立てば幸いです。

## 環境について
- React: 18.3.1
- Node.js: v20.16.0
- Podman: 3.4.4
- Ubuntu 22.04.4 LTS　(WindowsのWSL2)
- WindowsOS：11 

# 目次
[手順](#手順)
- [1. Reactプロジェクトの作成について](#1reactプロジェクトの作成について)
    - [1.1. Reactプロジェクトの作成](#11-reactプロジェクトの作成)
    - [1.2. Reactプロジェクトの確認](#12-reactプロジェクトの確認)
- [2. 作成したReactプロジェクトのコンテナ化](#2-作成したreactプロジェクトのコンテナ化)
    - [2.1. Dockerfile と docker-compose.ymlの作成](#21-dockerfile-と-docker-composeymlの作成)
        - [2.1.1. Dockerfileの作成](#211-dockerfileの作成)
        - [2.1.2. docker-compose.ymlの作成](#212-docker-composeymlの作成)
    - [2.2. イメージのビルド](#22-イメージのビルド)
    - [2.3. コンテナ化 および起動](#23-コンテナ化-および起動)
        - [2.3.1. 補足 発生し得るエラーおよびその解決策](#231-補足-発生し得るエラーおよびその解決策)

[まとめ](#まとめ)

# 手順
## 1.Reactプロジェクトの作成について
### 1.1. Reactプロジェクトの作成
-  作業用のディレクトリを作成します。
```
$mkdir mySPAapp
```
- Reactプロジェクトを作成します。
```
$npx create-react-app my-spa-react-app

#Version指定することもできます
$npx create-react-app@(version number) (project name)
```
:::note warn
 `npx` は `npm` の一部として Node.js をインストールする際に自動的にインストールされます。したがって、Node.js をインストールしていない環境では、まず Node.js をインストールする必要があります。
:::


### 1.2. Reactプロジェクトの確認
my-spa-react-appというプロジェクト名のディレクトリも作成されます。
現在、フォルダの構成は以下のようになります：
```
.
└── mySPAapp
    └── my-spa-react-app
```
- カレントディレクトリを `my-spa-react-app` に変更します。
- `npm start` で「http://localhost:3000/」 を起動してアプリケーションが動作できるかどうかをチェックします。

## 2. 作成したReactプロジェクトのコンテナ化
### 2.1. Dockerfile と docker-compose.ymlの作成
#### 2.1.1. Dockerfileの作成

Dockerfileとは、新規Dockerイメージを作成するための一連の命令を記述したテキストファイルです。システムは、これらの命令とプロセスに従ってイメージをビルドします。

プロジェクトフォルダ(my-spa-react-app)内で、ReactアプリケーションのDockerfileを作成します。
```
$touch Dockerfile
```
現在、フォルダの構成は以下のようになります：
```
.
├── Dockerfile
├── README.md
├── package-lock.json
├── package.json
├── public
└── src
```
Dockerfileに以下の内容を記述します。
```
FROM node:20  
WORKDIR /app  
COPY package.json package-lock.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm","start"]
```
**説明**：

- `FROM node:20`
今回はNode.js v20でプロジェクトを作成しているため、node:20を指定します。
これにより、コンテナ内でNode.js v20の環境が準備されます。
 
- `WORKDIR /app`  
システムはコンテナ中で/appというディレクトリを作ってくれます。
また、このディレクトリがコンテナ内での作業領域となります。

- `COPY package.json package-lock.json ./`
ローカルのNode.jsのコンフィグファイル(package.json package-lock.json)をWORKDIR(/app)中にコピーします。

- `RUN npm install`
package.json package-lock.json二つのコンフィグファイルに従ってパッケージのインストール
**順番は重要です**  これを`COPY package.json package-lock.json ./`の前に移すとエラーになる。

- `COPY  .   .`
残りのファイルをコンテナ内の現在の作業ディレクトリ(/app)にコピーします。

- `EXPOSE 3000`
`EXPOSE` は開発者や他の人に「コンテナ内でこのポートが使われている」ことを示すだけで、ホストとのポートマッピングは行いません。
ポートマッピングを行うには、docker-compose.yml で設定するか、コンテナ起動時に -p オプションを使用します。

- `CMD ["npm", "start"]`
コンテナを起動する際のデフォルトコマンドを指定します。
ここでは、npm startを実行するように設定しています。これがないとコンテナを起動しても何も実行されず、すぐに終了してしまいます。
<br>

:::note info
Docker はレイヤーベースのアーキテクチャを使用しており、各コマンドがレイヤーとしてキャッシュされます。

COPY を2回に分ける理由は、package.json のような依存関係のファイルが変わらない限り、キャッシュを再利用できるためです。これにより、再ビルド時に全体を再実行する必要がなくなり、効率が向上します。全てのファイルを一度にコピーすると、わずかな変更でもすべてのステップが再実行されてしまいます。
:::



**.dockerignore ファイルの作成について**
※本手順は必須でない為、スキップしても問題無い

`.dockerignore` ファイルは、Docker イメージをビルドする際に特定のファイルやディレクトリを除外するためにつくられるファイルです。

一般的に、次のような内容を記述します。
```
node_modules  #インストールされた依存パッケージのフォルダ
.git　　　　　 #Gitのバージョン管理情報を含むフォルダ
Dockerfile    #Dockerイメージ作成の指示を記載したファイル
.dockerignore #本ファイル
.env　　　　　 #環境変数を定義するためのファイル
```
:::note warn

コンテナを起動する際に、Dockerfile中の命令 `RUN npm install` を通して自動的に必要なパッケージをコンテナ中でインストールされます。

Reactアプリケーションのnode_modulesフォルダのサイズは大きいため、node_modulesを除外することでビルド時間を高速、イメージサイズの縮小を行います。
:::
<br>

#### 2.1.2. docker-compose.ymlの作成

docker-compose.ymlは、複数のコンテナを管理するための設定ファイルです。Docker Composeを使うことで、複数コンテナのビルドや起動・停止が簡単に行えます。

:::note info
Docker Compose は通常、複数のコンテナを管理するために使用されますが、1つのコンテナの場合でも、環境設定を簡単にしたり、設定ファイルをバージョン管理できるメリットがあります。さらに、将来的にコンテナが増える可能性がある場合にも、柔軟に対応できます。
:::
>現在、Reactアプリケーションのフロントエンド部分のみを作成していますが、今後データベースサービスを追加するため、Docker Composeを使用させていただきます。

プロジェクトフォルダ(my-spa-react-app)内で、docker-compose.ymlを作成します。
```
$touch docker-compose.yml
```

現在、フォルダの構成は以下のようになります：
```
.
├── Dockerfile
├── README.md
├── docker-compose.yml
├── package-lock.json
├── package.json
├── public
└── src
```
docker-compose.yml に以下記の内容を記述します。
```
version: '3'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./:/app
```
**説明**：
- `version`: '3': Docker Composeのバージョン指定。通常、最新バージョンを使用します。
- `services`: サービス名は自由に設定できます。今回はappというサービスを定義します。

Webアプリケーションのサービスをappとして定義します。
```
app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./:/app
```
- `build: .`
現在のディレクトリ（Dockerfileが存在するディレクトリ）からイメージをビルドします。

- `ports: "3000:3000"`
Dockerfile中のEXPOSEで指定したポートはコンテナ内のポートです。
ホストとのポートマッピングを行うには Docker Compose の設定か、docker run の -p オプションで指定する必要があります。

- `volumes: ./:/app`
ホストの現在のディレクトリ（./）をコンテナ内の/appディレクトリにマウントします。これにより、ホスト側のファイルを変更するとコンテナ内のファイルもリアルタイムで反映されます。

>補足：
>データベースサービスの追加の場合は、次のようになります
```
version: '3'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./:/app

  db:
    image: mongodb
    environment:
      MONGO_INITDB_ROOT_USERNAME: example
      MONGO_INITDB_ROOT_PASSWORD: example
    ports:
      - "27017:27017"
```

        
---
### 2.2. イメージのビルド

以上のことを完了した上で、 イメージをビルドします。
```
$podman build -t my-spa-react-app:01 . 
```
- -t : タグ
- .  :　現在のディレクトリ

完了後、以下のコマンドで、自分が作成したイメージがリストに表示されていることが確認します。
```
$podman images

localhost/my-spa-react-app  01          5c0c7096dbb1  6 seconds ago  1.47 GB
```

### 2.3. コンテナ化 および起動
ビルドしたイメージでコンテナを作成して、起動します。
```
$podman run -d -p 3000:3000 --name my-spa-app-container my-spa-react-app:01 
```
- `-d`: コンテナをバックグラウンド（デタッチドモード）で実行します。
- `--name my-spa-app-container`:  コンテナの名前を指定します。
- `my-spa-react-app:01`：  先ほど作ったイメージ名とタグ。

:::note warn
※ Docker Composeでホストポート番号とコンテナポート番号のマッピングを設定していない場合、`-p` オプションを使用して、ホストのポート3000をコンテナのポート3000にマッピングする必要があります。
:::


完了後、以下のコマンドでチェックします。 
status が Up になっていれば、コンテナは正常に起動して稼働中であることを意味します。
```
$podman ps -a 

43e15c99bb94  localhost/my-spa-react-app:01  npm start   3 seconds ago  Up 3 seconds ago  0.0.0.0:3000->3000/tcp  my-spa-app-container
```

:::note warn
通常、podman ps コマンドだけでは、現在実行中のコンテナのみが表示されます。しかし、停止中のコンテナやエラーで起動できなかったコンテナ、過去に実行されたコンテナも確認したい場合は、-a オプションの使用が推奨されます。
:::

最後は 「http://localhost:3000/」 を起動してアプリケーションをチェックします。画面が表示されれば、環境構築は完了になります。

 <img width="350" alt="起動できた例" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887716/a494f98f-add5-9951-03bc-f6f754893edf.png">


#### 2.3.1. 補足 発生し得るエラーおよびその解決策
今回はwindowsで作ったReactアプリケーションをLinuxでpodmanを用いてデプロイしました。
Windows と Linux ではファイルパスや環境設定が異なるため、このようなエラーが発生する可能性があります。

```
Error: spawn /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe ENOENT
```
このエラーは、ReactアプリケーションがホストOSであるWindowsの環境で動作していると認識し、Powershellを使ってブラウザを開こうとしているために発生しています。

これを解決するために、プロジェクトフォルダ内のpackage.jsonファイルを以下のように修正します。

```
"scripts": {
  "start": "cross-env BROWSER=none react-scripts start",
  ...
}
```
**説明**：
- `BROWSER=none` ：アプリケーションの環境変数です。通常`create-react-app` で作られたアプリを`npm start`で起動するとブラウザでアプリが自動的に開かれます。しかし、サーバーやコンテナなど、ブラウザやGUIがない環境では、自動的にブラウザを開くことができず、エラーが発生します。`BROWSER=none`に設定することで、自動的に開かなくなります。

- `cross-env` : 異なるOS間で環境変数を一貫して設定できるようにするための npm パッケージです。これを通して、どのOSでもおなじような環境変数を設定できます。


# まとめ
今回はReactアプリケーションの作成からコンテナの立ち上げまでの環境構築をしました。

最後までお読みただき、ありがとうございました！