Jenkins로 CI/CD 설계하기: 빌드 3가지 패턴 정리

DevBadger·2026년 1월 13일

Jenkins

목록 보기
2/6
post-thumbnail

Jenkins로 CI/CD를 설계할 때는 “빌드를 어디서 하고, 어떤 형태의 결과물을 어디서 어떻게 실행할 것인가”를 먼저 정리하는 것이 좋습니다. 본글에서 실무에서 자주 쓰이는 세 가지 패턴을 중심으로 정리하려고 합니다. 해당 패턴들은 배포 서버 구조, 보안(아웃바운드 규칙 등), 팀 규모에 따라 선택이 달라질 수 있습니다.

패턴 1: 배포 서버에서 git pull + docker compose 스크립트

이 패턴은 가장 원초적인 방법으로, 가장 쉬운 방법입니다. Jenkins가 원격 서버에 접속해 git pulldocker compose 또는 Dockerfile 를 실행하게 하는 방식입니다. 즉, 코드 체크아웃과 컨테이너 빌드·실행을 모두 “배포 대상 서버”에서 처리합니다.

Jenkins 역할

  • 웹훅 또는 스케줄로 파이프라인 트리거
  • SSH로 배포 서버 접속 후 아래와 같은 명령어 스크립트 실행
# 해당 디렉토리 이동 
cd /home/ubuntu/app

# git pull 
git pull origin main 

# docker compose 내림. 볼륨은 선택 
docker compose -f docker-compose.yml down -v 

# docker compose 실행 
docker compose -f docker-compose.yml up --build -d 

# 기존 도커 이미지 삭제 
docker image prune -f

장점

  • Jenkins 서버에 Docker를 설치하지 않아도 됨
  • 배포 서버 기준으로 환경이 곧 실행 환경이라 가장 쉽다.

단점 및 고려사항

  • 배포 서버에 Git, Docker, Compose 가 모두 설치되어 있어야 함.
  • 그럼으로 CI/CD를 위한 배포 환경 관리가 분산되어 있어 관리가 어려움.
  • 빌드 작업이 배포 서버 리소스를 점유하므로, 부담이 있을 수 있음.

이 패턴은 “단일/소규모 서버 + 간단한 서비스” 환경에서 시작하기 좋습니다. 초기 세팅이 간단하고, 서버 한 대로 사이드 프로젝트 또는 PoC용 환경에 적합합니다.


패턴 2: Docker 바이너리(이미지) 빌드 패턴

해당 패턴은 Jenkins가 애플리케이션을 빌드하고 Docker 이미지를 생성한 뒤, 레지스트리(Docker Hub, ECR 등)에 저장(push) 후에 배포 서버에서는 이미지만 pull 해서 실행하는 방식입니다.

패턴 1방식에서 깃 저장소가 아닌 Docker 이미지를 저장하는 곳으로 변경했다고 이해하시면 됩니다.

Jenkins 역할

  • 소스 코드 체크아웃 후 애플리케이션 빌드(Gradle, Node 등)
  • Dockerfile 기반 이미지 빌드 및 레지스트리 push
stage("Build & Push Image"){
	steps{
		sh ""
			docker build -t my-registry/app:${BUILD_NUMBER} .
			docker push my-registry/app:${BUILD_NUMBER} 
		""
	}
}

배포 서버 역할

  • Jenkins 에서 SSH로 접속하거나, 별도 스크립트로 이미지를 가져온(pull) 후 컨테이너를 재시작하는 방법입니다.
# docker 이미지 가져오기 
docker pull my-registry/app:${BUILD_NUMBER}

# 이미지 실행 
docker compose up -d 

장점

  • Docker 이미지 라는 표준 아티팩트가 남아 롤백과 추적이 쉽다.
  • 빌드 환경을 Jenkins 쪽에서 통제하므로, 배포 서버는 런타임만 신경쓰면 된다.
  • Kubernetes 등 다른 오케스트레이션으로 확장하기 좋다.

단점

  • Jenkins에서 Docker를 사용할 수 있도록 Docker-in-Docker, docker socket 마운트 등 추가 구성이 필요
  • 레지스트리 권한 관리, 태그 전략, 이미지 정리 정책 등을 함께 설계해야 한다.
  • 이미지 정리 정책을 제대로 설정 안 할시 Jenkins 서버 용량 부족 문제가 발생한다.

패턴 3: Jenkins 자체 빌드 → SSH 전송 및 원격 실행

이 패턴은 Jenkins가 바이너리(JAR, dist, tar, 도커 이미지 등)를 직접 빌드한 후, SSH를 통해 배포 서버로 해당 내용을 전송하고, 이를 원격에서 스크립트를 실행하는 방식입니다.

젠킨스 서버가 별도로 있고, 배포 서버에서 아웃바운드가 모두 막혀있는 경우 배포 서버에서는 빌드 자체가 불가능하기 때문에 사용한 적이 있습니다.

여기서는 Spring Boot 를 JAR로 빌드하는 것이 아닌 Docker 이미지 빌드하며 .tar 파일을 저장하고 전송하는 것으로 예를 들겠습니다.

Jenkins 역할

  1. 코드 체크아웃 및 빌드(JAR, dist, rar, 도커 이미지 등을 생성)
  2. Publish Over SSH 또는 ssh/scp 로 빌드 결과물 전송
  3. 전송 완료 후 원격 서버에서 스크립트 실행
  4. 원격 서버에서 기존 컨테이너 종료 → 새 이미지 loaddocker compose 로 서비스 재기동

1단계: Spring Boot Jar 빌드(Build JAR)

첫 번째 스테이지에서는 로컬(젠킨스 서버)에서 Spring Boot JAR를 빌드합니다.

stage('Build JAR') {
  steps {
    script {
      dir("${env.LOCAL_SPRING_DIR}") {
        // Gradle Wrapper 실행 권한 부여
        sh "chmod +x gradlew"

        // 테스트는 제외하고 bootJar 빌드
        sh "./gradlew clean bootJar -x test"
      }
    }
  }
}

2단계: Docker 이미지 빌드 & Tar 저장(Build & Save Image)

두 번쨰 스테이지에서는 방금 빌드된 JAR를 기반으로 Docker 이미지를 만들고, 이를 .tar 파일로 저장합니다.

stage('Build & Save Image') {
  steps {
    script {
      // 2-1. Spring Boot 이미지 빌드 (Dual Tags: latest, git-tag)
      sh """
        docker build --no-cache \
          -t $IMG_SPRING \
          -t ${imgTagged} \
          -f $LOCAL_SPRING_DIR/$DOCKERFILE $LOCAL_SPRING_DIR
      """

      // 2-2. 도커 이미지를 tar 파일로 저장
      sh "docker save -o $TAR_SPRING $IMG_SPRING ${imgTagged}"
    }
  }
}

--no-cache 옵션은 캐시를 사용하지 않고 항상 깨끗한 빌드를 수행하는 옵션입니다. 이미지 레이어에서 변동된 코드 반영이 안 될 위험을 줄이는 대신, 빌드 시간이 조금 더 길어집니다.

3단계: Tar 파일 SSH 전송(Transfer to Remote)

세 번째 스테이지에서는 SSH 키를 이용해 원격 서버에 접속하고, docker save 로 만든 .tar 파일을 전송합니다.

stage('Transfer to Remote') {
  steps {
    withCredentials([sshUserPrivateKey(credentialsId: env.SSH_CRED_ID, keyFileVariable: 'SSH_KEY')]) {
      script {
        // 1. 원격 디렉토리 생성
        sh '''
        ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "mkdir -p $DEPLOY_ROOT"
        '''

        // 2. 대용량 Tar 파일 전송
        sh '''
        scp -i "$SSH_KEY" -o StrictHostKeyChecking=no "$TAR_SPRING" "$REMOTE_USER@$REMOTE_HOST:$DEPLOY_ROOT/"
        '''

        // 3. 전송 후 로컬 캐시 삭제
        sh '''
        rm "$TAR_SPRING"
        '''
      }
    }
  }
}
  • withCredentials([sshUserPrivateKey(...])
    • Jenkins 크리덴셜 스토어에 저장된 SSH 개인키를 빌드 시점에만 환경 변수(SSH_KEY)로 꺼내서 사용합니다.
  • mkdir -p $DEPLOY_ROOT
    • 배포 루트 디렉터리가 없더라도 에러 없이 생성되도록 p 옵션을 사용합니다.
  • scp 전송 후 rm
    • Jenkins 노드의 디스크 사용량을 줄이기 위해, 전송이 끝난 .tar 파일을 바로 삭제합니다. 장기 운영 시 불필요한 아티팩트가 쌓이지 않도록 하는 실무적인 처리입니다.

4단계: 원격 서버에서 Docker Compose 배포 (Remote Deploy)

마지막 스테이지에서는 다시 SSH로 원격 서버에 접속하여, 기존 컨테이너를 내리고 새 이미지를 load한 뒤 docker compose로 재기동합니다.

stage('Remote Deploy') {
  steps {
    withCredentials([sshUserPrivateKey(credentialsId: env.SSH_CRED_ID, keyFileVariable: 'SSH_KEY')]) {
      script {
        sh '''
        ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "
          cd $DEPLOY_ROOT

          # 1. 기존 컨테이너 종료
          docker compose -f $COMPOSE_FILE down

          # 2. 로그 디렉토리 생성 및 권한 설정
          mkdir -p logs/fastapi logs/spring
          chmod -R 755 logs 2>/dev/null || echo 'Log directory exists'

          # 3. 새로운 Spring Boot 이미지 로드
          docker load -i $TAR_SPRING

          # 4. 서비스 실행 (이미지 기반)
          docker compose -f $COMPOSE_FILE up -d --no-build

          # 5. tar 파일 삭제
          rm $TAR_SPRING

          # 6. 미사용 이미지 정리
          docker image prune -f
        "
        '''
      }
    }
  }
}
  • docker compose -f $COMPOSE_FILE down
    • 기존 컨테이너를 깨끗하게 내려 준 뒤 새 이미지를 올리므로, 포트 충돌이나 오래된 컨테이너가 남는 문제를 방지합니다.
  • 로그 디렉터리 선 생성
    • logs/fastapilogs/spring를 미리 만들고 권한을 맞춰줌으로써, 컨테이너 내부에서 호스트 볼륨을 마운트할 때 권한 문제를 최소화합니다.
  • docker load -i $TAR_SPRING
    • 앞서 전송한 .tar 파일에서 이미지를 로드합니다. 이 시점 이후에 docker compose는 새 이미지를 사용할 수 있습니다.
  • docker compose up -d --no-build
    • 원격 서버에 소스 코드는 없고 이미지만 존재하므로, -no-build 옵션으로 불필요한 빌드를 방지합니다.
  • 배포 이후 정리 작업
    • rm $TAR_SPRINGdocker image prune -f로 디스크를 정리하여, 장기적으로 서버 용량 이슈를 예방합니다.

장점

  • Docker를 쓰지 않는 레거시 환경에 적합합니다.
  • 보안 정책 등이 신경 쓰여 젠킨스 서버에서 모든 것을 할 때 유리합니다.
  • 어떤 형식의 아티팩트든 빌드 후 그대로 전송하여, 환경(OS 등)에 관계없이 실행할 수 있습니다.

단점

  • 젠킨스 서버에 JDK, Gradle, NodeJS 등 플러그인 버전 관리가 필요.
  • 아티팩트 버전 관리와 롤백 스크립트를 별도로 설계해야 합니다.
  • SSH 키/계정 관리, 네트워크 보안 규칙 등을 신경 써야 합니다.

세 패턴 비교 요약

항목git pull + compose (패턴 1)Docker 이미지(패턴 2)빌드 후 SSH 전송(패턴 3)
빌드 위치배포 서버Jenkins (또는 빌드 전용 노드)Jenkins
배포 아티팩트Git 워킹 디렉터리 + 컨테이너Docker 이미지JAR/ZIP/바이너리 파일
Docker 필요 위치배포 서버Jenkins + 배포 서버선택 (필수 아님)
롤백 난이도브랜치/태그 기반 수동 롤백태그된 이미지로 비교적 쉬운 롤백아티팩트 보관/스크립트에 따라 상이
초기 도입 난이도낮음중간(레지스트리, Docker 구성 필요)중간(SSH/키 관리 필요)
확장성(K8s 등)낮음높음중간
적합한 상황소규모 서버, 빠른 PoC멀티 서버/쿠버네티스, 표준화된 배포 필요레거시 서버, 단일 JAR/바이너리 배포 환경, 보안 정책
profile
개발 오소리

0개의 댓글