この言語/ライブラリ/フレームワークをとりあえず触ってみたいというとき

「Next.jsとFastAPIでフロントエンドとバックエンドを分けて開発するのを試してみたい」
のように、今まで触ったことがないものを試してみたいというケースはよくあるかと思います。
そういうときにとりあえず始めてみるのにDockerが役に立ちます。

※あくまでサッと確かめることを目的としているため、ビルドの最適化やパフォーマンスチューニング等は考慮していません。

# Docker/docker composeを使って確かめる

Next.jsを試すのにnode.jsが必要ですが、既にインストール済みだったりすると、一度アンインストールしたり、バージョン管理をできるようにnvmをインストールしたりと、始めるまでに前段のステップがいくつか発生します。
Docker Desktopを入れてPC上にDocker環境を整えれば、
欲しいものは大体Dockerが拾ってきてくれます。
※Docker Desktopを入れるという前段が発生しとるやないかいというツッコミはごもっともですが。

# Dockerを使える状態にする

https://www.docker.com/ja-jp/products/docker-desktop/

個人利用なら無料で使えます。
とりあえずDocker Desktopをインストールすれば前段はほぼ完了です。

# サクッとやるためのテンプレートを作っておく

Dockerが使えるようになったら、以下2ファイル用意すれば大体の環境構築が可能です。

* Dockerfile（機能ごとに用意したほうが楽）
* docker-compose.yml
* .env（これはなくてもOK）

大体の環境構築で似たような構成になるので、一度作ってしまえば後はコピペでOKです。

それぞれ以下のようなファイルです。
サンプルとして上で上げた「Next.jsとFastAPIを確かめるための環境」を作ってみます。

フォルダ構成は以下のような感じ
```
/test_dir（フォルダ名はなんでもOK）
 ┣━/next_js
 ┃  ┗━Dockerfile
 ┣━/fast_api
 ┃  ┗━Dockerfile
 ┣━docker-compose.yml
 ┗━.env
```

```Dockerfile:/test_dir/next_js/Dockerfile
FROM node:latest

ADD . /app
WORKDIR /app

RUN apt-get update -y && \
    apt-get install -y git curl vim procps
```

```Dockerfile:/test_dir/fast_api/Dockerfile
FROM python:latest

ADD . /app
WORKDIR /app

RUN apt-get update -y && \
    apt-get install -y git curl vim procps
```

## `FROM`について

Next.jsはnode.jsを使うのでnode.jsが入っているイメージを使います。
同様にFastAPIはpythonを使うのでpythonのイメージです。

それぞれどんなイメージがあるのかはDockerHubのサイトにいって検索すると早いです。

https://hub.docker.com/

とりあえず公式のイメージを使っておけば間違いないです。
上記ではどちらも`latest`という最新版を取ってくる書き方をしていますが、
特定のバージョンが良ければ`node:20`のように指定します。

また、特に指定しなければOSがdebianになりますが、
`node:20-alpine`のように指定することでalpineを使えます。
基本的にはdebianかalpineの2択です。
どちらも軽量で使いやすい（alpineのほうが軽量）ですが、ライブラリのインストールするときのコマンドが
`apt`か`apk`かで異なり、alpineのほうは特にARM系では用意されていないものなどあったりしてサッと使いたいときに面倒なので個人的にはdebianの方を使っています。
※本番環境構築する際はパフォーマンスなどを考慮しながら最適な方を選びます。

```yml:docker-compose.yml
services:
  next_js:
    build: ./next_js
    volumes:
      - ./next_js:/app
    ports:
      - "${NEXT_JS_PORT-3000}:3000"
    tty: true

  fast_api:
    build: ./fast_api
    volumes:
      - ./fast_api:/app
    ports:
      - "${FAST_API_PORT-8000}:8000"
    tty: true
```

## `tty: true`について

これは立ち上げたコンテナを常時起動状態にしておくためのオプションです。
基本コンテナは立ち上がると指定されたコマンドを実行して終了しようとします。
上記のように何も指定がないと起動完了と共に終了してしまいます。
それを防ぐために使用します。

コンテナの中に入ってコマンドを入力したいときなどにも使えますし、
例えばNext.jsの開発中のときも、docker-composeなどで
```yml
command: npm run dev
```
のように指定しておけば`tty: true`は不要ですが、
あえてこれを使わず、
```
docker compose exec next_js npm run dev
```
で起動しておけば、標準出力をそのまま確認できますし、Next.jsを再起動するのにわざわざコンテンを再起動する必要もなくなります。
好みの問題も大きいですが、僕は最初の環境構築段階だったり、細かいデバッグ作業の場合にこの方法をよく使います。

---

```:.env
NEXT_JS_PORT=3001
FAST_API_PORT=8001
```

## `${NEXT_JS_PORT-3000}`の書き方と`.env`について

docker-compose.yml上で環境変数を使いたい場合に`${変数名-初期値}`という形で使えます。
`${NEXT_JS_PORT-3000}`だと、NEXT_JS_PORTの変数の値を使うけどなければ3000にするという意味です。

かつ、docker-compose.ymlと同じフォルダに.envがある場合、
その内容を環境変数として読み込んでくれます。
上記の場合はポート番号を変更したい場合に使います。

自分で使う場合は特に意識しなくていいですが、
複数人で共有するときとかポート番号が被って起動しないみたいなケースがあったりするので、
あらかじめ変数にしておくと変更が楽です。

---

とりあえず上記のファイルを用意したらターミナル等で`/test_dir`に移動して、

```
docker compose up -d
```

を実行します。
エラー等がなければそれぞれの環境が立ち上がります。

# フレームワークの用意

上記はまだnode.jsとpythonが使えるようになっただけなので、
今度はNext.jsとFastAPIを使えるようにします。

## Next.js

まずはコンテナの中に入ります。

```
docker compose exec next_js bash
```

次にNext.jsインストールコマンドを実行します

基本的には公式の導入を参考にします。

https://nextjs.org/docs/app/getting-started/installation

```bash
npx create-next-app@latest
# 対話式でアプリ等が聞かれるので答えていきます。
# 基本的に最初のアプリ名以外は初期選択のまま決定でOKです。
✔ What is your project named? … app
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like your code inside a `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to use Turbopack for `next dev`? … No / Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes
```

上記でappというフォルダにNext.jsのソースコード一式が入ります。
※コンテナのカレントディレクトリもappにしてるので/app/appになってしまいましたが今回はこれで進めます。

```bash
# appディレクトリに入って起動
cd app
npm run dev
# yarn派の人は yarn installの後、yarn devで起動してください。
```

これでNext.jsが動いたので http://localhost:3001 にアクセスします。（ポート番号変更している方は適宜変更を）
![スクリーンショット 2025-01-24 22.49.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13210/155266ea-205e-6f72-5479-b4771bfd0b17.png)

## FastAPI

次にFastAPIを入れます。
別のターミナルを立てるなどして今度はpythonの方のコンテナに入ります。

```
docker compose exec fast_api bash
```

次にFastAPIをインストールします。
こちらも基本的には公式を参考にします。

https://fastapi.tiangolo.com/

```bash
pip install "fastapi[standard]"
# WEBアプリとして起動する用のライブラリも追加します。
pip install uvicorn
```

こちらはNext.jsと違い、サンプルなどは生成されないのでとりあえず簡単なAPIを作成します。
下記は公式のドキュメントに書かれているものです。

```python:main.py
from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}
```

```bash
# Docker外からアクセスするのでオプションを追加して起動
fastapi dev main.py --host 0.0.0.0
```

これでFastAPIが動いたので http://localhost:8001 にアクセスします。（ポート番号変更している方は適宜変更を）
![スクリーンショット 2025-01-24 23.09.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13210/b20039ba-a4d3-f89d-93a3-a42c4a6dc45b.png)

# ２つをつなげてみる

フロントエンドからバックエンドを呼び出して実行してみます。

とりあえず簡単なサンプルをNext.js側に作成します。

```tsx:next_js/app/app/test/page.tsx
export default async function Page() {
  const items = await Promise.all([
    fetch("http://fast_api:8000/items/1?q=アイテム1").then((res) => res.json()),
    fetch("http://fast_api:8000/items/2?q=アイテム2").then((res) => res.json()),
    fetch("http://fast_api:8000/items/3?q=アイテム3").then((res) => res.json()),
  ]);

  return (
    <>
      <main className="w-9/12 mx-auto my-10">
        <h1 className="text-2xl">テスト表示</h1>
        {items.map((item) => (
          <p key={item.item_id} className="mt-5 font-bold">
            {item.item_id}: {item.q}
          </p>
        ))}
      </main>
    </>
  )
}

```

上記を作成して http://localhost:3001/test にアクセスします。

![スクリーンショット 2025-01-24 23.38.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13210/b2cedf94-2bcb-f2b2-f8b4-68582424cad8.png)

無事、FastAPIのAPIを呼び出して表示させることができました。

昔はDockerのコンテナ間で連携させる場合、docker-compose上でlinkなどでどのコンテナがどのコンテナにアクセス可能かを指定していましたが、今は基本的に繋げられるようになっています。
いい時代になりました。

# おまけ

環境設定周りは触らず、Next.jsやFastAPIの再起動が不要というケースの場合は、
上記のDockerfileやdocker-compose.ymlを修正してもいいです。

```diff_docker:/test_dir/fast_api/Dockerfile
FROM python:latest

ADD . /app
WORKDIR /app

RUN apt-get update -y && \
    apt-get install -y git curl vim procps

+# 最初からインストールしておく
+RUN pip install "fastapi[standard]" uvicorn
```

```diff_yaml:docker-compose.yml
services:
  next_js:
    build: ./next_js
+    command: /bin/bash -c "cd app && npm install && npm run dev" # 複数コマンド実行の場合はこの記述
    volumes:
      - ./next_js:/app
    ports:
      - "${NEXT_JS_PORT-3000}:3000"
-    tty: true # 削除してもしなくてもOK

  fast_api:
    build: ./fast_api
+    command: fastapi dev main.py --host 0.0.0.0
    volumes:
      - ./fast_api:/app
    ports:
      - "${FAST_API_PORT-8000}:8000"
-    tty: true # 削除してもしなくてもOK
```

この場合、いちいち起動コマンドが不要になるのでコンテナを立ち上げるだけでOKです。
```
docker compose up -d
```

# 最後に

上記はNext.jsとFastAPIを例にしましたが、
RubyOnRails、Django、Nuxt.js、ginなどなど大体の言語やフレームワークも同じような形で対応できます。

Dockerのいいところは、途中でインストールするライブラリを間違えたり、インストールするバージョンを間違えたりしてもコンテナを削除して起動しなせば何度でもやり直しがきくことです。

また、最近はECS Fargateに限らずコンテナをそのままデプロイできるような仕組みも多くなったので、
Dockerで開発できるとインフラに詳しくなくてもデプロイまでできるようになりました。

皆さんもよりよいDockerライフをお送りください。

ちなみに、今回の例よりもさらにサッと確認できるという方法ご存知でしたら教えて下さい