개발(Dev)·검증(QA/Stage)·운영(Prod) 배포를 위한 버전 정보”를 일관되게 만들고, 추적·승격이 쉬운 기준을 정리하고 바로 쓸 수 있는 Jenkinsfile 패턴
핵심 원칙(요약)
한 번 빌드 → 다단계 승격(Dev→QA→Prod)
환경마다 새로 빌드하지 말고, 동일 아티팩트를 승격하세요. 버전은 빌드 순간에만 확정됩니다.
버전 = 사람 친화(SemVer/CalVer) + 기계 추적(Git SHA)
예) 1.4.0-rc.7+gabc1234 또는 2025.09.28-rc.7+gabc1234
환경별 접미사 규칙 통일
Dev: -alpha.<branch>.<build>
QA : -rc.<build>
Prod: 접미사 제거(정식 릴리스), 태그로 고정
여러 태그를 동시에 푸시(동일 이미지/패키지)
app:1.4.0-rc.7+gabc1234, app:1.4.0-rc.7, (선택) app:1.4.0 등.
단, Prod에서만 major.minor/major 추가 태그를 허용하고 latest는 운영 금지.
버전 전파는 원자적
산출물(JAR/WHL), Docker 이미지, Helm/Compose, 앱 메타(properties) 모두 동일 버전 문자열로 주입.
재현성과 감사성 강화
버전 문자열에 Git SHA, Build URL, (선택) SBOM/체크섬을 함께 보관. version.txt로 아티팩트에 포함.
권장 버전 체계(샘플)
기본: SemVer + 프리릴리스 + 빌드메타
Dev : 1.4.0-alpha.feature-login.153+gabc1234
QA : 1.4.0-rc.7+gabc1234
Prod : 1.4.0+gabc1234 (또는 단순 1.4.0 태그로 릴리스)
CalVer 선호 시: YYYY.MM.DD[-rc.N][-alpha.x]+gSHA 유지.
예) QA: 2025.09.28-rc.7+gabc1234
Jenkinsfile 공용 함수 (버전 생성)
아래 블록을 Jenkinsfile 최상단 script 또는 공유 라이브러리로 두고 호출하세요.
// 환경값 예: env.DEPLOY_ENV in ['dev','qa','prod']
// 필수: Multibranch/SCM 체크아웃 완료 상태
def computeVersion(String deployEnv) {
// 1) Git 기초정보
def sha = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
def dirty = sh(script: "git status --porcelain | wc -l", returnStdout: true).trim()
def isClean = (dirty == "0")
def branch = env.BRANCH_NAME ?: sh(script: "git rev-parse --abbrev-ref HEAD", returnStdout: true).trim()
// 2) 태그 기반(없으면 CalVer로 폴백)
def base
def lastTag = sh(script: "git describe --tags --abbrev=0 2>/dev/null || echo ''", returnStdout: true).trim()
if (lastTag) {
base = lastTag
} else {
base = sh(script: "date +%Y.%m.%d", returnStdout: true).trim()
}
// 3) 환경별 규칙
if (deployEnv == "dev") {
// 브랜치명 안전화
def safeBranch = branch.replaceAll('[^A-Za-z0-9._-]', '-')
return "${base}-alpha.${safeBranch}.${env.BUILD_NUMBER}+g${sha}${isClean ? '' : '.dirty'}"
}
if (deployEnv == "qa") {
return "${base}-rc.${env.BUILD_NUMBER}+g${sha}${isClean ? '' : '.dirty'}"
}
if (deployEnv == "prod") {
// 정식 릴리즈(접미사 제거). base가 SemVer가 아니라 CalVer일 수도 있음
return "${base}+g${sha}"
}
error "Unknown DEPLOY_ENV=${deployEnv}"
}
파이프라인 예시(빌드 1회, 환경별 승격)
Build 파이프라인: 한 번만 빌드 → 레지스트리/아티팩트에 보관
Deploy 파이프라인: 환경 파라미터로 버전 선택 후 배포(재빌드 금지)
(A) Build 파이프라인(JAR/Docker 생성)
pipeline {
agent any
options { timestamps() }
stages {
stage('Checkout'){ steps { checkout scm } }
stage('Compute Version for Dev Build') {
steps {
script {
env.DEPLOY_ENV = 'dev' // 빌드 단계는 dev 스냅샷 성격
env.APP_VERSION = computeVersion(env.DEPLOY_ENV)
sh "echo ${env.APP_VERSION} > version.txt"
}
}
}
stage('Package / Image Build') {
steps {
sh """
# 예: Maven/Jar
mvn -B -Drevision=${APP_VERSION} package
# Docker
docker build -t myrepo/myapp:${APP_VERSION} .
"""
}
}
stage('Push Artifacts') {
steps {
sh """
docker push myrepo/myapp:${APP_VERSION}
# 아티팩트 저장소에도 version.txt/JAR 업로드 등
"""
}
}
stage('Metadata') {
steps {
script {
currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.APP_VERSION}"
}
}
}
}
}
(B) Deploy 파이프라인(Dev/QA/Prod 공용)
실행 시 입력으로 버전을 고르거나, 브랜치/환경 규칙으로 자동 선택.
pipeline {
agent any
parameters {
choice(name: 'DEPLOY_ENV', choices: ['dev','qa','prod'], description: '배포 환경')
string(name: 'VERSION', defaultValue: '', description: '배포할 정확한 버전(빈값이면 규칙에 따라 계산/선택)')
}
stages {
stage('Checkout'){ steps { checkout scm } }
stage('Resolve Version') {
steps {
script {
if (!params.VERSION?.trim()) {
env.APP_VERSION = computeVersion(params.DEPLOY_ENV)
} else {
env.APP_VERSION = params.VERSION.trim()
}
echo "Deploy Version => ${env.APP_VERSION}"
}
}
}
stage('Retag for Env (if needed)') {
when { expression { return params.DEPLOY_ENV != 'dev' } }
steps {
script {
// 동일 이미지 재활용(빌드 금지)
sh """
docker pull myrepo/myapp:${APP_VERSION} || true
"""
if (params.DEPLOY_ENV == 'qa') {
// QA 표준 별칭(선택)
sh """
docker tag myrepo/myapp:${APP_VERSION} myrepo/myapp:qa-current
docker push myrepo/myapp:qa-current
"""
}
if (params.DEPLOY_ENV == 'prod') {
// Prod 릴리스 태그 고정 (SemVer일 경우만 추가 별칭 권장)
def semver = sh(script: "echo '${APP_VERSION}' | sed -n 's/\\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\).*/\\1/p'", returnStdout: true).trim()
if (semver) {
def majorMinor = semver.split('\\.')[0..1].join('.')
sh """
docker tag myrepo/myapp:${APP_VERSION} myrepo/myapp:${semver}
docker push myrepo/myapp:${semver}
docker tag myrepo/myapp:${APP_VERSION} myrepo/myapp:${majorMinor}
docker push myrepo/myapp:${majorMinor}
"""
}
}
}
}
}
stage('Deploy') {
steps {
// Compose/Helm/Ansible 등 버전 주입
sh """
sed -i "s#\\(image: myrepo/myapp:\\).*#\\1${APP_VERSION}#g" deploy/compose.yaml
docker compose -f deploy/compose.yaml up -d
"""
}
}
stage('(Prod Only) Git Tag') {
when { allOf { expression { return params.DEPLOY_ENV == 'prod' } } }
steps {
script {
// 보호된 브랜치에서만 수행 권장
def tagName = sh(script: "echo '${APP_VERSION}' | sed -n 's/+.*//p'", returnStdout: true).trim()
if (tagName) {
sh """
git config user.email "ci@example.com"
git config user.name "jenkins"
git tag -a ${tagName} -m "Release ${tagName}"
git push origin ${tagName} || true
"""
}
}
}
}
}
}
실무 체크리스트
빌드 1회 원칙: QA/Prod는 재빌드 금지, 이미지/아티팩트만 승격
환경별 접미사: Dev=-alpha.*, QA=-rc.*, Prod=없음(정식)
Git 태그: Prod 릴리스에만 주석 포함(annotated) 태그 발행
동일 버전 전파: Docker/Helm/Compose/앱 설정 모두 같은 문자열 사용
추적 정보 포함: +g<shortSHA>, BUILD_URL, SBOM/체크섬(옵션)
태그 폴백: 태그 없으면 CalVer로 폴백
금지: 운영에서 latest 사용, 운영에서 재빌드