こんにちは、Tsucchiiです。
とあるプロジェクトで Yarn 4 (Berry) へ移行した際、Docker 環境で
「yarn prisma
が動かない」「node_modules
が古いまま」といった問題に直面しました。
この記事では、その原因と対処法、Corepack・Volume運用を通じて得た知見を紹介します。
1. 移行の背景
Next.js + Docker の開発環境で、node_modules
を 名前付きボリューム(named volume) で切り離して管理しています。
(ソースコードは bind mount
、依存は named volume
という構成)
# docker-compose.yml(抜粋)
services:
web:
volumes:
- ./web:/app
- node_modules:/app/node_modules
# ...
volumes:
node_modules:
# ...
この構成では、entrypoint.sh
でコンテナ起動時に /app/package.json
と /app/yarn.lock
のハッシュを計算し、
前回保存した値と比較します。差分があれば(または .yarn_hash
が無ければ)
yarn install --immutable
を実行して node_modules
を最新化する仕組みになっています。
#!/bin/bash
# web/entrypoint.sh(移行前のイメージ)
if [ -f /app/.yarn_hash ]; then
OLD_HASH=$(cat /app/.yarn_hash)
CURRENT_HASH=$(md5sum /app/package.json /app/yarn.lock | sort | md5sum | cut -d' ' -f1)
if [ "$OLD_HASH" != "$CURRENT_HASH" ]; then
echo "Dependencies changed, updating node_modules..."
yarn install --immutable
echo "$CURRENT_HASH" > /app/.yarn_hash
fi
else
yarn install --immutable
md5sum /app/package.json /app/yarn.lock | sort | md5sum | cut -d' ' -f1 > /app/.yarn_hash
fi
exec "$@"
しかし当時は Dockerfile
で Yarn のバージョンを固定していなかったため、
docker-compose up
時に Yarn 1 が実行され、yarn.lock
が書き換わる問題が発生。
再現性を高めるために Yarn 4 (Berry) へ統一することにしました。
2. 最初に行った変更 ─ Corepack で Yarn を固定化
「Docker 上でも Yarn 4.6.0 を使う」ため、まず次の対応を行いました。
2-1. Dockerfile で Corepack を有効化
# docker/web/Dockerfile.dev(一部抜粋)
ARG YARN_VERSION=4.6.0
RUN corepack enable && corepack prepare "yarn@${YARN_VERSION}" --activate
RUN YARN_ENABLE_SCRIPTS=false yarn install --immutable
2-2. entrypoint でも Yarn 4 を起動時に有効化
# web/entrypoint.sh(Yarn 4 強制後)
YARN_VERSION=${YARN_VERSION:-4.6.0}
if command -v corepack >/dev/null 2>&1; then
corepack enable >/dev/null 2>&1
corepack prepare "yarn@${YARN_VERSION}" --activate >/dev/null 2>&1
fi
# この時点では 後述のPATH 補正はまだ入っていません
この時点で Dockerfile と entrypoint の双方で Yarn 4.6.0 が使われる状態になりました。
3. 変更によって発生した問題
しかし「これで移行完了」とはいかず、複数のトラブルが顕在化しました。
3-1. yarn prisma
/ yarn next
が command not found
$ yarn prisma migrate dev
Usage Error: Couldn't find a script named "prisma".
3-2. PATH
補正を忘れると next
が見つからない
Corepack で Yarn 4 を有効にしただけでは /app/node_modules/.bin
が PATH
に含まれず、
command not found: next
が頻発。
3-3. named volume の初期化で古い node_modules
がコピーされる
docker-compose down -v → docker-compose up
をすると、
新しく追加した依存だけ module not found
になるケースが発生(理由は後述)。
4. 各トラブルの原因と解決策
4-1. CLI 呼び出しを npx
/ yarn exec
に統一
Yarn 1 までは yarn prisma
や yarn next
のような 省略形 CLI が使えましたが、
Yarn 4 (Berry) ではこの形式が廃止されました。
そこで、package.json
の scripts
を npx
に統一し、Makefile や README といったドキュメントも更新します。
- "dev": "prisma generate && next dev --turbopack",
+ "dev": "npx prisma generate && npx next dev --turbopack",
コマンド | Yarn 1 | Yarn 4 |
---|---|---|
yarn next |
✅ 可 | ❌ エラー |
yarn exec next |
✅ 可 | ✅ 可 |
npx next |
✅ 可 | ✅ 可 |
4-2. entrypoint で PATH を補正
corepack enable
/ corepack prepare
は Yarn 4 の実行ファイルを用意する だけで、
シェルの PATH
には手を加えません。
Yarn 4(Berry)は yarn run
実行時に一時的に .bin
を追加しますが、
exec "$@"
で普通のシェルコマンドを起動する場合は PATH に .bin
が入らず、
command not found: next
になります。
# web/entrypoint.sh(最終形の抜粋)
export PATH="/app/node_modules/.bin:$PATH"
💡 補足: PATH を補正しないと
command not found: next
エラーが解消されません。
4-3. named volume の初期化を正しく扱う
Docker の named volume は、初回マウント時にイメージ内の /app/node_modules
を丸ごとコピー します。
そのため、古いイメージを使って docker-compose up
を行うと、古い依存が volume
に複製されたままになります。
※ entrypoint のハッシュチェックは「lockfile と node_modules が一致」と誤認し、
yarn install
をスキップしてしまいます。
運用ルール
依存を追加・更新したときは必ず以下をセットで実行します。
docker-compose down -v # volume 削除
docker-compose build # イメージ再ビルド
docker-compose up -d # 最新依存で起動
5. Volumes と bind mount の違い
項目 | bind mount (./web:/app ) |
named volume (node_modules:/app/node_modules ) |
---|---|---|
中身の実体 | ホストのファイルを直接参照 | Docker が管理する専用ストレージ |
反映タイミング | ホスト変更が即反映(ホットリロード向き) | 初期化時にイメージからコピー、その後は独立 |
I/O パフォーマンス | macOS/Windows は遅くなりがち | Linux 上で完結するため高速 |
ネイティブモジュール | ホストと OS 不一致で不整合発生 | コンテナ内ビルドで再現性が高い |
named volume を使うと再現性は上がりますが、
“最初にイメージ内の/app/node_modules
がコピーされる” という仕組みを理解しておく必要があります。
古い依存を持ったイメージのまま down -v → up すると、そのイメージの内容が volume に複製されてしまいます。
6. CI/CD にも効果 ─ ビルド時間が半分に
Yarn 4 へ移行し、yarn install --immutable
に統一した結果、
ステージングデプロイ時間が 12〜15分 → 約7分 に短縮 しました。
主な理由は以下の通りです。
- Dockerfile から不要な
yarn add canvas
などを削除し、キャッシュ効率を改善 - CI ランナーでも Yarn 4 を使用し、
lockfile
の揺れや再インストールを防止
7. 学びとまとめ
-
Yarn 4 では CLI を
npx
/yarn exec
に統一 -
Corepack で Yarn バージョンを固定し、PATH を補正
corepack enable → corepack prepare → export PATH=/app/node_modules/.bin:$PATH
-
named volume 初期化時は イメージが初期値としてコピーされる
-
docker-compose down -v → build → up
を徹底し、古い依存を防ぐ
-
8. トラブルシューティングに役立ったコマンド
# Yarn 4(Berry)が有効か確認
docker-compose run --rm web yarn --version
# Prisma / Next CLI の動作確認
docker-compose run --rm web npx prisma --version
docker-compose run --rm web npx next --version
# named volume の中身を確認(Volume 名は環境に合わせて)
docker volume ls
docker run --rm -v <project>_node_modules:/data alpine ls /data
# 依存を確実に反映
docker-compose down -v
docker-compose build
docker-compose up -d
9. 同じ課題に直面している方へ
Yarn 4 への移行は lockfile 揺れを防ぐ うえで非常に有効ですが、
従来の CLI や Docker 運用とは挙動が異なります。
特に named volume の初期化の仕組み を理解しておくことで、
依存追加時のトラブルを未然に防ぐことができます。
この記事が同じ課題に直面している方の一助になれば幸いです。
質問やフィードバックはお気軽にどうぞ!