【Day10】結局コンテナ1つで開発する方が楽かもしれない

## はじめに

勉強とアウトプットを兼ねて、Webアプリ開発の記録を記事にしてみます。

個人のメモ的な側面が強いです。
Web開発初心者すぎて至らぬところだらけだと思いますが、暖かい目で見ていただければ幸いです。

↓ これまでの内容 ↓
[【Day0】アプリが完成するまで毎日投稿](https://qiita.com/midorigame/items/f9b9a445c945b4a837b1)
[【Day1】開発環境を考える](https://qiita.com/midorigame/items/e26344599e2ffaa22033)
[【Day2】開発環境を構築する、、、はずでした](https://qiita.com/midorigame/items/3f57f2691a8e1074adb8)
[【Day3】まずはフロントだけ開発環境を構築する](https://qiita.com/midorigame/items/9de27ba72aaef504a833)
[【Day4】バックエンドの開発環境を整えつつ、マルチコンテナ対応のDevcontainerにする](https://qiita.com/midorigame/items/5b95374cd85f715a6498)
[【Day5】お試し実装！ファイルをアップロードしてバックエンドで処理する](https://qiita.com/midorigame/items/9663f0063b95b501c08a)
[【Day6】Docker Desktopが起動しなくなり泣きながら原因調査する](https://qiita.com/midorigame/items/d07df1caf05267d0609d)
[【Day7】画像にフレームとExif情報を付けてみる](https://qiita.com/midorigame/items/e2d85ce8dd7a5bcb5daf)
[【Day8】フロントに表示する画像のサイズを小さくする](https://qiita.com/midorigame/items/f7f5f9b14fb79259c716)
[【Day9】フロントエンドとバックエンドをそれぞれデプロイする](https://qiita.com/midorigame/items/4c8037024e667ff1105b)

## 前回やったこと

ついに開発環境からサーバーへデプロイしました！
外部公開したことでスマホなどからアクセスして操作できるの楽しい！

## 今回やったこと

[【Day4】](https://qiita.com/midorigame/items/5b95374cd85f715a6498)でわざわざ苦労してNodeとPythonでそれぞれコンテナを作ってマルチコンテナ開発環境にしたのですが、、、結局NodeもPythonも１つのコンテナにまとめることにしました。

### １つのコンテナにまとめた理由

シンプルにコンテナの切り替えがめんどくさかったです。笑

当初の思想としては、
`フロントの開発はNodeのコンテナで、バックの開発はPythonのコンテナで`
というようにそれぞれ明確に開発環境を分けるつもりでした。

それに伴い、以下のようにコンテナの挙動を制御してました。

- フロントのコンテナにリモートアクセスして開発するとき
    - フロント：何もしない
    - バック：起動時にバックエンドのサーバーを起動する
- バックのコンテナにリモートアクセスして開発するとき
    - フロント：起動時にフロントエンドのサーバーを起動する
    - バック：何もしない

でも結局フロントの挙動を変えつつ、バックも挙動を変えたい、みたいなことが多発してしまい、、、もう１つのコンテナで全部やろうよ！！！ってなりました。

もっと大規模かつ複数人で実装範囲を分担するときはマルチコンテナでよかったと思いますが、個人で開発する分には全部１つのコンテナにまとまってる方が便利でした。

### マルチコンテナ開発の構成

```text:ディレクトリ構成
.
├── .devcontainer
│   ├── backend
│   │   └── devcontainer.json
│   ├── frontend
│   │   └── devcontainer.json
│   ├── docker-compose_backend.yml
│   └── docker-compose_frontend.yml
│
├── backend
│   ├── app
│   └── Dockerfile
│
└── frontend
    ├── node_modules
    ├── src
    └── Dockerfile
```

上記のようにフロントとバックにそれぞれ`devcontainer.json`, `docker-compose.yml`, `Dockerfile`を用意してました。

個人的にここで肝になるのが`docker-compose.yml`を２つ分けたことです。

フロント用とバック用のどちらの`docker-compose.yml`を読み込んでも、呼び出すDockerfileは同じです。
しかし、それぞれのDockerfileに対して最終的に実行させるコマンドが異なります。

開発対象のサーバーのコンテナでは`command: ["sleep", "infinity"]`を指定してサーバーを起動させず、もう片方のコンテナではそれぞれサーバー起動のコマンドを指定しています。

Dockerfileだけではここまで柔軟に対応しきれませんでした。（もしかしたらもっといい方法があるかもしれないですが、現時点の私ではこれしか思いつきませんでした。）

```yml:docker-compose_frontend.yml
version: "3.8"

services:
  frontend:
    build:
      context: ..
      dockerfile: frontend/Dockerfile
    volumes:
      - ..:/home/devuser/workspace
      - node_modules:/home/devuser/workspace/frontend/node_modules
    working_dir: /home/devuser/workspace/frontend
    ports:
      - "3000:3000"
    command: ["sleep", "infinity"]  # フロントはサーバーを起動しない

  backend:
    build:
      context: ..
      dockerfile: backend/Dockerfile
    volumes:
      - ..:/home/devuser/workspace
    working_dir: /home/devuser/workspace/backend
    ports:
      - "8000:8000"
    command: ["/home/devuser/venv/bin/uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] # FastAPIのサーバーを起動する

volumes:
  node_modules:
```

```yml:docker-compose_backend.yml
version: "3.8"

services:
  frontend:
    build:
      context: ..
      dockerfile: frontend/Dockerfile
    volumes:
      - ..:/home/devuser/workspace
      - node_modules:/home/devuser/workspace/frontend/node_modules
    working_dir: /home/devuser/workspace/frontend
    ports:
      - "3000:3000"
    command: ["npm", "run", "dev"] # Reactのサーバーを起動する

  backend:
    build:
      context: ..
      dockerfile: backend/Dockerfile
    volumes:
      - ..:/home/devuser/workspace
    working_dir: /home/devuser/workspace/backend
    ports:
      - "8000:8000"
    command: ["sleep", "infinity"] # バックはサーバーを起動しない

volumes:
  node_modules:
```

Dockerfileとdevcontainer.jsonについては説明は省略します。気になる方はリポジトリを参照ください。（2025/06/12時点では公開中）

https://github.com/midorigameee/React-Image-Generator/tree/develop

### シングルコンテナ開発の構成

結論からというと以下のようにしました。

```text:ディレクトリ構成
.
├── .devcontainer
│   └── develop
│       ├── devcontainer.json
│       └── docker-compose.yml
│
├── backend
│   └── app
│
├── frontend
│   ├── node_modules
│   └── src
│
└── Dockerfile
```

正直、わざわざdevelopフォルダを作って、そこに`devcontainer.json`と`docker-compose.yml`を入れる必要はないんですが、現在のリポジトリはマルチコンテナ用の各種設定ファイルも共存しているため、このような形になっています。

シングルコンテナだけなら.devcontainer直下に置いていいと思います。

この構成で個人的に肝になっているのはDockerfileがルート直下にあることです。

理由としては、Dockerfile内でbackendとfrontendを操作するときに、操作対象が下位のディレクトリにあった方が直観的にコマンドを記述できるからです。

最初は.devcontainer/developにDockerfileもまとめていたのですが、相対パスでのディレクトリ操作で頭がごちゃごちゃになるので止めました。
「backendはカレントディレクトリ（.devcontainer/develop）の2個上の1個下だから、`../../backend`で、、、」って考えるのが大変でした。笑

また、docker-compose.ymlでは何もサーバーが起動しないように`command: ["sleep", "infinity"]`を指定しています。

シングルコンテナの場合、Dockerfileで`sleep infinity`を実行すればいいのかなと思いましたが、そうするとDockerfileのビルドが永遠に終わりませんでした。（冷静に考えたら当たり前）

```yml:docker-compose.yml
version: "3.8"

services:
  fullstack:
    build:
      context: ../../
      dockerfile: Dockerfile
    volumes:
      - ../../:/home/devuser/workspace
      - ../../frontend/node_modules:/home/devuser/workspace/frontend/node_modules
    working_dir: /home/devuser/workspace
    ports:
      - "3000:3000"
      - "8000:8000"
    command: ["sleep", "infinity"]

volumes:
  node_modules:
```

Dockerfileとdevcontainer.jsonについては説明は省略します。気になる方はリポジトリを参照ください。（2025/06/12時点では公開中）

## おわりに

開発環境構築ほんとうに難しいです。（n回目）
実装はChat GPTに聞けばだいたい解決するけど、開発環境はなかなか上手くはいかない。

複数の言語で同時に開発するときのベストプラクティスとかもっと調べればよかったかも。というよりあるのかな？

これからも色々試行錯誤しながら自分的にしっくりくる構成を探していきたいですね。

Dockerfileとdocker-compose.ymlとdevcontaier.jsonの立ち位置やコマンドの実行順があいまいになってきたので、どこかで一度まとめたい。。。