본문 바로가기

programming

[3/3] Next.js + AWS ECS 컨테이너 배포하기 : CodePipeline 활용한 CI/CD 자동화와 Docker 빌드 최적화 사례 분석

⚠️ 주의: 이 포스팅을 포함한 총 3편의 Next.js 배포 최적화 시리즈는 제가 실제 환경에서 시행착오를 겪으며 얻은 경험과 지식을 정리한 글입니다.

이 글들은 완벽한 정답이 아니라, 다양한 시도와 시행착오를 통한 저의 삽질기이자 성장 과정이며, 동시에 나와 비슷한 고민을 하는 사람들과 지식을 나누고자 하는 목적으로 작성되었습니다.

따라서, 가능하면 3편의 글을 모두 읽고, 환경과 상황에 맞춰 신중히 테스트한 후 적용하는 것을 추천합니다.

마지막까지 읽으시면 무엇이 문제였는지, 그리고 어떻게 해결했는지 확인할 수 있습니다! 🚨🚧✨

 

📋 총 3편의 글 리스트 

https://tacit.tistory.com/267

 

Next.js + AWS ECS 컨테이너 배포하기 : CodePipeline 활용한 CI/CD 자동화와 Docker 빌드 최적화 사례 분석 [1

⚠️ 주의: 이 포스팅을 포함한 총 3편의 Next.js 배포 최적화 시리즈는 제가 실제 환경에서 시행착오를 겪으며 얻은 경험과 지식을 정리한 글입니다.이 글들은 완벽한 정답이 아니라, 다양한 시도

tacit.tistory.com

https://tacit.tistory.com/269

 

Next.js + AWS ECS 컨테이너 배포하기 : CodePipeline 활용한 CI/CD 자동화와 Docker 빌드 최적화 사례 분석 [2

⚠️ 주의: 이 포스팅을 포함한 총 3편의 Next.js 배포 최적화 시리즈는 제가 실제 환경에서 시행착오를 겪으며 얻은 경험과 지식을 정리한 글입니다.이 글들은 완벽한 정답이 아니라, 다양한 시도

tacit.tistory.com

https://tacit.tistory.com/271

 

Next.js + AWS ECS 컨테이너 배포하기 : CodePipeline 활용한 CI/CD 자동화와 Docker 빌드 최적화 사례 분석 [3

1. 원인 분석캐시 이미지 생성·업로드·다운로드 과정에서 소요되는 시간 때문에 Docker 레이어 캐싱 전략이 오히려 비효율적이었습니다.npm ci 명령어의 캐싱 불확실성으로 인해 Docker 레이어 캐싱

tacit.tistory.com

 


1. 원인 분석

  • 캐시 이미지 생성·업로드·다운로드 과정에서 소요되는 시간 때문에 Docker 레이어 캐싱 전략이 오히려 비효율적이었습니다.
  • npm ci 명령어의 캐싱 불확실성으로 인해 Docker 레이어 캐싱의 이점을 제대로 활용하기 어려웠습니다.

 

2. 해결방안

이번에는 Next.js의 공식문서에서 권장하는 CI 빌드 캐싱 방식을 사용하기로 했습니다.

buildspec.yml에 캐시할 데이터 폴더를 명시하고, AWS CodeBuild에서 Artifact 캐싱으로 S3 버킷 경로를 설정합니다.

cache:
  paths:
    - 'node_modules/**/*'  # node_modules 캐싱
    - '.next/cache/**/*'   # Next.js 빌드 캐시

 

3. 변경내용

이 전략을 사용하려면 다음과 같은 변경이 필요했습니다.

🔸 npm 명령어 변경 (npm ci → npm i)

  • 캐시된 node_modules를 재사용하기 위해 npm i 사용
  • CI/CD 환경에서는 보통 정확성을 위해 npm ci가 추천되지만, 속도 향상을 위한 전략적 선택
  • 프로덕션에서는 npm ci를 사용하는 것이 권장됨
  • 잦은 배포가 있는 dev 환경에서 사용함

🔸 pre_build.sh 스크립트 수정 (Dockerfile 밖에서 빌드 실행)

  • AWS 빌드 환경에서 직접 node_modules 및 .next/cache 접근 및 S3 업로드 가능하게 변경
  • Dockerfile 내부가 아닌, 외부 환경에서 npm i와 npm run build 실행
  • pre_build.sh
#!/bin/bash

npm install

if [ ! -z "$RUN_ENV" ] && [ "$RUN_ENV" == "dev" ]; then
    echo "DEV BUILD SETTING..."
    envsubst '${COUNTRY}' < .env.development > .env
    echo "" >> .env
else
    echo "PROD BUILD SETTING..."
    envsubst '${COUNTRY}' < .env.production > .env
    echo "" >> .env
fi

npm run build

🔸 Dockerfile 조정

  • Dockerfile은 빌드 결과물만을 복사하여 최종 이미지 생성 역할만 수행
  • Docker Layer 캐싱 제거 (오히려 속도를 저하시킴)
# --- 2) 최종 이미지(Stage: runner) ---
FROM public.ecr.aws/docker/library/node:20.5.1-alpine3.18

# 1. 작업 디렉터리 설정
WORKDIR /application

# 2. 배포 시 필요한 운영 의존성만 설치
COPY package*.json ./
RUN npm ci --omit=dev

# 3. 빌드 산출물(standalone, static, 설정 파일 등) 복사
COPY .next/standalone ./
COPY .next/static ./.next/static
COPY next.config.js ./
COPY run_server.js ./

# 4. 데이터 vercel 에 전송 안함
ENV NEXT_TELEMETRY_DISABLED=1

# 5. 포트 개방 및 실행 명령
EXPOSE 3000
CMD ["node", "run_server.js"]

🔸 .dockerignore 설정

  • 불필요한 파일을 빌드 context에서 제외하여 Docker 빌드 속도 향상
  • 기존에는 Dockerfile 안에서 빌드를 했기 때문에, .next 폴더가 .dockerignore 에 포함되어 있었지만, 다시 외부에서 빌드하고, Dockerfile 에서는 필요한 파일만 복사해 오는 형식으로 변경하므로, .dockerignore 에서 .next 폴더를 제외시켜야 docker context 를 로드하여 복사할 수 있다.
# Next.js 빌드/캐시 폴더
.next/
!/.next/standalone
!/.next/static

# 테스트 산출물
tests/
__tests__/
jest.config.js

node_modules
.git
.gitignore
Dockerfile
docker-compose.yml
README.md

# 2) 환경 변수
.env
.env.*

 

4. 빌드 로그로 개선 확인하기

🔹 S3 캐시 로딩

[Container] Downloading S3 cache...
Expanded cache path node_modules/**/*
Expanded cache path .next/cache/**/*

🔹 npm install 캐싱 적용 확인

[Container] Running command sh buildspec/pre_build.sh
up to date, audited 1724 packages in 12s

🔹 npm run build 캐싱 확인

  • .next/cache를 가져왔으나, 실제 빌드는 재실행됨
  • 캐시 활용 인식은 있으나, 파일 변경으로 전체 빌드가 실행되는 현상
DEV BUILD SETTING...
> next build
○  (Static) prerendered as static content
λ  (Dynamic) server-rendered on demand using Node.js

🔹 Docker 빌드 context 축소로 인한 성능 개선

  • Docker 빌드 context 크기 축소 (2.41GB → 155.82MB)
  • 전송 및 복사 속도 대폭 개선
[internal] load build context
#5 transferring context: 155.82MB 1.3s done

🔹 S3 캐시 업로드 확인

[Container] Uploading S3 cache...

 

5. 실제 소요 시간 비교

최초 vs 현재

단계 최초 현재
S3 캐시 다운로드 24s 15s
pre_build
(npm install / npm run build)
2m 51s 3m 12s
Docker context 로드 34.7s 1.6s
COPY 소스 파일 67.5s 2s
Docker 이미지 내보내기 12.9s 0.6s
post_build.sh 1m 10s 5s
S3 캐시 업로드 1m 30s 1m 27s
전체 평균 소요시간 8m 27s 6m 57s

 

동일 환경 추가 비교 (빠른 빌드 vs 느린 빌드)

  • 일한 빌드 세팅에서 간혹 8분 이상 걸리는 빌드가 존재함
    • 어떤 구간에서 오래걸리는지 오랜빌드 vs 짧은빌드 단계별 소요 시간 비교
단계 빠른 빌드 (5m 10s) 느린 빌드 (8m 10s)
S3 캐시 다운로드 17s 24s
pre_build
(npm install / npm run build)
3m 10s 3m 26s
Docker context 로드 3s 5s
npm ci CACHED 35s
COPY 소스 파일 1.5s 1.5s
Docker 이미지 내보내기 0.6s 6.9s
post_build.sh 4s 40s
S3 캐시 업로드 1m 17s 1m 39s
  • 네트워크 성능과 npm 패키지의 캐싱 여부 등 세부적인 차이로 인해 빌드 시간에 편차가 발생했습니다.
  • 프로세스와 연관지어보았을 때는 npm ci 가 캐시된 것과, 도커 이미지 파일이 캐싱된 점이 차이
  • 패키지의 설치는 없었으나, 이런한 차이가 보임.