Next.js 배포 시 캐시 권한 문제 해결하기
Next.js 배포 시 캐시 권한 문제 해결하기
moseoh
nextjs docker

Next.js 배포 시 캐시 권한 문제 해결하기

moseoh · 2025년 06월 20일

💡 백엔드 개발자의 시선으로 바라본 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 등의 최적화된 포맷으로 변환하여 저장합니다.

캐시 전후 성능 비교:

  • 캐시 적용 전: 매번 이미지 최적화 처리로 인한 지연 Image

  • 캐시 적용 후: 빠른 이미지 로딩 및 대역폭 절약 Image

주의사항:

  • Next.js는 이미지의 전체 URL을 기준으로 캐시를 생성합니다
  • 같은 이미지라도 파라미터가 다르면 별도로 캐시됩니다 (예: 이미지 사이즈, S3 Presigned URL 인증 정보)

데이터 fetch 캐시 (.next/cache/fetch-cache)

SSR(Server Side Rendering) 방식에서 API 호출 결과를 캐시합니다.

적용 조건:

  • SSR 방식에서만 동작 (CSR에서는 활용 불가)

  • API 응답 시간에 따라 성능 개선 효과가 극대화됩니다 캐시 전후 비교:

  • 캐시 적용 전: 매 요청마다 API 호출 Image

  • 캐시 적용 후: 캐시된 데이터로 빠른 응답 Image

추가 이슈: 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 /web
ENV PORT=3000
# 보안을 위한 비루트 사용자 생성
RUN addgroup --system webuser && adduser -S -s /bin/false -G webuser webuser
RUN chown -R webuser:webuser /web # ❌ 파일 복사 전에 권한 설정
USER webuser
# 파일 복사
COPY public ./public
COPY messages ./messages
COPY .next/standalone ./
COPY .next/static ./.next/static
CMD ["node", "server.js"]

수정된 Dockerfile (해결)

FROM node:22-alpine AS deployer
WORKDIR /web
ENV PORT=3000
# 보안을 위한 비루트 사용자 생성
RUN addgroup --system webuser && adduser -S -s /bin/false -G webuser webuser
# 파일 복사 (root 권한으로)
COPY public ./public
COPY messages ./messages
COPY .next/standalone ./
COPY .next/static ./.next/static
# 복사 후 권한 설정 ✅
RUN chown -R webuser:webuser /web
USER webuser
CMD ["node", "server.js"]

핵심 변경사항

# 파일 복사...
RUN chown -R webuser:webuser /web
USER webuser
# 파일 복사...

문제의 원인

  1. 파일 복사 순서 문제: COPY 명령어 실행 시점에 USER webuser가 이미 설정되어 있었지만, 복사되는 파일들은 여전히 root 소유권을 가졌습니다.
  2. 권한 불일치: 애플리케이션은 webuser 권한으로 실행되지만, 캐시 디렉토리를 생성해야 하는 상위 디렉토리가 root 소유였습니다.
  3. 런타임 권한 부족: Next.js가 .next/cache 디렉토리를 동적으로 생성하려 할 때 권한이 부족했습니다.

결론

  • Docker에서 Next.js를 배포할 때는 파일 복사와 권한 설정의 순서가 중요합니다.
  • 특히 Next.js가 런타임에 캐시 파일을 생성할 수 있도록 적절한 디렉토리 권한을 보장해야 합니다.
  • 보안을 위해 비루트 사용자를 사용하되, 애플리케이션이 필요로 하는 디렉토리에 대한 쓰기 권한을 올바르게 설정하는 것이 핵심입니다. 문제를 해결하고 나서 다시 생각해보니… 그저 Dockerfile 작성 실수 였습니다… 처음에는 “아, 내가 프론트엔드를 잘 모르니까 Next.js의 복잡한 캐시 시스템 때문에 생긴 문제구나!” 라고 생각했는데 말이죠. 결국 백엔드든 프론트엔드든 기본기가 중요하다는 걸 다시 한번 깨달았습니다. Docker 파일 하나 제대로 못 짜놓고 Next.js 탓을 했던 제 자신을 반성합니다.