prisma generate 를 어디서 돌릴까: postinstall 과 도커 빌드 캐시
새 맥에 프로젝트들을 하나씩 세우는 중이었습니다. 클론하고, 의존성을 깔고, 개발 서버를 띄우면 이 화면이 떴습니다.
Module not found: Can't resolve '../generated/prisma/client'
1 | import { PrismaPg } from "@prisma/adapter-pg";
> 2 | import { PrismaClient } from "../generated/prisma/client";같은 오류를 하루에 세 번 봤습니다. 리포마다 원인이 조금씩 달랐고, 고치려다 배포를 한 번 깨먹었습니다.
리포마다 갈린 답
Prisma 는 스키마를 읽어 타입이 붙은 클라이언트 코드를 생성합니다. 그 결과물을 리포에 커밋할지 말지가 갈립니다. 제 리포 네 개가 두 진영으로 나뉘어 있었습니다.
| 생성 코드 | 클론 직후 | |
|---|---|---|
| 대시보드 | 커밋함 | 바로 뜸 |
| 나머지 셋 | .gitignore |
Module not found |
무시하는 쪽이 셋인데 postinstall 은 아무 데도 없었습니다. 그러니 클론한 사람은 npx prisma generate 를 손으로 쳐야 하고, 그걸 아는 방법은 오류를 한 번 만나는 것뿐이었습니다.
커밋하는 쪽에 있던 다른 문제
바로 뜨는 쪽이 나아 보였는데, 그 리포에서 습관적으로 npx prisma generate 를 돌렸더니 파일 21개가 바뀌었습니다. 대부분이 삭제였습니다.
스키마에서 걷어낸 모델들의 생성 파일이 그대로 남아 있었던 것입니다. 두 달 전에 문서 기능을 별도 서비스로 떼어내면서 스키마에서는 지웠는데, 생성 코드는 다시 만들지 않아 옛 모델이 남았습니다.
이 상태로도 빌드는 됩니다. 앱이 그 모델을 안 쓰니 아무 일도 일어나지 않습니다. 어긋났다는 사실이 어디에도 드러나지 않고, 누군가 generate 를 돌리는 날 21개 파일 diff 로 나타납니다.
생성 코드를 커밋하는 방식은 스키마를 고칠 때마다 generate 하고 함께 커밋한다 는 규율을 요구합니다. 규율이 지켜지지 않으면 정본이 둘이 되고, 둘 중 어느 쪽이 맞는지는 아무도 안 알려 줍니다.
한 줄이면 될 줄 알았던 postinstall
무시하는 쪽 리포에 이 한 줄을 넣었습니다.
"postinstall": "prisma generate"로컬에서 확인했습니다. src/generated 를 통째로 지우고 npm install 만 돌리자 다시 만들어졌습니다. 62ms 걸렸습니다. 커밋하고 푸시했습니다.
2분 뒤 배포가 실패했습니다.
> app@0.1.0 postinstall
> prisma generate
Error: Could not find Prisma Schema
prisma/schema.prisma: file not found스키마가 없는 도커 deps 단계
Dockerfile 을 열어 보니 이유가 한눈에 보였습니다.
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json .npmrc ./
RUN npm ci # ← 여기서 postinstall 이 돈다
FROM node:22-alpine AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate # ← 원래는 여기서 생성했다의존성 설치 단계는 소스 전체를 복사하지 않습니다. package.json 과 잠금 파일만 복사하는데, 그래야 소스가 바뀌어도 그 레이어가 캐시로 재사용되기 때문입니다. 도커 이미지를 빠르게 만드는 가장 기본적인 방법입니다.
그 시점에는 prisma/schema.prisma 가 없습니다. 그런데 제가 npm ci 에 "스키마를 읽어라" 는 일을 붙여 버렸습니다.
로컬에서는 스키마가 늘 거기 있습니다. 그래서 이 실패는 도커에서만 났고, 저는 로컬만 확인하고 푸시했습니다.
고치는 방법과 그 대가
deps 단계에 스키마를 함께 복사하면 됩니다.
COPY package.json package-lock.json .npmrc ./
COPY prisma ./prisma
RUN npm ci다음 배포는 통과했습니다. 다만 공짜가 아닙니다. 스키마를 고칠 때마다 이 레이어의 캐시가 무효화됩니다. 마이그레이션을 만들 때마다 CI 가 npm ci 를 처음부터 다시 돌립니다.
바꿔 말하면 이 선택은 "설치가 곧 생성" 이라는 약속을 사기 위해 캐시 적중률을 조금 파는 것입니다. 저는 샀습니다. 새 기계에서 오류를 만나고 원인을 찾는 시간이 CI 20초보다 비싸다고 봤습니다.
그래서 지금 규칙
생성물은 커밋하지 않습니다. 정본은 스키마 하나입니다. 어긋남이 생길 자리를 없앱니다.
설치가 생성합니다. postinstall 에 두면 사람이 기억할 것이 하나 줄어듭니다.
그 약속을 도커 빌드에서도 지킵니다. 설치 단계가 스키마를 필요로 하게 됐으면, 그 단계에 스키마를 넣어야 합니다. 한쪽에서만 성립하는 약속은 약속이 아닙니다.
package.json 의 scripts 를 건드리는 변경은 로컬 실행만으로 검증되지 않습니다. npm ci 가 도는 자리는 여러 곳이고, 자리마다 있는 파일이 다릅니다. 이번에 제가 놓친 것이 정확히 그 차이였습니다.
아직 정리하지 못한 것도 있습니다. 생성 코드를 커밋하는 그 리포는 여전히 스키마와 어긋난 상태입니다. 되돌려 두었고, 옮기는 작업은 따로 잡아야 합니다.
함께 읽기
- colima 자동 기동: brew services가 10초마다 재실행되는 이유개발기에 도커를 새로 깔 일이 생겼습니다. 컨테이너 하나만 돌리면 되는 일이라 가볍게 끝날 줄 알았는데, 설치 자체는 5분이었고 그 뒤에 두 가지를 더 고쳐야 했습니다. 하나는 깃 저장소가 더러워진 것이고, 하나는 코어 하나의 2.8%가 계속 타고 있던 것입니다.
- 드라이런과 멱등성: 사고를 막는 시점의 차이배포 스크립트에 안전장치를 넣는다고 할 때 두 가지가 자주 같이 나옵니다.
- 새 맥북 개발 환경 세팅: git clone이 가져오지 않는 것들맥북을 새로 받고 하던 프로젝트를 이어서 하려 했습니다. 리포를 클론하고 npm install 을 돌린 다음 개발 서버를 띄웠는데, 브라우저에 이게 떴습니다.
- xcode-select와 DEVELOPER_DIR: 어느 쪽이 정석일까?Expo 앱을 iOS 시뮬레이터에 띄우려고 도구부터 점검했는데, 결과가 앞뒤가 안 맞았습니다.
- 환경변수로 둔 CLI 기본값: 비대화형 셸에서 사라지는 이유문서를 저장하는 CLI를 하나 만들었습니다. 저장할 때마다 그 문서가 어디로 떨어졌는지 찾는 일이 반복돼서, 새 문서는 인박스로 가도록 기본값을 정했습니다. 터미널에서 쳐 보니 의도한 자리에 들어갔습니다.