From 446243910fa061d2d9c7f1bd724e2db7723196bb Mon Sep 17 00:00:00 2001 From: DEV-SERVER Date: Tue, 24 Feb 2026 13:20:48 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20stage=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EC=B9=98=20=EC=A0=9C=EA=B1=B0,=20main=EC=97=90=EC=84=9C=20Stag?= =?UTF-8?q?e=E2=86=92=EC=8A=B9=EC=9D=B8=E2=86=92Production=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=ED=9D=90=EB=A6=84=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - develop: 개발서버 자동 배포 (변경 없음) - main: Stage 자동 배포 → Jenkins 승인 → Production 재빌드+배포 - stage 브랜치 더 이상 사용 안함 - Next.js는 빌드 시 env 바인딩되므로 Stage/Production 별도 빌드 --- Jenkinsfile | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a0789999..ef4607be 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,22 +14,22 @@ pipeline { stage('Prepare Env') { steps { script { - def envFile = "/var/lib/jenkins/env-files/react/.env.${env.BRANCH_NAME}" - sh "cp ${envFile} .env.local" + if (env.BRANCH_NAME == 'main') { + sh "cp /var/lib/jenkins/env-files/react/.env.stage .env.local" + } else { + def envFile = "/var/lib/jenkins/env-files/react/.env.${env.BRANCH_NAME}" + sh "cp ${envFile} .env.local" + } } } } stage('Install') { - steps { - sh 'npm install --prefer-offline' - } + steps { sh 'npm install --prefer-offline' } } stage('Build') { - steps { - sh 'npm run build' - } + steps { sh 'npm run build' } } // ── develop → 개발서버 배포 ── @@ -53,9 +53,9 @@ pipeline { } } - // ── stage → 운영서버 Stage 배포 ── + // ── main → 운영서버 Stage 배포 ── stage('Deploy Stage') { - when { branch 'stage' } + when { branch 'main' } steps { sshagent(credentials: ['deploy-ssh-key']) { sh """ @@ -77,6 +77,26 @@ pipeline { } } + // ── 운영 배포 승인 ── + stage('Production Approval') { + when { branch 'main' } + steps { + timeout(time: 24, unit: 'HOURS') { + input message: 'Stage 확인 후 운영 배포를 진행하시겠습니까?\nStage: https://stage.sam.it.kr', + ok: '운영 배포 진행' + } + } + } + + // ── main → Production 재빌드 (운영 환경변수) ── + stage('Rebuild for Production') { + when { branch 'main' } + steps { + sh "cp /var/lib/jenkins/env-files/react/.env.main .env.local" + sh 'npm run build' + } + } + // ── main → 운영서버 Production 배포 ── stage('Deploy Production') { when { branch 'main' } From be3a6c05968ce117688423eab490e4f8f1f8e3aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Tue, 24 Feb 2026 17:41:21 +0900 Subject: [PATCH 2/5] =?UTF-8?q?ci:Jenkinsfile=202-branch=20=EC=A0=84?= =?UTF-8?q?=EB=9E=B5=EC=9C=BC=EB=A1=9C=20=EC=A0=84=ED=99=98=20(stage=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EC=B9=98=20=EC=A0=9C=EA=B1=B0,=20main?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Stage=E2=86=92=EC=8A=B9=EC=9D=B8=E2=86=92P?= =?UTF-8?q?roduction)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ef4607be..dfd52f3d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,6 +15,7 @@ pipeline { steps { script { if (env.BRANCH_NAME == 'main') { + // main: Stage 빌드 먼저 (승인 후 Production 재빌드) sh "cp /var/lib/jenkins/env-files/react/.env.stage .env.local" } else { def envFile = "/var/lib/jenkins/env-files/react/.env.${env.BRANCH_NAME}" @@ -39,14 +40,10 @@ pipeline { sshagent(credentials: ['deploy-ssh-key']) { sh """ rsync -az --delete \ - --exclude='.git' \ - --exclude='.env*' \ - --exclude='ecosystem.config.*' \ + --exclude='.git' --exclude='.env*' --exclude='ecosystem.config.*' \ .next package.json next.config.ts public node_modules \ ${DEPLOY_USER}@114.203.209.83:/home/webservice/react/ - scp .env.local ${DEPLOY_USER}@114.203.209.83:/home/webservice/react/.env.local - ssh ${DEPLOY_USER}@114.203.209.83 'cd /home/webservice/react && pm2 restart sam-react' """ } @@ -60,13 +57,10 @@ pipeline { sshagent(credentials: ['deploy-ssh-key']) { sh """ ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/react-stage/releases/${RELEASE_ID}' - rsync -az --delete \ .next package.json next.config.ts public node_modules \ ${DEPLOY_USER}@211.117.60.189:/home/webservice/react-stage/releases/${RELEASE_ID}/ - scp .env.local ${DEPLOY_USER}@211.117.60.189:/home/webservice/react-stage/releases/${RELEASE_ID}/.env.local - ssh ${DEPLOY_USER}@211.117.60.189 ' ln -sfn /home/webservice/react-stage/releases/${RELEASE_ID} /home/webservice/react-stage/current && cd /home/webservice && pm2 reload sam-front-stage 2>/dev/null || pm2 start react-stage/current/node_modules/.bin/next --name sam-front-stage -- start -p 3100 && @@ -104,13 +98,10 @@ pipeline { sshagent(credentials: ['deploy-ssh-key']) { sh """ ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/react/releases/${RELEASE_ID}' - rsync -az --delete \ .next package.json next.config.ts public node_modules \ ${DEPLOY_USER}@211.117.60.189:/home/webservice/react/releases/${RELEASE_ID}/ - scp .env.local ${DEPLOY_USER}@211.117.60.189:/home/webservice/react/releases/${RELEASE_ID}/.env.local - ssh ${DEPLOY_USER}@211.117.60.189 ' ln -sfn /home/webservice/react/releases/${RELEASE_ID} /home/webservice/react/current && cd /home/webservice && pm2 reload sam-front && @@ -126,4 +117,4 @@ pipeline { success { echo '✅ react 배포 완료 (' + env.BRANCH_NAME + ')' } failure { echo '❌ react 배포 실패 (' + env.BRANCH_NAME + ')' } } -} +} \ No newline at end of file From 4681a2a6a4ff88f3649c1c7f3730a9ad64b8afce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Tue, 24 Feb 2026 20:45:49 +0900 Subject: [PATCH 3/5] =?UTF-8?q?ci:Jenkinsfile=20Slack=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(slackSend=20#product=5Finfra)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index dfd52f3d..677173ad 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -114,7 +114,13 @@ pipeline { } post { - success { echo '✅ react 배포 완료 (' + env.BRANCH_NAME + ')' } - failure { echo '❌ react 배포 실패 (' + env.BRANCH_NAME + ')' } + success { + slackSend channel: '#product_infra', color: 'good', + message: "✅ *react* 배포 성공 (`${env.BRANCH_NAME}`)\n<${env.BUILD_URL}|빌드 #${env.BUILD_NUMBER}>" + } + failure { + slackSend channel: '#product_infra', color: 'danger', + message: "❌ *react* 배포 실패 (`${env.BRANCH_NAME}`)\n<${env.BUILD_URL}|빌드 #${env.BUILD_NUMBER}>" + } } } \ No newline at end of file From 1a4a009543ced79fa3c2047cc498e62da3f4c3a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Tue, 24 Feb 2026 21:00:04 +0900 Subject: [PATCH 4/5] =?UTF-8?q?ci:Jenkinsfile=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=20Slack=20=EC=95=8C=EB=A6=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 677173ad..b158af79 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,11 @@ pipeline { stages { stage('Checkout') { - steps { checkout scm } + steps { + slackSend channel: '#product_infra', color: '#439FE0', + message: "🚀 *react* 빌드 시작 (`${env.BRANCH_NAME}`)\n<${env.BUILD_URL}|빌드 #${env.BUILD_NUMBER}>" + checkout scm + } } stage('Prepare Env') { From 830567d6c92454ca049e1c0913c4ed2f69e929e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Tue, 24 Feb 2026 21:53:46 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:slackSend=EC=97=90=20tokenCredentialId?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(credential=20null=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b158af79..7780d3f9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -119,11 +119,11 @@ pipeline { post { success { - slackSend channel: '#product_infra', color: 'good', + slackSend channel: '#product_infra', color: 'good', tokenCredentialId: 'slack-token', message: "✅ *react* 배포 성공 (`${env.BRANCH_NAME}`)\n<${env.BUILD_URL}|빌드 #${env.BUILD_NUMBER}>" } failure { - slackSend channel: '#product_infra', color: 'danger', + slackSend channel: '#product_infra', color: 'danger', tokenCredentialId: 'slack-token', message: "❌ *react* 배포 실패 (`${env.BRANCH_NAME}`)\n<${env.BUILD_URL}|빌드 #${env.BUILD_NUMBER}>" } }