logo

Dockerize

nextjs 운영을 위한 Dockerize 방안

43 조회

0 추천

1,215 단어

7분 예상

2025. 02. 18. 게시

2025. 02. 23. 수정

luasenvy 작성

CC BY-NC-SA 4.0

Dockerfile with Standalone

도커를 통해 서비스를 구성하기 위한 방법으로 여러가지를 들 수 있겠으나 nextjs의 빌드결과를 이미지로 복사하여 컨테이너가 실행되면 서비스를 시작하는 방법을 선호하고 있다. 여러가지 방안을 적용해본 결과 정착한 형태이며 장점으로는 도커 이미지의 용량이 최적화되고 컨테이너 밖에서 빌드하기 때문에 비교적 빠른 호스트의 자원을 활용해 빌드할 수 있다.

import type { NextConfig } from "next";

export default {
  reactStrictMode: true,
  output: "standalone",
} satisfies NextConfig;

next.config.ts파일을 열어 output옵션을 standalone으로 설정하여 빌드 결과만으로 웹서버가 구동될 수 있도록 한다. 이 옵션을 사용하지 않으면 next 패키지 전체가 필요하고 next start와 같은 명령으로만 구동될 수 있다. standalone의 빌드 결과는 단순히 node index.js와 같은 노드 실행 명령만으로 웹 서버가 구동될 수 있게 된다.

FROM node:lts-alpine AS base

RUN apk add --no-cache libc6-compat tzdata

nodejs 이미지는 항상 lts-alpine태그를 사용하여 최소화된 이미지를 베이스로 작업한다. nodejs에서 필요로 하는 기본 패키지로 libc6-compat을 설치해주는 것이 좋다. 왜 libc6가 필요한지에 대해서는 링크를 따라가 확인해볼 수 있다. tzdata의 경우는 시스템의 시간대를 설정하기 위하여 설치한다. 서버측에서 Date 객체를 다루어야 한다면 이 시간대를 잘 설정해두는 것이 정신건강에 좋다.

WORKDIR /app

FROM base AS depends

RUN npm i sharp

주 작업 경로를 /app으로 지정하고 이미지를 스위치하여 sharp패키지를 설치해준다. sharp 패키지는 이미지를 변환 패키지로 높은 성능을 가지고 있다. nextjs에서 이미지 태그를 활용하여 최적화된 이미지를 서비스하기 위해 필요한 패키지이다.

FROM base AS runner

RUN npm i -g pm2

다시 이미지를 스위치하여 실제로 구동될 이미지를 구성한다. pm2를 사용하여 기본적으로 로드밸런싱이 되도록 구성하는 것을 추천한다.

module.exports = {
  apps: [
    {
      name: "my application",
      script: "server.js",
      instances: Number(process.env.CLUSTER_INSTANCES || "1"),
      exec_mode: "cluster",
      env: {
        PWD: "/app",
        NODE_ENV: "production",
        HOSTNAME: "0.0.0.0",
        PORT: "3000",
      },
    },
  ],
};

pm2 패키지로 구동하기 위해서는 필요한 설정 파일을 만들어 주어야한다. ecosystem.config.js를 작성하여 pm2 스펙을 작성한다. 위의 경우 동시에 실행될 인스턴스 개수를 환경변수를 통하여 설정할 수 있도록 유도하였다.

COPY --chown=node:node .next/standalone ./
COPY --chown=node:node .next/static ./.next/static
COPY --chown=node:node public ./public
COPY --chown=node:node ecosystem.config.js ./ecosystem.config.js

COPY --from=depends --chown=node:node /app/node_modules ./node_modules

계속해서 빌드된 결과를 이미지로 복사한다. standalone으로 빌드된 결과는 .next/standalone 디렉토리에 구성된다. 이외에 필요한 정적파일들은 라우트 규칙에 맞추어 적절한 디렉토리에 위치시켜야 한다. ecosystem.config.jsnode_modules 또한 올바른 위치에 복사한다.

node_modules의 경우 standalone 빌드시에 필요한 의존성들은 왠만해서 모두 번들링된다. 소스에서 복사할 경우 용량이 매우 커지기 때문에 sharp와 pm2를 예외적으로 추가 설치한 이미지로부터 복사하는 것임을 주의한다.

# Volume Settings
RUN mkdir /mnt/log
RUN chown node:node /mnt/log

VOLUME /mnt/log

# Timezone Settings
RUN cp /usr/share/zoneinfo/Asia/Seoul /etc/localtime
RUN echo "Asia/Seoul" > /etc/timezone

ENV TZ Asia/Seoul
ENV LANG ko_KR.UTF-8
ENV LANGUAGE ko_KR.UTF-8
ENV LC_ALL ko_KR.UTF-8

# Application Environments
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000

EXPOSE 3000

USER node

ENTRYPOINT ["pm2-runtime", "ecosystem.config.js"]

필요한 마운트 지점이 있다면 설정하고 기본 환경변수, 연결할 포트, 사용자, 시간대 설정을 지정한다. 마지막으로 컨테이너 실행지점을 pm2-runtime 명령을 통해 실행될 수 있도록 설정한다.

PM2

로드밸런싱을 하지 않고 바로 node server.js를 통해 실행해도 구성상 오류없이 잘 서비스 할 수 있지만 동시 접속자수가 늘어나거나 큰 작업을 실행할 때 응답이 지연되는 현상이 있을 수 있다. 여러 사용자에게 서비스하는 것이 목적이라면 기본적으로 로드밸런싱을 구성해두는 것이 좋다.

환경변수

필요한 환경변수들은 docker-compose.yml을 통하여 파일로 분리관리할 수도 있으며 파일내부에 직접 포함할 수도 있다. 머신의 성능에 따라서 CLUSTER_INSTANCES의 값을 통해 최적값을 조정할 수 있다.