Docker を使用したデプロイ
Docker (新しいタブで開きます) イメージを構築することは、あらゆる種類のアプリケーションをデプロイする一般的な方法です。ただし、モノレポからこれを行うには、いくつかの課題があります。
問題点
要約: モノレポでは、無関係な変更によってアプリをデプロイする際に Docker が不必要な作業を行う可能性があります。
次のようなモノレポがあるとしましょう。
├── apps
│ ├── docs
│ │ ├── server.js
│ │ └── package.json
│ └── web
│ └── package.json
├── package.json
└── package-lock.json
Docker を使用して apps/docs
をデプロイしたいので、Dockerfile を作成します。
FROM node:16
WORKDIR /usr/src/app
# Copy root package.json and lockfile
COPY package.json ./
COPY package-lock.json ./
# Copy the docs package.json
COPY apps/docs/package.json ./apps/docs/package.json
RUN npm install
# Copy app source
COPY . .
EXPOSE 8080
CMD [ "node", "apps/docs/server.js" ]
これにより、ルートの package.json
とルートのロックファイルが docker イメージにコピーされます。次に、依存関係をインストールし、アプリのソースをコピーしてアプリを起動します。
また、.dockerignore
ファイルを作成して、node_modules がアプリのソースと一緒にコピーされないようにする必要があります。
node_modules
npm-debug.log
ロックファイルが頻繁に変更される
Docker は、アプリのデプロイ方法に関して非常に優れています。Turbo と同様に、可能な限り少ない作業で済むように (新しいタブで開きます)努めています。
Dockerfile の場合、イメージ内のファイルが以前の実行時と異なる場合にのみ、npm install
を実行します。そうでない場合は、以前の node_modules
ディレクトリを復元します。
つまり、package.json
、apps/docs/package.json
、または package-lock.json
が変更されるたびに、docker イメージは npm install
を実行します。
これは素晴らしいように聞こえます。しかし、あることに気づくとそうではありません。package-lock.json
はモノレポに対してグローバルです。つまり、apps/web
内に新しいパッケージをインストールすると、apps/docs
が再デプロイされることになります。
大規模なモノレポでは、モノレポのロックファイルへの変更が数十または数百のデプロイに波及するため、これは膨大な時間のロスにつながる可能性があります。
解決策
解決策は、Dockerfile への入力を厳密に必要なもののみに絞ることです。Turborepo は、簡単な解決策を提供します。それは、turbo prune
です。
turbo prune docs --docker
このコマンドを実行すると、./out
ディレクトリ内にモノレポの枝刈りバージョンが作成されます。これには、docs
が依存するワークスペースのみが含まれます。
重要な点として、関連するnode_modules
のみがダウンロードされるように、ロックファイルを整理(プルーニング)します。
--docker
フラグ
デフォルトでは、turbo prune
はすべての関連ファイルを./out
の中に配置します。しかし、Dockerでのキャッシュを最適化するために、ファイルを2段階でコピーすることが理想的です。
まず、パッケージをインストールするために必要なものだけをコピーしたいと考えます。--docker
を実行すると、これは./out/json
の中にあるでしょう。
out
├── json
│ ├── apps
│ │ └── docs
│ │ └── package.json
│ └── package.json
├── full
│ ├── apps
│ │ └── docs
│ │ ├── server.js
│ │ └── package.json
│ ├── package.json
│ └── turbo.json
└── package-lock.json
その後、./out/full
のファイルをコピーしてソースファイルを追加できます。
このようにして、依存関係とソースファイルを分割することで、依存関係が変更されたときにのみnpm install
を実行することができ、大幅な高速化につながります。
--docker
がない場合、プルーニングされたすべてのファイルは./out
の中に配置されます。
例
詳細なwith-docker
の例 (新しいタブで開きます)では、prune
を最大限に活用する方法について詳しく説明しています。便宜上、Dockerfileをコピーして以下に示します。
このDockerfileは、standalone
の出力モード (新しいタブで開きます)を使用しているNext.js (新しいタブで開きます)アプリ用に記述されています。
FROM node:18-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
RUN apk update
# Set working directory
WORKDIR /app
RUN yarn global add turbo
COPY . .
RUN turbo prune web --docker
# Add lockfile and package.json's of isolated subworkspace
FROM base AS installer
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN yarn install
# Build the project
COPY --from=builder /app/out/full/ .
RUN yarn turbo run build --filter=web...
FROM base AS runner
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
COPY --from=installer /app/apps/web/next.config.js .
COPY --from=installer /app/apps/web/package.json .
# Automatically leverage output traces to reduce image size
# https://nextjs.dokyumento.jp/docs/advanced-features/output-file-tracing
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
CMD node apps/web/server.js
リモートキャッシュ
Dockerビルド中にリモートキャッシュを利用するには、ビルドコンテナがリモートキャッシュにアクセスするための認証情報を持っていることを確認する必要があります。
Dockerイメージ内でシークレットを管理する方法はたくさんあります。ここでは、最終的なイメージでは非表示になるビルド引数としてシークレットを使用する、マルチステージビルドによるシンプルな戦略を使用します。
上記のDockerfileと同様のものを使用していると仮定して、turbo build
の直前に、ビルド引数からいくつかの環境変数を取り込みます。
ARG TURBO_TEAM
ENV TURBO_TEAM=$TURBO_TEAM
ARG TURBO_TOKEN
ENV TURBO_TOKEN=$TURBO_TOKEN
RUN yarn turbo run build --filter=web...
これでturbo
はリモートキャッシュにヒットできるようになります。キャッシュされていないDockerビルドイメージでのTurborepoのキャッシュヒットを確認するには、プロジェクトルートから次のようなコマンドを実行します。
docker build -f apps/web/Dockerfile . --build-arg TURBO_TEAM=“your-team-name” --build-arg TURBO_TOKEN=“your-token“ --no-cache