React（Vite）＋Expressの開発環境をDockerで構築する手順まとめ

# はじめに
Webアプリ開発を始めたばかりの頃、「環境構築がうまくいかない」「公式ドキュメントを読むのは難しい」といった悩みに直面したことはありませんか？

私自身もまさにそうで、環境構築について調べても「何言ってるかわからない〜！！」と迷ってばかりでした。

そこで今回は、React（Vite）＋ExpressをDockerで動かす汎用的な環境構成をわかりやすく（？）まとめてみました。

自分の備忘録・アウトプットを兼ねて、**初心者でもつまずきにくい手順を丁寧に解説していきます。**
チーム開発に挑戦したい方や、環境構築に不安がある方の参考になれば嬉しいです。

なお、筆者もまだ学習中の身です。間違いや改善点があれば、コメントで教えていただけると嬉しいです！

# この記事の対象となる方
- ReactとExpressで開発したいけど環境構築で悩んでいる方
（少しDockerのことを知っていると楽かもしれないです）

# 実行環境
- **OS**：macOS Sonoma 14.4.1
- **CPU**：Apple M3
- **Node.js**：v22.15.0
- **npm**：v10.9.2

今回、筆者はmacOSを使用していますが、windowsとの差はほぼありません。

# 事前準備
以下のアプリケーションやツール類は、事前にインストールされている前提で進めていきます。
- Docker Desktop
- VScode
- Node.js

# フロントエンド構築（React）
まず、フロントエンド側の環境から構築していきます。

最初に、ローカルに任意の名前でフォルダを作ります。プロジェクト名だとわかりやすいです。今回、私は「supply-tracker」というフォルダを作成しました。

![スクリーンショット 2025-05-19 10.01.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681209/dc60c025-8e8c-4eea-ae2c-da6eb2d5ff9c.png)

次に、作成したフォルダをvscodeで開きます。

![スクリーンショット 2025-05-20 3.24.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681209/dce67020-9de5-4594-9ecd-42ab7aea9b51.png)


`command+J`でターミナルを開き、以下のコマンドを実行します。
```
npm create vite@latest
```
すると、以下のような項目について聞かれます。
- Project name: に対しては `frontend` と記入
- Select a framework: に対しては `React` を選択
-  Select a variant: に対しては、今回はJavaScriptを使うのと、コンパイル時間を短くしたいので `JavaScript + SWC` を選択

![スクリーンショット 2025-05-20 3.29.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681209/03a929b9-8247-463d-92db-2e709c1b723c.png)


次に、作成したフロントエンドに移動し、依存パッケージをインストールします。以下のコマンド2つを実行してください。
```
cd frontend
```
```
npm install
```
以上でフロントエンド側の作業は完了です。

## 依存パッケージとは？
ここで、依存パッケージについて説明します。
依存パッケージとは、簡単にいうと、**自分のアプリが動くために必要な「他人が作った便利なプログラム」** です。

例えば、日付をいい感じに表示したいとき、ゼロから全部コードで書くのは大変です。そんなときに「すでに誰かが作ってくれたライブラリ（＝依存パッケージ）」を使えば、簡単に実現できます。

### package.jsonについて
インストールしたパッケージは、 package.json というファイルに自動で記録されます。これにより、
- プロジェクトになんのパッケージが入っているかひと目でわかる
- チームで共有しても、同じ環境を再現できる

といったメリットがあります。

### node_modulesについて
実際の依存パッケージ本体は、この node_modules というフォルダに保存されます。

### npm install について
単に、
```
npm install
```
を実行すると、現在 package.json に書かれている依存パッケージの一覧をもとに、対応するバージョンのパッケージを全てインストールします。

install を省略して、
```
npm i
```
としても良いです。（以下、省略した書き方で進めます）

一方、新しくパッケージをインストールするときなど、個別にパッケージをインストールしたい場合は
```
npm i dayjs
```
として `npm i` の後にパッケージ名を指定する必要があります。

## 動作確認
念の為、環境が正しく構築されているか確認します。

以下のコマンドを frontend/ ディレクトリで実行してください。
```
npm run dev
```
ターミナルに表示されたURLに移動し、以下のような画面が表示されたら正しく構築されています。

![スクリーンショット 2025-05-19 10.36.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681209/263f4ba1-f398-4888-b4c7-187c9a429ab5.png)


なお、ターミナルで立ち上げたサーバーはターミナル内で`control+C`と入力することで終了できます。

# バックエンド構築（Express）
バックエンド側の環境を構築する前に、プロジェクトのルートディレクトリに行ってください。
現在、 frontend/ ディレクトリにいる場合は、
```
cd ..
```
をターミナルで実行することでプロジェクトのルートディレクトリに移動できます。

次に、以下のコマンドを実行してバックエンド用のフォルダを作成します。
```
mkdir backend
```
現在、以下のような構成になっているはずです。

```
your-app/
├── backend/
├── frontend/
│   └── ...
└── ...
```

次に、以下のコマンドを実行し、作成したフォルダに移動します。
```
cd backend
```
以下のコマンドを実行します。
```
npm init
```
すると、package.jsonファイルを生成するための以下の項目を質問形式で聞かれます。
- `package name: (backend)`： プロジェクトの名前
- `version: (1.0.0)` ：初期バージョン番号
- `description:`：プロジェクトの簡単な説明
- `entry point: (index.js)`：アプリケーションの基点となるファイル
- `test command:`：テストを実行するためのコマンド
- `git repository:`：このプロジェクトのGitリポジトリURL
- `keywords:`
- `author: `
- `license: (ISC) `

何か情報を入れたい場合は入力してください。全てEnterキーを押して進んでも問題ありません。
```
npm init -y
```
で質問をスキップし、即作成することも可能です。

また、後で値を変更したい場合は backend/package.json の中身を直接編集すれば良いです。

次に、以下のコマンドを実行し、Expressのパッケージをインストールします。
```
npm i express
```

### 動作確認
backendディレクトリ直下に、src というフォルダを作成し、その中に`index.js`ファイルを作成してください。
現在、以下のようなディレクトリ構成になっているはずです。

```
your-app/
├── backend/
│   ├── src
│   │   └── index.js
│   └── ...
├── frontend/
│   └── ...
└── ...
```

以下のコードを index.js に貼り付けます。
```javascript:index.js
const express = require("express");
const app = express();
const PORT = 4000;

app.get("/", (req, res) => {
  res.send("Hello from backend!");
});

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
```
以下のコマンドをbackend/src/で実行し、サーバーを立ち上げます。
```
node index.js
```
表示されたURLに以下のような画面が表示されたらバックエンドも正しく環境が構築されています。

![スクリーンショット 2025-05-20 1.31.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681209/d0cd9f6c-287b-491d-bd8e-aaf86afcbcee.png)

# 入れておくと良い依存パッケージ
ここで、入れておくと良い依存パッケージを紹介します。自分の開発手法などに合わせて検討してみてください！
## React（フロントエンド）

|パッケージ名|用途・役割|備考|
|-|-|-|
|`react-dom`|DOMへの描画|同上|
|`react-router-dom`|SPAルーティング|複数ページ構成に必須|
|`axios`|API通信|fetchよりシンプルで多機能|
|`prettier`|コード整形|スタイル統一|

---

## Express（バックエンド）

|パッケージ名|用途・役割|備考|
|-|-|-|
|`express`|HTTPサーバー構築の基本フレームワーク|必須|
|`cors`|フロントエンドとの通信許可|必須|
|`dotenv`|環境変数管理|`.env` でDB URIなどを管理|
|`nodemon`| 自動リロード（開発効率UP）|devDependenciesで導入|

:::note warn
以後、パッケージのnodemon、cors、dotenvをインストールしている前提で進めていきます。
:::

# Dockerを用いた環境構築
さて、これで個人開発ができるようになりました。しかし、チーム開発を行う場合、これだけでは不十分です。
どの環境でも同じように開発ができるように、Dockerを用いて、作業する環境を作ってあげる必要があります。

## ディレクトリ構成
今までの操作で、現在以下のようなディレクトリ構成になっていると思います。
```
your-app/
├── backend/
│   └── ...
├── frontend/
│   └── ...
└── ...
```

上記の状態から、以下のように、ファイルを新たに作成してください。

```
your-app/
├── backend/
│   ├── Dockerfile（new!）
│   ├── .dockerignore（new!）
│   ├── .env（new!）
│   └── ...
├── frontend/
│   ├── Dockerfile（new!）
│   ├── .dockerignore（new!）
│   └── ...
├── docker-compose.yml（new!）
└── ...
```

:::note warn
- ディレクトリの階層に気をつけてください。
- （new!） と記載されているファイルが、今回新たに作成するファイルです。ファイル名には含めないようにしてください。
:::

`Dockerfile`、`docker-compose.yml`、`.env`、`.dockerignore`については、順番に説明していきます。

## Dockerfileについて

### Dockerfileとは
Dockerfileとは、アプリを動かすための環境を自動で作るレシピのようなものです。
どのソフトを入れるか、どのコマンドを動かすかなどを書いておくことで、誰でも同じ環境をすぐに作れるようになります。

### frontend/Dockerfileの中身と役割 
以下に、それぞれのファイルの中身と、その簡単な説明をまとめました。コピペしたり、自分なりにアレンジして書いてみてください。
```Dockerfile:frontend/Dockerfile
FROM node:18
# Node.jsの公式イメージ（バージョン18）を使用

WORKDIR /app
# コンテナ内の作業ディレクトリの設定

COPY package*.json ./
#ローカルのpackage.json、package-lock.jsonをコンテナの/app内にコピー

RUN npm install
#コンテナ内で、必要なパッケージをインストール

COPY . .
#ソースコード全体をコンテナにコピー

CMD ["npm", "run", "dev"]
#npm run devを実行し、Viteなどの開発サーバーを起動
```

### なぜCOPYを2回に分けて行うのか
→ビルド時間を大幅に短縮するため！
詳しく説明します。

```Dockerfile:frontend/Dockerfile
COPY . .

RUN npm install
```
上記のようにコード全体をまとめてコピーしてしまうと、ソースコードに少しでも変更があるたびに、その後の `RUN npm install` も毎回実行され、ビルドに時間がかかってしまいます。

そのため、まずは依存関係に関するファイル（ package.json や package-lock.json ）だけを先にコピーし、それらに変更があった場合のみ `RUN npm install` を実行するようにすることで、**依存パッケージのインストール処理にキャッシュが利用され、ビルド時間を大幅に短縮することができます。**

### backend/Dockerfileの中身と役割
こちらもfrontend/Dockerfileと同様、コピペしたり、自分なりにアレンジして書いてみてください。
```Dockerfile:backend/Dockerfile
FROM node:18
# Node.jsの公式イメージ（バージョン18）を使用

WORKDIR /app
# コンテナ内の作業ディレクトリの設定

COPY package*.json ./

RUN npm install
#コンテナ内で、必要なパッケージをインストール

COPY . .
#ソースコード全体をコンテナにコピー

CMD ["npx", "nodemon", "src/index.js"]
#npx nodemon src/index.js を実行して、ソースコードの変更を監視しながらサーバーを起動
```


## docker-compose.ymlについて

### docker-compose.ymlとは
docker-compose.yml は、複数のDockerコンテナを一括で管理・起動できる設定ファイルです。

今回は、フロントエンド（React）とバックエンド（Express）の2つのサービスを用意するため、それぞれのDockerfileを1つのファイルにまとめて起動できるようにします。

### docker-compose.ymlの中身と役割
以下に、docker-compose.ymlの中身とその簡単な説明をまとめました。こちらもDockerfileと同様に、コピペしたり、自分なりにアレンジして書いてみてください。
```yml:docker-compose.yml
services:
  backend:
    build: ./backend           # backend/の Dockerfile をビルド
    ports:
      - 8080:8080              # ホストの8080番とコンテナの8080番をつなぐ
    volumes:
      - ./backend:/app:cached  # ローカルの./backendフォルダを、コンテナ内の/appに同期
      - /app/node_modules      # node_modulesフォルダは同期から除外して、コンテナ内のものを使用
    env_file:
      - ./backend/.env         # .envファイルから環境変数を読み込む
  
  frontend:
    build: ./frontend          # frontend/の Dockerfile をビルド
    ports:
      - 3000:3000              # ホストの3000番とコンテナの3000番をつなぐ
    volumes:
      - ./frontend:/app:cached # ローカルの./frontendフォルダを、コンテナ内の/appに同期
      - /app/node_modules      # node_modulesフォルダは同期から除外して、コンテナ内のものを使用
    depends_on:
      - backend                # backendサービスが先に起動するように依存関係を指定
```
### cachedとは？
volumesでファイル同期をする際、`cached`をつけるとローカル側の変更が優先されるプロパティです。これは必ず記述する必要はありません。
### なぜnode_modulesを同期から除外するのか？
ホストとコンテナでOSや依存パッケージの環境が異なる場合、node_modulesを共有すると動作しなくなることがあります。

そのため、コンテナ内でインストールした依存関係のみを使うようにすることで、動作の安定性を保ちます。

### .envファイルとは？
アプリで使う設定値や秘密の情報（APIキーなど）を保存するファイルです。外部ファイルである.envファイルに分けて管理することで安全な設計となります。

docker-compose.ymlから読み込むことで、コードに直接書かずに設定を変更でき、セキュリティや環境ごとの切り替えが容易です。

また、.envファイルは機密情報を含むため、.gitignoreに追加して、Gitの管理下から外しておきましょう。（Git、GitHubの記事についてもそのうち書きたいです）

## .dockerignoreについて

### .dockerignoreとは

.dockerignoreファイルは、Dockerイメージをビルドするときに、**コンテナに含めないファイルやフォルダを指定する**ためのファイルです。

例えば、以下のようなものは、イメージに入れる必要がありません。
- 開発にしか使わない設定ファイル
- node_modulesなど、ローカル側で生成される大容量フォルダ
- .envファイル（セキュリティ上の観点から）

### .dockerignoreを使うメリット
- Dockerイメージが軽くなる
- 秘密情報の漏洩を防げる etc...

### .dockerignoreの中身の例
```.dockerignore
# node_modulesはコンテナ内で再構築するため除外
node_modules

# 環境変数などを含むファイル
.env

# ログファイル類
*.log

# macOSのゴミファイル
.DS_Store

# Git関連ファイルも不要
.git
.gitignore

# エディタの設定ファイルなど
.vscode
```
コピペしたり、自分なりにアレンジして書いてみてください！

## vite.config.jsについて
Viteでは、開発サーバーのデフォルトのアドレスは http://localhost:5173/ に設定されています。

しかし、先ほどの docker-compose.yml でフロントエンドのポート番号を 3000 に設定したため、Vite側もそれに合わせてポート番号を変更する必要があります。この設定をしないと、ブラウザで正しくページを表示できません。

そこで、frontend/ ディレクトリにある vite.config.js を以下のように編集します。
```javascript:vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000,  // 使用するポート番号を指定
    host: '0.0.0.0',  // すべてのIPアドレスからアクセスを受け入れる
    watch: {
      usePolling: true,  // ファイル変更の検出をポーリング方式にする（特にDocker環境で必要）
    },
  },
})

```

もちろん、 docker-compose.ymlに書いたフロントエンドのポート番号を 5173 とすることでも解決できます。

### usePolling: ture の意味とは？
通常、Viteはファイルの変更を自動で検出します。しかし、Dockerコンテナ内ではこれがうまく機能しない場合があります。その結果、ホスト側でファイルを編集しても、ホットリロードが動かないことがあります。

この問題を防ぐために、
```javascript:vite.config.js
usePolling: true
```
を指定することで、Viteは**一定間隔でファイルの変更を確認できるようになります。**

なお、ポーリング方式は、通常よりリソースを消費するため注意が必要です。

## コンテナの立ち上げ
ここまで来ればほぼ終了です！

**コンテナを立ち上げる前にDocker Desktopを起動し、`Engine running`の状態にしておいてください。**

まず、ターミナルでプロジェクトのルートディレクトリに移動し、
```
docker compose build
```
を実行した後に、
```
docker compose up
```
を実行してみてください。

コンソールに以下のように出力され、`Local:` に記載されているURLに移動し、画面が正しく表示されていれば成功です！

![スクリーンショット 2025-05-20 1.54.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681209/f912288e-ade6-4cd8-8a78-7e214f79f12c.png)


![スクリーンショット 2025-05-20 1.52.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3681209/37766dbe-3eb2-45e9-942d-4aea061e5df6.png)

:::note warn
画像はポート番号を 5050 としたときの画像です。この記事通りに環境構築を進めた方は http://localhost:3000/ に移動すると正しい画面が表示されるはずです。
:::

以上で環境構築終了です！
お疲れ様でした！

# 追加したいパッケージがある場合
開発を進めていく中で、「あ、このパッケージ欲しい」となることがあるかもしれません。そういった時のパッケージの追加方法について説明していきます。

まず、以下のコマンドを実行し、コンテナをリセットします。
```
docker compose down
```
次に、ローカルでパッケージを入れたいディレクトリに移動します。（ frontend/ や backend/ など）

移動できたら、以下のコマンドを実行してパッケージをインストールしてください。（ ~~ には入れたいパッケージ名が入ります）
```
npm i ~~
```
パッケージが入れ終わったらプロジェクトのルートディレクトリに移動し、以下の2つのコマンドを実行してコンテナを立ち上げてください。
```
docker compose build
```
```
docker compose up
```

以上でパッケージの追加は完了です！

一応、なぜこれでコンテナ内にもパッケージがインストールされるのか分からない方もいらっしゃるかもしれないので解説します。

### なぜコンテナ内にパッケージがインストールされるのか？
まず、ローカルでパッケージをインストールしましたよね。その際に、 package.json にインストールしたパッケージの情報が反映されます。
この状態で再度コンテナをビルドします。

すると、コンテナの作業ディレクトリに package.json がコピーされます。
この際、 package.json に変更があるため、`npm install`が実行され、コンテナ内に package.json に記載されたパッケージがインストールされます。
結果として、コンテナ内にパッケージがインストールされることになります。

# 終わりに
以上、React（Vite）＋Expressの開発環境をDockerで構築する手順をご紹介しました。
少しでも参考になっていれば幸いです。

わかりにくい点や間違い、「こうするともっと良くなるのでは？」といったご意見があれば、ぜひコメントで教えてください。

速攻で修正します！！