개발 도구

Next.js 정적 사이트를 Jenkins로 자동 배포하는 방법 — Jenkinsfile로 빌드부터 배포까지

매번 수동으로 빌드하고 서버에 올리던 Next.js 정적 사이트 배포를 Jenkins 파이프라인으로 자동화한 방법을 정리했다. Checkout부터 scp 배포까지 단계별로 설명한다.

Next.jsJenkins자동배포CI/CD정적사이트
Next.js 정적 사이트를 Jenkins Pipeline으로 자동 배포하는 Jenkinsfile 전체 구조 — Checkout, Install, Build, Deploy 단계 포함
  • ·output: 'export': next.config.ts에 설정 시 빌드 결과가 out/ 폴더에 정적 파일로 생성됨
  • ·npm ci: package-lock.json 기준으로 정확히 고정된 버전 설치. npm install보다 재현성이 높음
  • ·scp: SSH 프로토콜 기반 파일 전송 명령. 별도 소프트웨어 설치 없이 SSH 접근 권한만으로 사용 가능
  • ·withCredentials: Jenkins Credentials에 저장된 SSH 키를 환경변수로 안전하게 전달하는 블록
Next.js 정적 사이트를 매번 로컬에서 빌드하고 수동으로 scp로 올리던 작업을 Jenkins 파이프라인으로 자동화했다. Checkout, Install, Build, Deploy 네 단계로 나누고 나니 구조가 명확해졌다. 처음에는 Deploy 단계에서 SSH 인증을 어떻게 처리할지 몰라서 막혔는데, Jenkins Credentials에 SSH 개인키를 등록하고 withCredentials 블록으로 감싸는 방식으로 해결했다. 그 뒤로는 코드를 push하면 배포까지 자동으로 끝난다.

Next.js 정적 사이트 빌드와 Jenkins 배포 구조 이해

Next.js 정적 사이트 빌드 결과물과 Jenkins 배포의 연결 구조

Next.js를 정적 사이트로 배포하려면 next.config.ts에 output: 'export'를 설정한 뒤 npm run build를 실행한다. 빌드가 완료되면 프로젝트 루트에 out/ 폴더가 생성되고, 그 안에 HTML, CSS, JavaScript, 이미지 등 정적 파일 전체가 담긴다. 이 out/ 폴더를 Nginx나 Apache가 바라보는 경로에 복사해두면 배포가 완료된다. 서버에서 Node.js 런타임이 필요 없고 정적 파일만 서빙하면 되기 때문에 인프라 부담이 낮다. Jenkins는 이 흐름을 자동화하는 역할이다. 코드를 Git 저장소에서 내려받고, 의존성을 설치하고, 빌드를 실행하고, out/ 폴더를 서버에 전송하는 과정을 파이프라인으로 묶어두면 이후에는 배포를 의식하지 않아도 된다. 처음에는 배포 단계가 어렵게 느껴졌는데, 결국 빌드 서버에서 대상 서버로 파일을 복사하는 작업이 전부라는 걸 파악하고 나니 구조가 단순하게 정리됐다. Jenkins 빌드 서버가 배포 대상 서버에 SSH로 접근할 수 있어야 한다는 조건만 맞추면 나머지는 scp 명령 한 줄로 해결된다.

Jenkins Pipeline으로 Next.js를 자동 배포하는 전체 흐름

Next.js 정적 사이트 Jenkins 파이프라인은 크게 네 단계로 구성된다. 첫 번째 Checkout 단계에서는 Git 저장소에서 코드를 내려받는다. 두 번째 Install 단계에서는 npm ci로 의존성을 설치한다. 세 번째 Build 단계에서는 npm run build로 정적 파일을 생성한다. 네 번째 Deploy 단계에서는 out/ 폴더를 대상 서버에 전송한다. 각 단계를 별도 stage로 나눠두면 어느 단계에서 실패했는지 Jenkins 화면에서 바로 파악할 수 있어서 디버깅이 편하다. 모든 단계를 하나의 sh 블록에 넣는 것도 기술적으로는 가능하지만, 실패했을 때 어디서 문제가 생겼는지 찾기 어려워진다. 파이프라인을 처음 구성할 때 Deploy 단계를 제외한 Build까지만 먼저 확인하고, 정상인 걸 확인한 뒤 Deploy를 붙이는 방식으로 단계별로 검증하면서 설정하는 게 수월하다.

Jenkinsfile로 Next.js 빌드 자동화하기

Jenkinsfile에 Next.js 빌드 스테이지 작성하는 방법

Next.js 빌드를 Jenkins에서 실행하려면 Jenkinsfile에 Node.js 도구 선언과 빌드 스테이지가 필요하다. tools 블록에 사용할 Node.js 버전을 명시하고, stages 안에 Install과 Build 단계를 작성한다. Install 단계에서는 sh 'npm ci'를 실행한다. npm install 대신 npm ci를 쓰는 이유는 package-lock.json을 기준으로 정확히 고정된 버전을 설치하기 때문이다. npm install은 package.json의 버전 범위 안에서 최신 버전을 설치할 수 있어서 CI 환경에서 의도치 않은 버전이 들어올 위험이 있다. Build 단계에서는 sh 'npm run build'를 실행한다. next.config.ts에 output: 'export'가 설정되어 있으면 빌드 완료 후 out/ 폴더가 생성된다. 처음 파이프라인을 작성할 때 BUILD_NUMBER나 GIT_COMMIT 같은 Jenkins 내장 환경변수를 빌드 로그에 찍어두면 나중에 어떤 커밋이 어느 빌드에서 실행됐는지 추적하는 데 도움이 된다.

Jenkins Next.js 배포에서 npm ci와 npm run build를 쓰는 이유

CI 환경에서 npm install 대신 npm ci를 써야 하는 이유는 재현성 때문이다. npm install은 package.json에 명시된 버전 범위 안에서 최신 버전을 설치한다. ^18.0.0처럼 캐럿이 붙어 있으면 18.x.x 중 가장 최신을 설치하는데, CI 실행 시점에 따라 설치되는 버전이 달라질 수 있다. 로컬에서 npm install로 세팅한 환경과 CI 서버 환경이 미묘하게 달라지는 원인이 여기에 있다. npm ci는 package-lock.json에 적힌 정확한 버전을 그대로 설치한다. 버전 범위 해석 없이 lock 파일 기준으로 동작하기 때문에 실행 시점과 무관하게 항상 동일한 의존성 트리가 구성된다. 속도도 npm install보다 빠른 편이다. npm run build는 package.json의 scripts.build에 정의된 명령을 실행하고, Next.js 프로젝트에서 output: 'export'가 설정되어 있으면 out/ 폴더가 생성된다. pnpm을 쓰는 프로젝트라면 pnpm install --frozen-lockfilepnpm run build로 대응하면 된다.

빌드된 Next.js 정적 파일을 서버에 배포하기

Jenkins Pipeline에서 scp로 Next.js 빌드 파일을 서버에 전송하는 방법

빌드가 완료된 out/ 폴더를 서버에 전송하려면 Deploy 단계에서 scp 명령을 사용한다. scp는 SSH 프로토콜 기반 파일 전송 명령으로, 대상 서버에 SSH 접근 권한이 있으면 별도 설정 없이 쓸 수 있다. Jenkins에서 SSH 개인키를 안전하게 관리하려면 Jenkins Credentials에 등록하고 withCredentials 블록으로 참조하는 방식을 사용한다. Credentials에 'SSH Username with private key' 타입으로 등록하면 Jenkins가 빌드 시 해당 키를 임시 파일로 만들어 환경변수로 전달해준다. Deploy 단계에서 scp -r -i $SSH_KEY out/ user@server:/var/www/html/ 형태로 실행하면 out/ 폴더 전체가 서버의 지정 경로로 복사된다. 처음에 개인키를 Jenkinsfile에 직접 작성하려 했는데, 키가 코드에 노출되면 보안 문제가 생기기 때문에 반드시 Credentials를 통해 관리해야 한다. 배포 전 기존 파일을 지우고 싶다면 ssh로 서버에 접속해서 rm -rf 명령을 먼저 실행한 뒤 scp로 새 파일을 올리는 방식으로 처리하면 된다.

pipeline {
  agent any
  tools { nodejs 'NodeJS 18' }
  stages {
    stage('Checkout') { steps { checkout scm } }
    stage('Install')  { steps { sh 'npm ci' } }
    stage('Build')    { steps { sh 'npm run build' } }
    stage('Deploy') {
      steps {
        withCredentials([sshUserPrivateKey(
          credentialsId: 'deploy-ssh-key',
          keyFileVariable: 'SSH_KEY'
        )]) {
          sh 'scp -r -i $SSH_KEY -o StrictHostKeyChecking=no out/ user@your-server:/var/www/html/'
        }
      }
    }
  }
  post {
    success { echo '배포 완료' }
    failure { echo '배포 실패' }
  }
}

Jenkins 배포 후 Next.js 정적 파일 서빙 확인하는 방법

배포가 완료됐다고 해서 끝이 아니다. 서버에서 Nginx가 올바른 경로를 바라보고 있는지 확인해야 한다. scp로 out/ 폴더를 /var/www/html/ 경로에 넣었다면 Nginx 설정의 root 항목이 그 경로를 가리키고 있어야 한다. 배포 후 브라우저에서 접속했을 때 404가 뜬다면 Nginx root 경로가 out/ 하위가 아닌 out/ 자체를 가리키고 있는지 확인한다. Next.js 정적 빌드는 index.html이 out/ 바로 아래에 있기 때문에 루트 설정만 맞으면 별도 설정 없이 바로 서빙된다. Jenkins 파이프라인의 post { success } 블록에 배포 완료 메시지 외에 curl로 서버 응답을 확인하는 커맨드를 추가해두면 배포 직후 서버가 정상 응답하는지 파이프라인 안에서 검증할 수 있다. sh 'curl -s -o /dev/null -w "%{http_code}" http://your-server'를 추가하면 HTTP 상태 코드가 로그에 찍히기 때문에 200이 아닌 경우 즉시 확인할 수 있다. 파이프라인이 완성되고 나면 코드를 push할 때마다 빌드와 배포가 자동으로 이뤄지니, 이후에는 서버를 신경 쓰지 않아도 된다.

자주 묻는 질문

output: 'export' 없이 빌드하면 Jenkins 배포가 안 되나요?+

output: 'export' 없이 빌드하면 정적 파일 대신 Node.js 서버 실행이 필요한 .next/ 폴더가 생성됩니다. 이 경우 scp로 파일만 복사하는 방식이 아니라 서버에서 Node.js 프로세스를 실행하는 방식으로 배포해야 합니다. 단순 정적 파일 배포를 원한다면 next.config.ts에 output: 'export'를 추가하세요.

빌드 서버와 배포 서버가 같아도 되나요?+

가능합니다. Jenkins가 설치된 서버에 Nginx도 함께 운영하고 out/ 폴더를 그 서버의 웹 루트로 복사하면 됩니다. 다만 Jenkins 빌드 작업이 서버 리소스를 점유하는 동안 웹 서빙에 영향이 생길 수 있어서, 규모가 커지면 분리하는 게 좋습니다.

scp 대신 rsync를 써도 되나요?+

네, rsync가 더 효율적입니다. rsync는 변경된 파일만 전송하기 때문에 파일이 많을수록 전송 속도가 빠릅니다. `rsync -avz --delete -e 'ssh -i $SSH_KEY' out/ user@server:/var/www/html/` 형태로 사용하면 됩니다. --delete 옵션을 추가하면 서버에서 삭제된 파일도 동기화됩니다.

Deploy 단계에서 permission denied 오류가 나면 어떻게 하나요?+

두 가지를 확인하세요. 먼저 Jenkins Credentials에 등록한 SSH 키가 맞는 키인지, credentialsId가 정확히 일치하는지 확인합니다. 그다음 scp 명령에 -o StrictHostKeyChecking=no 옵션이 있는지 확인합니다. 이 옵션이 없으면 처음 접속하는 서버의 known_hosts 확인 과정에서 멈출 수 있습니다.

관련 글