リポジトリ
ドキュメント
Docker を使用したデプロイ

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.jsonapps/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