pnpm workspace + prisma + dockerの構築
背景
個人開発でnextjsでフロント・バックを書いていたが、スケジュールジョブ(特定の時間にリマインドするなど)どうしようとなった。
vercelでホストしてたので、vercelのcron jobを使おうと思ったが、hobbyだと制約がきつい。
以前cloud run jobsを使ったことがあったので、dockerfileやらterraformやら作るのめんどいなと思いつつそれでやろうとなった。
フォルダ構造変更前(t3-app使用)
.
├── prisma
│ └── schema.prisma
├── public
│ └── favicon.ico
├── src
│ ├── app
│ │ └── layout.tsx
│ ├── server
│ │ ├── auth.ts
│ │ └── db.ts
│ ├── styles
│ │ └── globals.css
│ ├── trpc
│ │ ├── react.tsx
│ │ └── server.ts
│ ├── env.js
│ └── middleware.ts
├── next.config.js
├── package.json
├── package-lock.json
├── postcss.config.cjs
├── prettier.config.js
├── README.md
├── start-database.sh
├── tailwind.config.ts
└── tsconfig.json
変更後
.
├── app
│ ├── job
│ │ ├── dist
│ │ │ └── index.js
│ │ ├── remind-task
│ │ │ └── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── web
│ ├── public
│ ├── src
│ │ ├── env.js
│ │ ├── middleware.ts
│ │ └── push-test.ts
│ ├── next.config.js
│ ├── next-env.d.ts
│ ├── package.json
│ ├── postcss.config.cjs
│ ├── prettier.config.js
│ ├── start-database.sh
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── packages
│ └── db
│ ├── prisma
│ │ └── schema.prisma
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ └── tsconfig.json
├── terraform
│ ├── main.tf
│ └── terraform.tfvars
├── Dockerfile
├── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
nextjsのプロジェクトの中にjobを突っ込むと、Jobのdockerfileで不要なファイルが含まれてしまうかなと思い、分けた。
npmよりもpnpmのほうがworkspaceを扱うには便利そうだった。
Buildの依存関係とかはturborepo使わないとなあと思ってるが、今はそこまで複雑じゃないので妥協。
packages/dbはこんな感じ
index.ts(これはt3 appで作られたやつのまんま)
import { PrismaClient } from "@prisma/client";
const createPrismaClient = () =>
new PrismaClient({
log:
process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
});
const globalForPrisma = globalThis as unknown as {
prisma: ReturnType<typeof createPrismaClient> | undefined;
};
export const db = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;
package.json
buildしないとインポートした側で実行時エラーになるので、buildコマンドを追加。
{
"name": "@retodo/db",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.11.20",
"prisma": "^5.10.2",
"typescript": "^5.4.5"
},
"dependencies": {
"@prisma/client": "^5.10.2"
}
}
nextjsでmonorepoでprismaを使う場合はこれに従う必要あるみたい
Dockerfileは https://pnpm.io/docker を参考に
FROM node:20-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN pnpm install --frozen-lockfile
RUN pnpm -filter job --filter db build
RUN pnpm deploy --filter=job --prod /prod/job
FROM base AS job
COPY /prod/job /prod/job
WORKDIR /prod/job
CMD [ "pnpm", "start" ]
pnpm -filter job --filter db buildで、必要なパッケージのみbuildする。
pnpm deployという見慣れないコマンドがあるが、どうやらjobの依存パッケージのみに絞ってnode_modulesを再構成してくれるみたい。
これのおかげで他のnextjsなどの不要な依存パッケージを含めずに済む。
docker build
docker build -t test1 . --progress=plain
試しにrun
docker run test1
エラー
> @retodo/job@1.0.0 start /prod/job
> node dist/index.js
/prod/job/node_modules/.pnpm/@prisma+client@5.10.2_prisma@5.14.0/node_modules/.prisma/client/default.js:43
throw new Error(
^
Error: @prisma/client did not initialize yet. Please run "prisma generate" and try to import it again.
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report
at new PrismaClient (/prod/job/node_modules/.pnpm/@prisma+client@5.10.2_prisma@5.14.0/node_modules/.prisma/client/default.js:43:11)
at createPrismaClient (/prod/job/node_modules/.pnpm/@retodo+db@file+packages+db_prisma@5.14.0/node_modules/@retodo/db/index.js:5:34)
at Object.<anonymous> (/prod/job/node_modules/.pnpm/@retodo+db@file+packages+db_prisma@5.14.0/node_modules/@retodo/db/index.js:9:40)
at Module._compile (node:internal/modules/cjs/loader:1358:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
at Module.load (node:internal/modules/cjs/loader:1208:32)
at Module._load (node:internal/modules/cjs/loader:1024:12)
at Module.require (node:internal/modules/cjs/loader:1233:19)
at require (node:internal/modules/helpers:179:18)
at Object.<anonymous> (/prod/job/dist/index.js:3:14)
どうやらpnpm deployはpnpm iをし直しているらしく、その際にprisma generateがなぜか実行されないことが問題のようだった。
postinstallフックを試すため、packages/db/package.jsonに以下を追加
"postinstall": "prisma generate"
pnpm deployで以下のエラーがでた
> [build 6/6] RUN pnpm deploy --filter=@retodo/job --prod /prod/job:
4.077 . | +7 +
4.179 Progress: resolved 40, reused 1, downloaded 16, added 7, done
4.188 .../node_modules/@prisma/engines postinstall$ node scripts/postinstall.js
4.406 .../node_modules/@prisma/engines postinstall: Done
4.480 .../node_modules/@prisma/client postinstall$ node scripts/postinstall.js
4.529 .../node_modules/@prisma/client postinstall: warning In order to use "@prisma/client", please install Prisma CLI. You can install it with "npm add -D prisma".
4.533 .../node_modules/@prisma/client postinstall: Done
4.580 .../node_modules/@retodo/db postinstall$ prisma generate
4.588 .../node_modules/@retodo/db postinstall: sh: prisma: not found
4.590 ELIFECYCLE Command failed.
prismaがないと。。。
最終的には、packages/db/package.jsonに以下を追加して解決した。
"postinstall": "pnpm dlx prisma generate"
なんか微妙な気もするが、動いたのでおk