docker 파일을 셋팅하고 최적화 작업을 한 내용을 기록용
최적화 구성
멀티스테이지 구성
FROM node:20.5.1-alpine3.17 AS base
FROM base AS deps
FROM base AS images
FROM base AS builder
FROM base AS runner
Docker
복사
•
최적화에서 가장 중요한 과정 중에 하나인 멀티스테이지 구성이다
•
각 환경을 분리해서 각 환경에서 필요한 파일만을 이용해 이미지를 구성하는 방법이다
•
일반적으로는 build 시와 실제 runtime에서 필요한 파일을 나누고 있다
•
◦
게임 어드민 특성상 이미지가 많아 image 관련 스테이지만 추가하였다
base 스테이지
•
노드
◦
이미지 크기를 최소화하기 위해 alpine (가장 경량화) 버전 사용
FROM node:20-alpine AS base
Docker
복사
◦
◦
▪
alpine 문서를 보면 가장 큰 차이는 glibc대신 musl libc를 사용하는 것으로 보인다. (리눅스 시스템에서 표준 C 라이브러리 - GNU Library C)
This variant is useful when final image size being as small as possible is your primary concern. The main caveat to note is that it does use musl libc instead of glibc and friends, so software will often run into issues depending on the depth of their libc requirements/assumptions. See this Hacker News comment thread for more discussion of the issues that might arise and some pro/con comparisons of using Alpine-based images.
▪
glibc이 필요한 경우 버전변경이 필요할수도 있다고 하니 glibc, musl libc 키워드에 대해 주의해야 겠다 (경량화 버전 사용으로 문제 발생 시 에러 메세지에 언급될 수 있으므로;;)
deps 스테이지
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
COPY .npmrc .
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
Docker
복사
•
◦
추가로 프로젝트에서 .npmrc 파일을 사용하고 있어 복사해 주었다
◦
pnpm i 을 할 때 --frozen-lockfile 옵션은 처음 알게 되었다
▪
pnpm-lock.yaml을 절대 변경하지 않고 오로지 lockfile에 명시된 버전 그대로 의존성을 설치하도록 강제하는 옵션이라고 한다
▪
lockfile 수정이 필요하면 에러가 발생한다고 하니 CI/CD과정에 잘 맞는 옵션이기도 하고 가끔 개발 환경별 문제가 발생할 때에도 유용하게 사용할 수 있을 것 같다.
•
예시 파일에서는 패키지 종류에 따라 다른 명령어를 쓰도록 분기처리 되어있었다
•
스튜디오 차원에서 패키지매니저는 pnpm 만 쓰고 있어 조건문을 수정하였다
COPY package.json pnpm-lock.yaml* ./
COPY .npmrc .
RUN \
if [ -f pnpm-lock.yaml ]; then pnpm i --frozen-lockfile; \
else echo "pnpm-lock.yaml not found." && exit 1; \
fi
Docker
복사
◦
혹시, 다른 패키지 매니저에 대한 부분도 고려해야 하지않을까 고민이 들기도 했지만
◦
package.json에 명시되어 있으므로 큰 문제는 없을 듯하다
// package.json
// ...
"packageManager": "pnpm@8.0.0",
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"react": "^18",
"react-dom": "^18"
}
},
// ...
JavaScript
복사
images 스테이지
•
위에서 언급한대로 게임 어드민 특성상 image 리소스가 많아 별도의 스테이지로 분리해두었다
FROM base AS images
WORKDIR /app
COPY ./public/res ./public/res
Docker
복사
builder 스테이지
•
github 캐시 사용
◦
Next.js에서 빌드시 .next/cache에 이전 빌드 데이터가 캐싱되게 된다
◦
remote 환경(github action)에서의 활용을 위해 actions/cache를 사용하였다
# ci.yaml
- name: Next.js cache
uses: actions/cache@v3
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-${{ runner.node }}-${{ hashFiles('**/pnpm-lock.yaml') }}-nextjs
YAML
복사
◦
분리 된 Docker 환경에서의 활용을 위해 내보내는 로직을 작성해 주었다
# ci.yaml
- name: export Next.js cache
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
target: cache
outputs: type=local,dest=.
YAML
복사
◦
Docker 환경에도 .next/cache가 존재하여 COPY 명령어를 통해 복사해서 사용할 수 있다
# Dockerfile
COPY .next/cache ./.next/cache
Docker
복사
runner 스테이지
•
.next/standalone에 생성 된 최적화 된 빌드파일을 사용하도록 설정
const nextConfig = getConfig({
// ...
output: 'standalone',
// ...
});
JavaScript
복사
COPY /app/.next/standalone ./
COPY /app/.next/static ./.next/static
Docker
복사
기본 문법
디렉티브 작성
•
디렉티브는 어떤 문법으로 파싱해야 하는지 알려주는 역할이다
•
반드시 첫번째 줄에 적어야한다
# syntax=docker/dockerfile:1.4
Docker
복사
레퍼런스
•
기본적으로는 Next.js에서 제공하는 Dockerfile을 템플릿을 참고하였다
•
node 환경별 구체적인 차이
•
github action cache 활용
•
docker의 기본 명령어