React + Vite 파이프라인 스크립트

DevBadger·2026년 1월 21일

Jenkins

목록 보기
5/6
post-thumbnail

이전 포스트(Git 연동 & Webhook, 브랜치/PR 트리거 설계)에서 Jenkins와 GitHub Webhook을 연동해 develop 브랜치로의 PR merge 시 파이프라인이 자동으로 트리거되도록 설정했습니다.
이번 글에서는 해당 트리거를 기반으로, React + Vite 환경의 클라이언트 애플리케이션을 Docker 기반으로 빌드하고 원격 서버에 배포하는 파이프라인 Jenkinsfile 스크립트를 구성해 보겠습니다.

파이프라인 전체 구조

아래 Jenkinsfile은 크게 3단계로 구성되어 있습니다.

  1. Build & Save Image: 로컬에서 Docker 이미지를 빌드하고 .tar 파일로 저장
  2. Transfer to Remote: SCP를 이용해 빌드된 이미지를 원격 서버로 전송
  3. Remote Deploy: 원격 서버에서 컨테이너 재시작 및 이미지 정리

각 단계는 Git 커밋 hash를 기반으로 버전 태깅을 자동화하여, 빌드 추적과 롤백이 용이하도록 설계했습니다.

주요 특징

  • Git 커밋 hash 자동 태깅 (롤백 용이)
  • 듀얼 태그 (latest + v1a2b3c)
  • 원격 배포 (SSH + docker-compose)
  • 자동 정리 (디스크 용량 문제 방지)

전체 스크립트 및 주석

pipeline {
    agent any
    options {
        timestamps() // 콘솔 로그에 타임스탬프 표시
    }

    environment {
        // [필수 설정] 원격 서버 접속 정보
        REMOTE_HOST = "xx.xxx.xxx"
        // 원격 서버에서 Docker 컨테이너 실행 유저
        REMOTE_USER = "ubuntu"
        SSH_CRED_ID = "web-server-credentails" // Jenkins에 등록된 SSH Key ID
        DEPLOY_PATH = "/home/ubuntu/app/jenkins/client"

        // 이미지 및 파일 이름 정의
        IMAGE_NAME = "example-client:latest"
        TAR_NAME = "example-client.tar"
        
        NODE_OPTIONS = "--max-old-space-size=4096"
    }

     stages {
        stage('Build & Save Image') {
          steps {
            script {
              // 🚨 디스크 용량 확보 (Pre-cleaning)
              echo "🧹 Pre-cleaning Docker space..."
              sh "docker image prune -f"
              sh "docker builder prune -f --filter 'until=24h'"

              // Git Short Hash 추출
              def gitVer = sh(returnStdout: true, script: "git rev-parse --short HEAD").trim()

              // 빌드 이름을 '#빌드번호-태그' 형식으로 변경 (예: #13-a1b2c3d)
              currentBuild.displayName = "#${env.BUILD_NUMBER}-${gitVer}"
              echo "🏷️ Building version: ${currentBuild.displayName}"

              // 이미지 태그 설정
              def baseImageName = IMAGE_NAME.replace(":latest", "")
              def imgTagged = "${baseImageName}:${gitVer}"

              echo "🐳 Building Docker Image (Tags: ${gitVer}, latest)..."
              sh "docker build -t ${imgTagged} -t ${IMAGE_NAME} ."

              echo "📦 Archiving Image to Tar..."
              sh "docker save -o ${TAR_NAME} ${imgTagged} ${IMAGE_NAME}"
            }
          }
        }

        // 2️⃣ 원격 서버로 tar 파일 전송
        stage('Transfer to Remote') {
            steps {
                withCredentials([sshUserPrivateKey(credentialsId: env.SSH_CRED_ID, keyFileVariable: 'SSH_KEY')]) {
                    script {
                        echo "🚀 Transferring image tar to $REMOTE_HOST..."

                        // 원격 배포 폴더 생성 (이미 있으면 무시)
                        sh "ssh -i \"$SSH_KEY\" -o StrictHostKeyChecking=no \"$REMOTE_USER@$REMOTE_HOST\" \"mkdir -p $DEPLOY_PATH\""

                        // Docker 이미지 tar 파일 전송
                        sh "scp -i \"$SSH_KEY\" -o StrictHostKeyChecking=no ${TAR_NAME} \"$REMOTE_USER@$REMOTE_HOST:$DEPLOY_PATH/\""
                    }
                }
            }
        }

        // 3️⃣ 원격 배포 및 서비스 재시작
        stage('Remote Deploy') {
            steps {
                withCredentials([sshUserPrivateKey(credentialsId: env.SSH_CRED_ID, keyFileVariable: 'SSH_KEY')]) {
                    script {
                        echo "🚀 Executing remote deployment on $REMOTE_HOST..."

                        // 원격 서버 접속 후 이미지 로드 및 컨테이너 재기동
                        sh """
                        ssh -i \"$SSH_KEY\" -o StrictHostKeyChecking=no \"$REMOTE_USER@$REMOTE_HOST\" "
                            cd $DEPLOY_PATH
                            echo '📥 Loading Docker Image...'
                            docker load -i ${TAR_NAME}

                            echo '🔄 Restarting Services...'
                            docker compose down
                            docker compose up -d

                            echo '🧹 Cleaning up...'
                            rm ${TAR_NAME}
                            docker image prune -f
                        "
                        """
                    }
                }
            }
        }
    }

    // ✅ 파이프라인 종료 후 후처리
    post {
		success { echo "✅ Client Deployment Successful" }
    	failure { echo "❌ Failed" }
    	cleanup { 
      	sh "rm -f *.tar" 
      	sh "docker image prune -f"
      }
   }
}

단계별 상세 설명

1. 디스크 용량 최적화 (Pre-cleaning)

sh "docker image prune -f"                    // Dangling 이미지 삭제
sh "docker builder prune -f --filter 'until=24h'" // 24시간 경과 캐시 삭제

2. 자동 버전 태깅

def gitVer = sh(returnStdout: true, script: "git rev-parse --short HEAD").trim()
// 결과: "a1b2c3d" (7자리 커밋 hash)

def imgTagged = "example-client:a1b2c3d"
sh "docker build -t ${imgTagged} -t example-client:latest ."

빌드 결과: example-client:latest + example-client:a1b2c3d

3. SSH로 산출물(tar) 전송

// 원격 배포 폴더 생성 (이미 있으면 무시)
sh """
  ssh -i "\$SSH_KEY" \
    -o StrictHostKeyChecking=no \
    "\$REMOTE_USER@\$REMOTE_HOST" \
    "mkdir -p \$DEPLOY_PATH"
"""

// Docker 이미지 tar 파일 전송
sh """
  scp -i "\$SSH_KEY" \
    -o StrictHostKeyChecking=no \
    ${TAR_NAME} \
    "\$REMOTE_USER@\$REMOTE_HOST:\$DEPLOY_PATH/"
"""

4. 원격 배포 자동화

원격 서버의 docker-compose.yml 예시:

version: '3.8'
services:
  example-client:
    image: example-client:latest
    ports:
      - "3000:3000"
    restart: unless-stopped

5. 원격 실행

SSH로 원격 서버를 접속하여 이미지를 로드하고 컨테이너를 재실행 시킵니다.

// 원격 서버 접속 후 이미지 로드 및 컨테이너 재기동
sh """
  ssh -i "\$SSH_KEY" \
    -o StrictHostKeyChecking=no \
    "\$REMOTE_USER@\$REMOTE_HOST" "
    
    cd \$DEPLOY_PATH
    
    echo '📥 Loading Docker Image...'
    docker load -i ${TAR_NAME}
    
    echo '🔄 Restarting Services...'
    docker compose down
    docker compose up -d
    
    echo '🧹 Cleaning up...'
    rm ${TAR_NAME}
    docker image prune -f
  "
"""

배포 서버 필수 설정

1. docker-compose.yml 위치

/home/ubuntu/app/jenkins/client/docker-compose.yml

2. Jenkins Credential 등록

ID: web-server-credentails
Kind: SSH Username with private key
Username: ubuntu
Private Key: ~/.ssh/id_rsa (서버 SSH 키)

Jenkins UI에서 GitHub Credentails 생성하는 곳에서 만들어 줍니다. Private Key 같은 경우 Enter directly 체크하여 .pem을 text 편집기로 열어 나온 내용 전부를 넣어줍니다.

트러블슈팅

문제해결법
No space left on devicePre-cleaning 추가 (docker image prune -f)
Permission deniedSSH_CRED_ID credential 재확인
docker compose not found서버에 docker-compose-plugin 설치
Image not founddocker load 성공 여부 로그 확인

실무에서는 Slack 알림 연동과 배포 승인 게이트를 추가하면 더욱 완벽합니다.
React SPA뿐 아니라 Vue, Next.js 등에도 동일하게 적용 가능합니다.

마무리

이 파이프라인은 Jenkins → Docker Build → 원격 서버 배포까지 완전 자동화된 클라이언트 배포 플로우를 제공합니다.
React + Vite 프로젝트뿐 아니라, Node.js 기반 SPA에서도 동일한 방식으로 확장할 수 있습니다.

필자의 경험상, 초기에는 한 서버 내에서 모든 CI/CD를 구성해 운영하는 것이 가장 안정적입니다.
이후 트래픽 증가나 서비스 분리 필요성이 생기면, 빌드 서버와 배포 서버를 분리하는 구조로 확장하는 것을 권장합니다.

profile
개발 오소리

0개의 댓글