1章 コピペに頼らない。ちゃんと理解するDocker(docker-compose)構築:Next.jsとNest.jsの環境構築
前置き
Dockerでプロジェクトを作成する際の環境構築で、公式ドキュメントを見たり、色々な記事を見ることになると思う。
コピペで良い感じに動くのもありつつ、色々な記事を見ると手順が変わるのもあり、ちゃんと分かってないと混乱することになる。
今回は、私自身が今後スムーズにDockerで環境を構築できるように
汎用的な方法として残しておこうと思う。
例を挙げるとNest.jsの公式ドキュメントの導入では、次のコマンドを実行するように記述されている
$ npm i -g @nestjs/cli
$ nest new project-name
Dockerで環境構築をやろうとすると、このようなコマンドは直接、そのまま打たないことが多い。
次のようにDockerの立ち上げ時にオプションで指定したりする。
$ docker-compose run nodejs npx @nestjs/cli new project-name
記事にあるソースをある程度コピペしても良い感じに動くことも多いが、行き詰って別の記事を見ると前提が違ったりで、混乱することもある。
それで今回は、プロジェクトを開始するにあたって、docker-compose.ymlファイルの基本構成や
イメージの選択から丁寧にやっていこうと考えたので、軌跡として残す。
コピペしているとどんな問題が起こり得るのか
例えば、Dockerfileのベースイメージとして、次の記述があったとする。
FROM node:20-alpine
これはnodeのベースイメージだが、alpineの記述がある。
nodeのベースイメージには、bullseyeやalpine、その他もあり、かなり雑に書くと次のような違いがある。
- bullseye
色々揃っててサイズ(容量)が大き目。 - alpine
最低限のライブラリしか入っておらず、非常に軽量。
ライブラリが最低限なのもあって、セキュリティはbullseyeよりは良いらしい。
しかし、ネイティブモジュールを使用するとなれば、素直には動いてくれないことがあるとのこと。
ネイティブモジュールというと、CやC++を使用するライブラリ(libc等)を使用するモジュールを指すようだ。
ネイティブモジュールを使用しないならalpineでよさそうだが、画像圧縮のためにsharp等のライブラリを使用する場合は注意が必要である。
このライブラリのpackage.jsonを見るとlibcを利用しているようなので、ネイティブモジュールと言える。
他には、パスワードなどの文字列のハッシュ化のライブラリの中にはネイティブモジュールもある。
勿論、必要なパッケージを随時いれることもできるし、問題を解決する方法も世の中に記事はあるが、これを知らずに開発を進めると、大変な思いをする可能性もある。
これからやっていくこと
- ベースイメージの選定
- docker-compose.ymlの作成
- Dockerfileの作成
- プロジェクトの初期化
- 5.Dockerfileとdocker-compose.ymlの修正
1.ベースイメージの選定
前述したように、今回のプロジェクトではNext.jsとNest.jsなのでNodeが必要になる。
Nodeを使用するなら下記のようなデータも見ていきたい。
2025/1/25現在、LTS(Long-Term Support)となっているのはNode.js 22である。
また画像を扱う必要もあり、ネイティブモジュールを使う可能性も大いにあり得る。
ということで、今回は22.13.1-bullseyeを利用する。
また、実行環境としてARM、X86-64の両方で動作できるように、対応するOS/ARCHも気にしておく。
2.docker-compose.ymlを作成する
ディレクトリ構造
project/
├── frontend/
├── backend/
└── docker-compose.yml
各設定項目の内容をコメントに記述する
docker-compose.yml
# バージョン指定
version: '3.8'
# サービスを列挙する(docker psで確認できるコンテナ一覧の単位)
services:
# サービス名
frontend:
# コンテナ名
container_name: frontend
# ソースからコンテナイメージを作成するためのビルド構成を指定する
build:
# Dockerfileを含むディレクトリへのパス、またはGitリポジトリへのURLを定義する。
# 指定が無ければデフォルトの(./)となる。
context: ./frontend
# 代替Dockerfileを設定する(おそらくDockerfileの名前)
dockerfile: Dockerfile
# コンテナからアクセス可能なホストのパスを指定する
volumes:
# バインドマウントのパスを指定
- ./frontend:/app
# 名前付きボリュームを指定
# - frontend-node-modules:/app/node_modules # 最初はコメントアウトしておく
# ホストマシンとコンテナ間のポートマッピング
ports:
# ホストのポート:コンテナのポート
- "3000:3000"
# 割り当てられた stdin を使用して実行するようにサービスのコンテナーを構成する。
# コンテナをSTDIN開いたままにして、標準入力を通じてコンテナに入力を送信できるようにする
stdin_open: true
# 疑似 TTY をコンテナに接続し、ターミナルをコンテナの I/O ストリームに接続する。
# 疑似 TTY をコンテナに割り当てると、TTY デバイスが提供する入出力機能にアクセスできる
# ようになる
tty: true
# サービスが相互に通信できるようになる
# 詳細:https://docs.docker.com/reference/compose-file/networks/
networks:
# ネットワーク名
- project_network
backend:
container_name: backend
build:
context: ./backend
dockerfile: Dockerfile
volumes:
- ./backend:/app
# - backend-node-modules:/app/node_modules # 最初はコメントアウトしておく
ports:
- "3001:3001"
stdin_open: true
tty: true
depends_on:
- db
networks:
- project_network
db:
container_name: project_mysql
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: project
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- project_network
# 名前付きボリューム
volumes:
mysql_data:
# frontend-node-modules: # 最初はコメントアウトしておく
# backend-node-modules: # 最初はコメントアウトしておく
# サービスが相互に通信できるようにする設定
networks:
# 名前
project_network:
# ネットワーク名
name: project_network
# デフォルトのネットワークドライバ
# 詳細:https://docs.docker.com/engine/network/drivers/
driver: bridge
Docker公式ドキュメント https://docs.docker.com/compose/gettingstarted/
docker-compose.yml リファレンス https://docs.docker.com/reference/compose-file/
3.Dockerfileを作成する
project/
├─ frontend/
│ └─Dockerfile
├─ backend/
│ └─Dockerfile
└─ docker-compose.yml
フロントエンド Next.js
フロントエンドはNext.jsなので、ベースイメージの起動だけにしておく
# frontend/Dockerfile
FROM node:22.13.1-bullseye
WORKDIR /app
バックエンド Nest.js
Nest.jsのプロジェクトのFirst stepsに次の記述があるので、ベースイメージの起動に加え、nestjs/cliのインストールだけはDockerのビルド時にやっておく
$ npm i -g @nestjs/cli
$ nest new project-name
# backend/Dockerfile
FROM node:22.13.1-bullseye
RUN npm i -g @nestjs/cli
WORKDIR /app
4.プロジェクトの初期化
コンテナを起動する。
上記docker-compose.ymlを配置したディレクトリで次のコマンドを実行すると、コンテナが3つ起動する。
$ docker compose up
起動するコンテナ
$ docker ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
backend crew-management-backend "docker-entrypoint.s…" backend 8 seconds ago Up 6 seconds 0.0.0.0:3001->3001/tcp
frontend crew-management-frontend "docker-entrypoint.s…" frontend 8 seconds ago Up 7 seconds 0.0.0.0:3000->3000/tcp
mysql mysql:8.0 "docker-entrypoint.s…" db 8 seconds ago Up 7 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp
※ここから、"コンテナに入る"と記述した際は、次のコマンドを使用してコンテナと対話状態にすることを言う。
# 例
$ docker compose exec frontend bash
# node:23-alpine3.20等の場合はbashではなくshとなる
フロントエンド(Next.js)プロジェクト初期化
bashに入った状態で、プロジェクトを初期化するが
Dockerfile等、他のファイルがあると失敗するので、一度Dockerfileを別のディレクトリに移動しておき、プロジェクトの初期化後にDockerfileを戻す。
※最後の「.」を忘れずに。
# mv Dockerfile ../
# npx create-next-app@latest .
~~~~~
# mv ../Dockerfile ./
Next.jsの最新バージョンでプロジェクトを初期化する。
この時色々聞かれるので選択していく。
インストールするnextのパッケージのバージョン
Need to install the following packages:
create-next-app@15.1.6
Ok to proceed? (y)
カレントディレクトリ指定せずにnpx create-next-app@latestした場合、プロジェクト名を聞かれる
プロジェクト名
What is your project named? …
TypeScriptを使用するか
Would you like to use TypeScript? … No / Yes
ESLintを使用するか
Would you like to use ESLint? … No / Yes
CSSでTailwindを使用するか
Would you like to use Tailwind CSS? … No / Yes
プロジェクトのソースをsrc/ディレクトリに配置するかどうか
※pagesやappディレクトリをsrc配下に配置するかどうか。
Would you like your code inside a `src/` directory? … No / Yes
画面のルーティングにApp routerを使用するか
Next.js 13.4移行、安定板としてリリースされているので、使わない理由は特にないでしょう。
Would you like to use App Router? (recommended) … No / Yes
Turbopackを有効にするか
※記事執筆時2025/1/25では、まだ開発環境のみ対応
Would you like to use Turbopack for `next dev`? … No / Yes
インポートエイリアスの設定をカスタマイズするかどうか
デフォルトでは'@/*'が設定されているが、それを変更するか
Would you like to customize the import alias (`@/*` by default)? … No / Yes
What import alias would you like configured? … @/*
プロジェクトを起動して画面が立ち上がるか確認する。
※npmの場合
# npm run dev
ブラウザからlocalhost:3000にアクセスして、Next.jsの初期画面が表示されればOK
バックエンド(Nest.js)プロジェクト初期化
Nest.jsのプロジェクト初期化
nest new .
どのパッケージ管理ツールを使用するかを聞かれるので、お好きなパッケージ管理ツールを選択する。
Which package manager would you ❤ to use?
npm
yarn
pnpm
今回は、フロントエンドをポート3000番、バックエンドをポート3001番でDockerを起動しているので
main.tsのポート指定部分を変更する。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
- await app.listen(process.env.PORT ?? 3000);
+ await app.listen(process.env.PORT ?? 3001);
}
bootstrap();
プロジェクトを起動して、レスポンスを確認する。
# npm run start:dev
ブラウザからlocalhost:3001にアクセスしてHello World!が表示されればOK
VScode等のエディタから権限エラーによって編集できない場合は権限を変更する
projectディレクトリ配下の全てのファイルとディレクトリのユーザー、およびグループを変更する。
sudo chown -R <new_user>:<new_group> /path/to/project
5.Dockerfileとdocker-compose.ymlの修正
プロジェクトを作成したら、次回からはdocker compose up時にアプリが起動するように修正していく。
フロントエンドのDockerfileを修正する
FROM node:23-bullseye
WORKDIR /app
+# パッケージファイルのみをコピー
+COPY package*.json ./
+# 依存関係のインストール
+RUN npm install
+# ソースコードをコピー
+COPY . .
+# コンテナ起動時のコマンド
+CMD ["npm", "run", "dev"]
バックエンドのDockerfileを修正する
FROM node:23-bullseye
WORKDIR /app
RUN npm i -g @nestjs/cli
+# パッケージファイルのみをコピー
+COPY package.json package-lock.json ./
+# 依存関係のインストール
+RUN npm install
+# ソースコードをコピー
+COPY . .
+# コンテナ起動時のコマンド
+CMD ["npm", "run", "start:dev"]
docker-compoe.ymlの修正
docker-compose.ymlの、以下のようにコメントアウトしていた部分を修正する。
volumes:
- ./frontend:/app
- # - frontend-node-modules:/app/node_modules # 最初はコメントアウトしておく
+ - frontend-node-modules:/app/node_modules
~~~~~
volumes:
- ./backend:/app
- # - backend-node-modules:/app/node_modules # 最初はコメントアウトしておく
+ - backend-node-modules:/app/node_modules
~~~~~
volumes:
mysql_data:
- # frontend-node-modules: # 最初はコメントアウトしておく
- # backend-node-modules: # 最初はコメントアウトしておく
+ frontend-node-modules:
+ backend-node-modules:
node_modulesディレクトリは名前付きボリュームにする。
これをしないと、npm install後にローカルのnodu_modulesが上書きされ、moduleがインストールされていない状態になる。
dockerでのnode_modulesに関して
coming soon...
再度ビルドする
Dockerfileを修正したら再度ビルドする
$ docker compose up --build
これをしないとDockerfileの修正が反映されない。
次回から、docker compose downを実行した後にdocker compose upを実行すると、Dockerの起動と共にフロントエンド/バックエンドの両方が起動する。
次回はフロントエンドとバックエンドの疎通確認を行う。
Dockerで構築したNext.js、Nest.js、MySQL、Prismaで疎通確認を行う
comming soon...
by 株式会社DELTA
[CTO Booster]
[VersionUp booster]
Discussion