🕌
NestJSをDocker化する
概要
Dockerを使用して、開発環境と本番環境の両方でNestJSアプリを実行する方法について紹介します。
工夫ポイント
NestJSアプリをDockerで構築する際のいくつかの工夫ポイントを紹介します。
- マルチステージビルド: イメージサイズを抑えるために、マルチステージビルドを使用します。これにより、開発環境と本番環境で必要なファイルのみをコピーし、不要なファイルを削除することができます。
- tini: PID 1問題に対処するために、tiniを使用します。tiniは、シグナルの適切な処理やゾンビプロセスの回収など、PID 1プロセスとして適切に機能するためのツールです。
- nodeユーザー: なるべくnodeユーザーを使用します。これにより、コンテナ内でrootユーザーとして実行されることを回避し、セキュリティを向上させます。
- ローカルファイルのマウント: 開発時には、ローカルのファイルをコンテナにマウントして、変更をリアルタイムで反映させることができます。
使い方
以下に、開発環境と本番環境(ローカル検証時)でのコンテナの起動方法を示します。
- 開発環境
docker compose up --build
- 本番環境(ローカル検証時)
docker build . -t test
docker run --rm -e TZ=Asia/Tokyo -p 3000:3000 test
コード
以下に、NestJSアプリケーションをDockerで構築するために必要なDockerfileとdocker-compose.ymlを示します。
Dockerfile
Dockerfileは、マルチステージビルドを利用しています。開発用(development)、ビルド用(build)、本番用(production)の3つのステージで構成されています。
DEVELOPMENTステージ
Dockerfile
FROM node:20.0.0-slim AS development
# 作業ディレクトリを/usr/src/appに設定しています。
WORKDIR /usr/src/app
# tzdataはタイムゾーンデータを提供し、tiniはPID 1問題に対処するために使用されます。
RUN apt-get update && apt-get -qq install -y --no-install-recommends \
tzdata \
tini \
&& rm -rf /var/lib/apt/lists/*
# 環境変数NODE_ENVをdevelopmentに設定しています。
ENV NODE_ENV development
# package.jsonとpackage-lock.jsonをコピーし、所有者をnodeユーザーとnodeグループに設定しています。
COPY package*.json ./
# package-lock.jsonに記載された依存関係をインストールしています。
RUN npm ci
# アプリケーションのソースコードをコピーし、所有者をnodeユーザーとnodeグループに設定しています。
COPY . .
USER node
PRODUCTION BUILDステージ
Dockerfile
FROM node:20.0.0-slim As build
WORKDIR /usr/src/app
ENV NODE_ENV production
COPY package*.json ./
# 開発ステージでインストールしたnode_modulesディレクトリをコピーし、所有者をnodeユーザーとnodeグループに設定しています。これにより、開発ステージでインストールされたすべての依存関係を利用できます。
COPY /usr/src/app/node_modules ./node_modules
COPY . .
# NestJSアプリケーションをビルドします。これにより、distディレクトリにトランスパイルされたJavaScriptファイルが生成されます。
RUN npm run build
# npm ciコマンドを使用して、開発用の依存関係を除いた本番用の依存関係をインストールします。また、npm cache clean --forceでnpmキャッシュを削除して、イメージサイズを削減します。
RUN npm ci --omit=dev && npm cache clean --force
USER node
PRODUCTIONステージ
Dockerfile
FROM node:20.0.0-slim
WORKDIR /usr/src/app
ENV NODE_ENV production
RUN apt-get update && apt-get -qq install -y --no-install-recommends \
tzdata \
tini \
&& rm -rf /var/lib/apt/lists/*
COPY /usr/src/app/package.json ./
COPY /usr/src/app/node_modules ./node_modules
COPY /usr/src/app/dist ./dist
EXPOSE 3000
USER node
# エントリーポイントにtiniを指定し、--オプションを渡しています。これにより、tiniがPID 1として実行され、その後に実行されるプロセスが正しくシグナルを受け取ることができます。
ENTRYPOINT ["/usr/bin/tini", "--"]
# デフォルトのコマンドとして、トランスパイルされたmain.jsをnodeで実行します。これにより、NestJSアプリケーションが起動します。
CMD [ "node", "dist/main.js" ]
完成版
Dockerfile
###################
# DEVELOPMENT
###################
FROM node:20.0.0-slim AS development
WORKDIR /usr/src/app
RUN apt-get update && apt-get -qq install -y --no-install-recommends \
tzdata \
tini \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_ENV development
COPY package*.json ./
RUN npm ci
COPY . .
USER node
###################
# PRODUCTION BUILD
###################
FROM node:20.0.0-slim As build
WORKDIR /usr/src/app
ENV NODE_ENV production
COPY package*.json ./
COPY /usr/src/app/node_modules ./node_modules
COPY . .
RUN npm run build
RUN npm ci --omit=dev && npm cache clean --force
USER node
###################
# PRODUCTION
###################
FROM node:20.0.0-slim
WORKDIR /usr/src/app
ENV NODE_ENV production
RUN apt-get update && apt-get -qq install -y --no-install-recommends \
tzdata \
tini \
&& rm -rf /var/lib/apt/lists/*
COPY /usr/src/app/package.json ./
COPY /usr/src/app/node_modules ./node_modules
COPY /usr/src/app/dist ./dist
EXPOSE 3000
USER node
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD [ "node", "dist/main.js" ]
docker-compose.yml
- appサービスとして、カレントディレクトリをコンテキストとし、Dockerfileを使用してビルドします。また、ターゲットステージとしてdevelopmentを指定しています。
- ホストマシンとコンテナ間でソースコードを同期し、node_modulesディレクトリをコンテナ内に保持します。これにより、開発中にソースコードを変更した場合でも、リアルタイムで反映されます。
- ホストマシンのポート5000をコンテナ内のポート3000にマッピングします。これにより、ホストマシンからアプリケーションにアクセスできます。
- エントリーポイントにtiniを指定し、--オプションを渡しています。これにより、tiniがPID 1として実行され、その後に実行されるプロセスが正しくシグナルを受け取ることができます。
- 開発サーバーを起動するためのコマンドnpm run start:devを実行しています。これにより、ソースコードの変更がリアルタイムで反映され、アプリケーションが自動で再起動されます。
docker-compose.yml
version: '3'
services:
app:
build:
context: ./
dockerfile: Dockerfile
target: development
volumes:
- ./:/usr/src/app
- /usr/src/app/node_modules
ports:
- "5000:3000"
entrypoint:
- /usr/bin/tini
- --
command: npm run start:dev
参考
Discussion