Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[22주차 2] Next.js standalone #223

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions new/22-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
## Standalone
> TL;DR
>
> Next.js 에서 지원하는 standalone 옵션을 이용해 Docker image 사이즈를 최소화하고 배포 시간을 줄이는 방법에 대해 알아봅니다

### Standalone
> standalone은 사전적으로 '독립형' 이라는 의미

- Next.js 에서는 애플리케이션을 실행하는데 필요한 최소한의 코드만 추출하겠다는 의미로 사용
- [공식문서](https://nextjs.org/docs/app/api-reference/next-config-js/output#automatically-copying-traced-files)을 살펴보면, standalone 옵션을 켜면 `.next/standalone` 폴더가 생성된다.

``` js
module.exports = {
output: 'standalone',
};
```
- standalone 옵션은 배포 환경에서 웹앱을 실행할 때 필요없는 코드는 빌드 결과물에서 제외한다.

---

- 이 옵션을 통해 Docker image 사이즈를 줄일 수 있고, 배포 시간을 줄일 수 있다.
- 여러 방식을 통해 웹앱을 배포할 수 있지만, CI/CD 파이프라인과 Docker image를 통해 배포하는 경우 위 옵션은 효과적이다.
- 배포 방식 예시
- Jenkins CI/CD 파이프라인을 이용해 Amazon ECS Cluster 에 Docker image 를 배포
- CD 파이프라인에서는 생성된 Docker image 를 빌드한 뒤,
- ECR(Amazon Elastic Container Registry)에 push
- 따라서 Docker image 의 사이즈가 작아지면, ECR 에 push & pull 하는 시간이 감소되어 배포 시간이 단축

- Next.js 공식 문서에서 docker를 사용해서 애플리케이션을 빌드한 경우, Dockerfile의 예시 파일이 있다.
- [참고 예시 Dockerfile](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile)
- Dockerfile을 작성하기 귀찮을 때는 아래 예시 파일을 사용해도 좋지만 이미지 크기가 커져서 수정해서 쓰는 것이 좋다.
```
FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi


# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js
```


#### standalone 적용

- 로컬에서 배포용 Docker 이미지를 실행하면 standalone 이 잘 적용되었는지 확인 가능
- `next build`를 실행하여 빌드 결과물을 생성한 뒤 다음 명령어 실행

``` bash
Docker build . --target {특정 빌드 스테이지} -t {이미지_이름:태그}
Docker run -p {호스트 포트}:{컨테이너 포트} {이미지_이름:태그}
```

- 서버 실행을 위해서는 다음 명령어 실행

``` bash
node standalone/server.js
```

- `server.js`
- 실제 `.next`의 실행파일들을 기반으로 애플리케이션을 구동시켜주는 진입점


- 빌드 전 미리 로드해야하는 라이브러리가 있다면 Dockerfile을 다음을 추가
- 현재 빌드 컨텍스트의 파일을 Docker 이미지 내의 디렉토리로 복사하고, 그 파일의 소유자와 그룹을 node로 설정
```
COPY --chown=node:node {현재 Docker 빌드 컨텍스트에서 source 파일 경로} {복사될 대상의 Docker image 내부 경로}

ENV LOG_LEVEL debug
ENV DD_LOGS_INJECTION true
...
ENV NODE_OPTIONS "-r ~/preload.js" // 해당 파일 경로로 설정 필요
ENTRYPOINT ["node", "~/server.js"] // 해당 파일 경로로 설정 필요
```

#### /static, /public 복사
- standalone 내에는 `/static`, `/public` 이 존재하지 않는다
- 따라서 공식문서에는 해당 폴더들을 CDN으로 관리할 것을 권장한다
- 하지만 별도의 CDN으로 서빙하지 않는 경우에는 `/static`, `/public`을 직접 복사를 해야한다.

```
COPY --chown=node:node ~/.next/standalone ./
COPY --chown=node:node ~/public ./~/public
COPY --chown=node:node ~/.next/static ./~/.next/static
```