0. 시작하며
이번에는 지금껏 공부했던 NCP와 Docker를 GitHub Action과 함께 버무려서 CI/CD를 구축해보는 시간을 가지기로 했다.
NCP를 이론적으로만 공부해보았지, 이렇게 실습해보는 것은 처음이라 제대로 사용법을 익혀두고 정리해두는 편이 좋을 것이라 생각했다.
1. Next.js Standalone 설정
1-1) Standalone 옵션을 사용하는 이유?
기존의 npm run build 방식은 배포 후 애플리케이션을 실행할 때 node_modules 폴더 전체(500MB~1GB 이상)가 필요하다.
Next.js의 output:'standalone' 옵션을 적용하면 Next.js 번들러가 코드 분석을 통해 실제 실행에 꼭 필요한 파일만 추출하여 ./next/standalone 디렉토리에 독립 실행형 형태로 구성해준다.
=> 도커 이미지 크기가 80% 이상 감소하여 빌드 및 배포 속도가 대폭 단축된다는 장점이 있다.
1-2) next.config.mjs 파일 생성
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone', // 독립 실행형(Standalone) 빌드 활성화
};
export default nextConfig;
1-3) 로컬 실습
vscode를 사용하였고, 아래의 명령어를 통해 실습을 진행했다.
# 1. 의존성 패키지 설치
npm install
# 2. 로컬 개발 서버 실행 (정상 동작 확인용 - http://localhost:3000 접속)
npm run dev
# 3. Standalone 빌드 동작 확인
npm run build
3가지 명령어를 모두 실행하여 프로젝트 폴더 내에 .next/standalone 및 .next/static 폴더가 생성된 것을 확인할 수 있었다.

2. Multi-Stage Dockerfiler & .dockerignore 구조 분석 및 테스트
2-1) Multi-Stage 빌드 개념
하나의 Dockerfile 안에서 여러 개의 FROM 절을 사용하여 빌드 단계를 나눈다.
빌드에 필요한 임시 파일(eslint, devDependencies 등)은 중간 단계에서 버리고, 최종 런타임 단계에는 경량 Node 런타임과 결과물만 복사한다.
2-2) Dockerfile 해석
# Stage 1: Base Stage (Alpine 기반 기본 Node 18 환경 준비)
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm install --production # 프로덕션용 패키지 1차 설치
# Stage 2: Dependencies Stage (빌드에 필요한 개발 패키지까지 설치)
FROM base AS deps
WORKDIR /app
RUN npm install # devDependencies(타입스크립트, 린터 등) 포함 설치
# Stage 3: Build Stage (Next.js 애플리케이션 빌드)
FROM deps AS builder
WORKDIR /app
COPY . .
RUN npm run build # Standalone 빌드 수행
# Stage 4: Runtime Stage (최종 경량 배포 이미지)
FROM node:18-alpine AS runner
WORKDIR /app
# 보안 강화를 위해 root 계정이 아닌 전용 계정(nextjs:nodejs) 생성
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
# builder 단계에서 생성된 경량 standalone 파일과 static 파일만 복사
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를 노드로 직접 실행 (npm start 불필요)
CMD ["node", "server.js"]
각 라인별로 해석하여 주석으로 추가하였다.
2-3) 로컬 도커 빌드 & 테스트 명령어
위의 실습에 이어서 vscode에서 아래의 명령어들을 실행해 실습을 진행했다.
# 로컬 도커 이미지 빌드
docker build -t nextjs-docker .
# 컨테이너 실행 테스트
docker run -d -p 3000:3000 --name test-app nextjs-docker
# 확인 후 컨테이너 종료 및 삭제
docker rm -f test-app
위 과정은 Docker Desktop을 실행한 후에 명령어를 실행하였으나, 설치가 되어 있지 않다면 굳이 설치하여 실행할 필요까진 없다. 이후에 Github Actions 서버에서 자동으로 도커 이미지 빌드가 될 것이므로 생략하고 넘어가도 좋다.
3. NCP Container Registry & API 키 생성
3-1) NCP Container Registry 생성
NCP를 사용하는 것은 처음이기에, 우선 네이버 클라우드 플랫폼 NCP 홈페이지에 접속하여 회원가입부터 진행하였다.
결제 정보를 등록하면 약 30만원의 무료 크레딧을 제공해주기에, 실습하는 동안 과금 걱정이 없었다.
아래의 순서대로 NCP Container Registry를 생성하였다.
- NCP 콘솔 로그인 ➔ 상단 Console 클릭
- 왼쪽 메뉴: Services ➔ Containers ➔ Container Registry 이동
- 레지스트리 생성 버튼 클릭
- 레지스트리 이름 입력 (예: cnergy-registry)
- 생성 후 표시되는 Endpoint 주소 복사 (예: cnergy-registry.kr.ncr.ntruss.com)
3-2) NCP API 인증키 발급
GitHub Actions가 NCP 저장소에 이미지 푸시할 때에 인증받기 위해 Access Key와 Secret Key가 필요하다.
아래의 순서대로 진행하여 NCP API 인증키를 발급받았다.
- NCP 콘솔 우측 상단 [계정 이름] 클릭 ➔ 마이페이지 ➔ 인증키 관리
- 신규 API 인증키 생성 클릭
- Access Key ID 및 Secret Key 안전하게 복사해 두기
4. NCP Server (Ubuntu) 생성 및 Docker 환경 구축
4-1) NCP Ubuntu 서버 생성 & 공인 IP 신청
AWS Cloud를 사용할 때처럼 Ubuntu 서버를 생성하여 IP를 연결해주었다.
- NCP 콘솔: Services ➔ Compute ➔ Server ➔ 서버 생성 (Ubuntu 20.04 또는 22.04)
- Services ➔ Server ➔ Public IP ➔ 공인 IP 신청 후 생성한 서버에 연결
- 서버 관리 ➔ 관리자 비밀번호 확인에서 pem 제출 후 root 비밀번호 확인
4-2) ACG(방화벽) 설정
ACG(방화벽)을 필수적으로 설정해주어야하는데, 이는 AWS Cloud에서 보안 규칙을 설정해주는 것과 동일한 역할을 하는 것 같았다.
- Services ➔ Server ➔ ACG ➔ 서버 ACG 선택 후 ACG 설정 클릭
- Inbound 규칙 2개 추가:
- SSH 접속용: 프로토콜 TCP, 포트 22, IP 0.0.0.0/0
- 웹 서비스용: 프로토콜 TCP, 포트 80, IP 0.0.0.0/0
4-3) 서버 SSH 접속 및 Docker 자동 설치 명령어
vscode 터미널에서 NCP 공인 서버 IP로 접속한다.
ssh root@<NCP_서버_공인_IP>
서버 접속 후 아래의 명령어를 복사해 실행한다.
# 1. 패키지 업데이트 및 필수 툴 설치
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release
# 2. Docker 공식 GPG 키 및 저장소 추가
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 3. Docker 설치 및 동작 검증
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin
sudo docker run hello-world
5. GitHub Repository 생성 및 GitHub Secrets 설정
5-1) GitHub 저장소 업로드
vscode 터미널에서 아래의 명령어로 github 저장소에 현재의 프로젝트를 업로드한다.
git init
git add .
git commit -m "feat: Initial commit for NCP CI/CD"
git branch -M main
git remote add origin https://github.com/본인계정/ncp-docker-cicd-practice.git
git push -u origin main
5-2) GitHub Repository Secrets 등록
GitHub 저장소 -> Setting -> Secrets and variables -> Actions -> New repository secret에 들어가서 아래의 7개의 Secret을 등록해준다.
| NCP_CONTAINER_REGISTRY | cnergy-registry.kr.ncr.ntruss.com |
| NCP_ACCESS_KEY | NCP API Access Key ID |
| NCP_SECRET_ACCESS_KEY | NCP API Secret Key |
| NCP_HOST | NCP 서버 공인 IP 주소 |
| NCP_USERNAME | root |
| NCP_PASSWORD | NCP 서버 비밀번호 |
| NCP_PORT | 22 |
6. GitHub Actions(deploy.yml) 코드 분석 및 자동 배포 트리거
6-1) yml 동작 해설
| Job 1 - push_to_registry |
- actions/checkout@v3: 소스 코드 다운로드
- docker/login-action@v3: NCP 레지스트리에 API 키로 로그인
- docker/build-push-action@v3: Multi-stage 빌드 수행 후 ${{ secrets.NCP_CONTAINER_REGISTRY }}/cnergy-frontend:$PP github.sha }} 태그로 NCP 레지스트리에 이미지 푸시
| Job 2 - pull_from_registry |
- appleboy/ssh-action@master: Secrets 정보로 NCP 서버에 SSH 접속
- 원격 실행 스크립트:
docker login -u ${{ secrets.NCP_ACCESS_KEY }} -p ${{ secrets.NCP_SECRET_ACCESS_KEY }} $REGISTRY
docker pull $IMAGE_FULL # 최신 이미지 다운로드
docker stop cnergy-frontend || true # 기존 서비스 중지
docker rm cnergy-frontend || true # 기존 컨테이너 삭제
docker run -d -p 80:3000 --name cnergy-frontend $IMAGE_FULL # 80번 포트로 새 앱 띄우기
docker image prune -f # 미사용 구버전 이미지 정리
6-2) 최종 동작 검증
GitHub Actions에서 Job이 녹색 체크 (✅)가 나오는지 확인한다.
실패시 아래와 같이 빨간색 X표시가 표현되고, 성공적으로 동작시 녹색 체크표시가 보인다.

7. 마무리하며
이번 시간에는 CI/CD 구축하는 실습을 진행했는데, NCP를 사용했다는 점이 다를 뿐 이전에 AWS Cloud를 사용해서 CI/CD를 구축했을 때와 크게 다를 것이 없었다고 느꼈다.
요즘 AI로 코딩을 많이 해서 이 과정들을 모두 완벽히 알고 있어야된다고 생각하진 않지만, 이렇게 원리와 흐름을 하나씩 파악해보는 시간은 뜻깊었던 것 같다.
'[INFRA]' 카테고리의 다른 글
| Prometheus, Grafana를 이용한 모니터링 & 로깅 (1) | 2026.08.02 |
|---|---|
| 프로젝트 배포 이해하기 (10) | 2025.08.10 |