이전 포스트(Git 연동 & Webhook, 브랜치/PR 트리거 설계)에서 Jenkins와 GitHub Webhook을 연동해
develop브랜치로의 PR merge 시 파이프라인이 자동으로 트리거되도록 설정했습니다.
이번 글에서는 해당 트리거를 기반으로, React + Vite 환경의 클라이언트 애플리케이션을 Docker 기반으로 빌드하고 원격 서버에 배포하는 파이프라인 Jenkinsfile 스크립트를 구성해 보겠습니다.
아래 Jenkinsfile은 크게 3단계로 구성되어 있습니다.
.tar 파일로 저장 각 단계는 Git 커밋 hash를 기반으로 버전 태깅을 자동화하여, 빌드 추적과 롤백이 용이하도록 설계했습니다.
latest + v1a2b3c)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"
}
}
}
sh "docker image prune -f" // Dangling 이미지 삭제
sh "docker builder prune -f --filter 'until=24h'" // 24시간 경과 캐시 삭제
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
// 원격 배포 폴더 생성 (이미 있으면 무시)
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/"
"""
원격 서버의 docker-compose.yml 예시:
version: '3.8'
services:
example-client:
image: example-client:latest
ports:
- "3000:3000"
restart: unless-stopped
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
"
"""
/home/ubuntu/app/jenkins/client/docker-compose.yml
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 device | Pre-cleaning 추가 (docker image prune -f) |
Permission denied | SSH_CRED_ID credential 재확인 |
docker compose not found | 서버에 docker-compose-plugin 설치 |
Image not found | docker load 성공 여부 로그 확인 |
실무에서는 Slack 알림 연동과 배포 승인 게이트를 추가하면 더욱 완벽합니다.
React SPA뿐 아니라 Vue, Next.js 등에도 동일하게 적용 가능합니다.
이 파이프라인은 Jenkins → Docker Build → 원격 서버 배포까지 완전 자동화된 클라이언트 배포 플로우를 제공합니다.
React + Vite 프로젝트뿐 아니라, Node.js 기반 SPA에서도 동일한 방식으로 확장할 수 있습니다.
필자의 경험상, 초기에는 한 서버 내에서 모든 CI/CD를 구성해 운영하는 것이 가장 안정적입니다.
이후 트래픽 증가나 서비스 분리 필요성이 생기면, 빌드 서버와 배포 서버를 분리하는 구조로 확장하는 것을 권장합니다.