Search
🐳

Docker 파일 기반의 CI/CD 프로세스 최적화

Date
2023/09/06
Authors
Published
Slug
docker-file-optimization
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에서 필요한 파일을 나누고 있다
각 스테이지의 기준은 Next.js의 예시파일 을 기반으로 비슷하게 구성하였다
게임 어드민 특성상 이미지가 많아 image 관련 스테이지만 추가하였다

base 스테이지

노드
이미지 크기를 최소화하기 위해 alpine (가장 경량화) 버전 사용
FROM node:20-alpine AS base
Docker
복사
각 환경별 용량 차이는 꽤 큰 편이다 (링크)
기본, slim, alpine, onBuild 버전의 차이는 Docker Hub 에서 확인 가능
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
복사
대부분은 Next.js의 템플릿 을 그대로 사용하였다.
추가로 프로젝트에서 .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
복사
telemetry 비활성화
vercel에서 수집하는 통계데이터인 telemetry가 수집되지 않도록 비활성화
최적화보다는 어드민의 특성상 보안을 위해 비활성화하였다
ENV NEXT_TELEMETRY_DISABLED 1
Docker
복사

runner 스테이지

.next/standalone에 생성 된 최적화 된 빌드파일을 사용하도록 설정
const nextConfig = getConfig({ // ... output: 'standalone', // ... });
JavaScript
복사
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
Docker
복사

기본 문법

디렉티브 작성

디렉티브는 어떤 문법으로 파싱해야 하는지 알려주는 역할이다
반드시 첫번째 줄에 적어야한다
# syntax=docker/dockerfile:1.4
Docker
복사

레퍼런스

기본적으로는 Next.js에서 제공하는 Dockerfile을 템플릿을 참고하였다
node 환경별 구체적인 차이
docker의 기본 명령어