RSS듀오랩스
개발 도구

prisma generate 를 어디서 돌릴까: postinstall 과 도커 빌드 캐시

작성자
듀오랩스 대표·6분 읽기

새 맥에 프로젝트들을 하나씩 세우는 중이었습니다. 클론하고, 의존성을 깔고, 개발 서버를 띄우면 이 화면이 떴습니다.

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.jsonscripts 를 건드리는 변경은 로컬 실행만으로 검증되지 않습니다. npm ci 가 도는 자리는 여러 곳이고, 자리마다 있는 파일이 다릅니다. 이번에 제가 놓친 것이 정확히 그 차이였습니다.

아직 정리하지 못한 것도 있습니다. 생성 코드를 커밋하는 그 리포는 여전히 스키마와 어긋난 상태입니다. 되돌려 두었고, 옮기는 작업은 따로 잡아야 합니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.