🐳

Prisma.jsを使ったアプリケーションでなるべくDockerイメージを小さくする

2024/02/01に公開

やりたいこと

Node.jsアプリケーションでPrismaを使用しています。
このアプリケーションをDockerイメージにしたいと考えたのですが、けっこう大変だったので共有します。

方針

  • Node.jsを使う
  • Prismaを使う
  • pnpmを使う
  • TypeScriptを使う

これらの方針のおかげでちょっと大変だったので、解説します。

とくに、大変だったのは、Prismaとpnpmの組み合わせでした。

Prismaとpnpmがうまく組み合わない

pnpmは、npmやyarnと同じくNode.js用のパッケージマネージャですが、パッケージがハードリンクされることにより、同じパッケージがいろんなところにインストールされない、という利点があります。

また、Prismaは prisma.schema というスキーマファイルを独自のスキーマ言語で記述して、さまざまなデータベースに同じようにアクセスできるORMapperです。TypeScriptに対応しており、スキーマファイルに書いたモデル定義に合わせてTypeScriptの型とクライアントコードをnode_modules下に吐き出してくれます。

このような状況でDockerfileを書くことを考えます。
Prismaのクライアントコードはローカルで生成する前提のものであるため、Dockerfile内で生成する必要があります。

ざっくり以下のようなコードを書きました。

FROM node:20.11.0-slim as base
RUN npm i -g pnpm
WORKDIR /app
COPY package.json .
COPY pnpm-lock.yaml .

FROM base as builder
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run gen # 1
RUN pnpm run build

FROM base as runner
RUN pnpm install --frozen-lockfile --prod && npm un -g pnpm
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma # 2
COPY --from=builder /app/dist ./dist

CMD ["node", "dist/index.js"]

#1#2 で示したところがPrismaクライアントを生成(#1)し、それを実行イメージにコピー(#2)しているところです。
Prismaのドキュメントによると、クライアントコードは node_modules/.prisma/client 下に生成されると書いてあるので、これでいけると思ったのですが、これは間違いでした。

実際には、 node_modules/.pnpm/@prisma+client@5.9.0_prisma@5.9.0/node_modules/.prisma/client の下に生成されてしまいました。原因は推測ですが、パッケージが丸ごとハードリンクされているため、リンク先に生成されてしまったものと思われます。

node_modulesに余計なものが入っていても気にしない

いや、気になる。気になるので、この方法はパス。

無理やりコピーしてみる

Dockerfileで上記パスからファイルをコピーして、実行イメージに置けばよいのでしょうが、Prismaのバージョンらしき文字列が含まれているため、Dockerfileが壊れやすい状態になることが想像できます。
そのため、この方法は避けることにしました。

出力先を変更する

出力先は schema.prisma で指定できます。
以下の記述で、schema.prismaファイルがあるディレクトリから相対パスで ../.prisma/client というところに出力されるようになります。

generator client {
  provider = "prisma-client-js"
  output   = "../.prisma/client"
}

これはこれでうまく動いたのですが、JavaScriptが出力されるため、TypeScriptのコンパイル時にディレクトリが合わなくなり、残念ながら失敗しました。

出力先をデフォルトと同じパスで明示する

結論、これで解決しています。
Prismaのデフォルトの出力先を指定することで、pnpmの余計なディレクトリ移動が挟まらないようにしています。

generator client {
  provider = "prisma-client-js"
  output   = "../node_modules/.prisma/client"
}

まとめ

Prismaの出力先を変更することで、パッケージマネージャの仕様の違いに対応することができました。
また、このおかげで、node_modulesの軽量化もできています。

ほかにもいくつかハマったところがあったのですが、それは別のお話ということで。

以上です。よろしくお願いします。

Discussion