💡 백엔드 개발자의 시선으로 바라본 Next.js 배포 경험기입니다.
문제 상황
Next.js 애플리케이션을 Docker로 배포하는 과정에서 다음과 같은 권한 오류가 발생했습니다.
⨯ Failed to write image to cache QHHgBajJpXQnUUX3dnw2T0vtdimx4z90zmoGkfI8jbU=Error: EACCES: permission denied, mkdir '/web/.next/cache'at async Object.mkdir (node:internal/fs/promises:852:10)at async writeToCacheDir (/web/node_modules/next/dist/server/image-optimizer.js:178:5)at async ImageOptimizerCache.set (/web/node_modules/next/dist/server/image-optimizer.js:451:13)
오류의 핵심은 Next.js가 런타임에서 .next/cache
디렉토리에 캐시 파일을 생성하려 했지만, 권한이 없어서 실패한 것이었습니다.
Next.js 캐시의 역할
Next.js 가 cache를 사용하지 않는다면 무슨일이 발생할까요? 캐시를 사용하지 않았을 때 손실되는 성능이 궁금하여 역할을 찾아보았습니다.
Next.js는 런타임에서 성능 최적화를 위해 다양한 캐시를 활용합니다.
이미지 캐시 (.next/cache/images)
이미지 최적화를 위한 캐시로, 원본 이미지를 WebP 등의 최적화된 포맷으로 변환하여 저장합니다.
캐시 전후 성능 비교:
-
캐시 적용 전: 매번 이미지 최적화 처리로 인한 지연
-
캐시 적용 후: 빠른 이미지 로딩 및 대역폭 절약
주의사항:
- Next.js는 이미지의 전체 URL을 기준으로 캐시를 생성합니다
- 같은 이미지라도 파라미터가 다르면 별도로 캐시됩니다 (예: 이미지 사이즈, S3 Presigned URL 인증 정보)
데이터 fetch 캐시 (.next/cache/fetch-cache)
SSR(Server Side Rendering) 방식에서 API 호출 결과를 캐시합니다.
적용 조건:
-
SSR 방식에서만 동작 (CSR에서는 활용 불가)
-
API 응답 시간에 따라 성능 개선 효과가 극대화됩니다 캐시 전후 비교:
-
캐시 적용 전: 매 요청마다 API 호출
-
캐시 적용 후: 캐시된 데이터로 빠른 응답
추가 이슈: Sharp 모듈
Standalone 모드에서 이미지 최적화를 사용하려면 sharp
패키지가 필요합니다.
⨯ Error: 'sharp' is required to be installed in standalone mode for the image optimization to function correctly.
해결 방법
기존 Dockerfile (문제 상황)
FROM node:22-alpine AS deployer
WORKDIR /webENV PORT=3000
# 보안을 위한 비루트 사용자 생성RUN addgroup --system webuser && adduser -S -s /bin/false -G webuser webuserRUN chown -R webuser:webuser /web # ❌ 파일 복사 전에 권한 설정USER webuser
# 파일 복사COPY public ./publicCOPY messages ./messagesCOPY .next/standalone ./COPY .next/static ./.next/static
CMD ["node", "server.js"]
수정된 Dockerfile (해결)
FROM node:22-alpine AS deployer
WORKDIR /webENV PORT=3000
# 보안을 위한 비루트 사용자 생성RUN addgroup --system webuser && adduser -S -s /bin/false -G webuser webuser
# 파일 복사 (root 권한으로)COPY public ./publicCOPY messages ./messagesCOPY .next/standalone ./COPY .next/static ./.next/static
# 복사 후 권한 설정 ✅RUN chown -R webuser:webuser /webUSER webuser
CMD ["node", "server.js"]
핵심 변경사항
# 파일 복사...RUN chown -R webuser:webuser /webUSER webuser # 파일 복사...
문제의 원인
- 파일 복사 순서 문제:
COPY
명령어 실행 시점에USER webuser
가 이미 설정되어 있었지만, 복사되는 파일들은 여전히 root 소유권을 가졌습니다. - 권한 불일치: 애플리케이션은
webuser
권한으로 실행되지만, 캐시 디렉토리를 생성해야 하는 상위 디렉토리가 root 소유였습니다. - 런타임 권한 부족: Next.js가
.next/cache
디렉토리를 동적으로 생성하려 할 때 권한이 부족했습니다.
결론
- Docker에서 Next.js를 배포할 때는 파일 복사와 권한 설정의 순서가 중요합니다.
- 특히 Next.js가 런타임에 캐시 파일을 생성할 수 있도록 적절한 디렉토리 권한을 보장해야 합니다.
- 보안을 위해 비루트 사용자를 사용하되, 애플리케이션이 필요로 하는 디렉토리에 대한 쓰기 권한을 올바르게 설정하는 것이 핵심입니다. 문제를 해결하고 나서 다시 생각해보니… 그저 Dockerfile 작성 실수 였습니다… 처음에는 “아, 내가 프론트엔드를 잘 모르니까 Next.js의 복잡한 캐시 시스템 때문에 생긴 문제구나!” 라고 생각했는데 말이죠. 결국 백엔드든 프론트엔드든 기본기가 중요하다는 걸 다시 한번 깨달았습니다. Docker 파일 하나 제대로 못 짜놓고 Next.js 탓을 했던 제 자신을 반성합니다.