이 실습에서는 Jenkins를 이용해 GitHub 저장소의 코드 변경을 감지하고, 자동으로 소스코드를 가져와 Docker 이미지를 빌드한 뒤 컨테이너 레지스트리에 업로드하는 기본 CI 파이프라인을 구성한다.
또한 Jenkins가 단순 빌드 도구가 아니라, 코드 변경 → 자동 실행 → 이미지 생성 → 결과물 배포 준비까지 연결하는 자동화 서버라는 점을 실습으로 확인한다.
이 실습을 끝내면 다음이 가능해진다.
이번 실습은 앞에서 나눠서 보았던 Jenkins 관련 세부 실습을 하나의 흐름으로 묶어서 수행하는 통합 실습이다.
실습 전체 흐름은 다음과 같다.
Jenkins 설치
→ 초기 관리자 설정
→ GitHub 저장소 준비
→ Jenkins Pipeline Job 생성
→ GitHub Webhook 등록
→ 코드 push 시 자동 실행
→ Docker 이미지 빌드
→ 컨테이너 레지스트리 Push
→ 결과 확인
즉, 이번 실습은 Jenkins를 "설치만 해보는 것"이 아니라
실제로 CI 파이프라인 역할을 수행하게 만드는 것이 목적이다.
개발자
↓ git push
GitHub Repository
↓ Webhook
Jenkins Server
├─ Pipeline Job 실행
├─ Git checkout
├─ Docker 이미지 빌드
└─ Registry Push
↓
Container Registry
이 구조에서 각 구성요소 역할은 다음과 같다.
소스코드 저장소 역할을 하며, 코드 변경 이벤트를 Jenkins에 전달한다.
CI 파이프라인 실행 서버 역할을 하며, 코드를 받아 이미지 빌드와 업로드를 수행한다.
Jenkins가 생성한 이미지를 저장하는 중앙 저장소 역할을 한다.
실습 전에 다음을 준비한다.
이번 실습에서는 Docker 이미지 빌드를 위해 간단한 정적 웹 예제를 사용한다.
저장소 예시 이름:
jenkins-ci-lab
저장소 구조는 다음처럼 구성한다.
jenkins-ci-lab/
├─ Dockerfile
├─ index.html
└─ README.md
index.html<!DOCTYPE html>
<html>
<head>
<metacharset="UTF-8">
<title>Jenkins CI Lab</title>
</head>
<body>
<h1>Jenkins CI Pipeline Success</h1>
<p>This page is served from an image built by Jenkins.</p>
</body>
</html>
DockerfileFROM nginx:alpine
RUN rm /usr/share/nginx/html/index.html
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
FROM nginx:latestNginx 공식 이미지를 기반 이미지로 사용한다.
COPY index.html ...현재 저장소의 index.html 파일을 Nginx 웹 루트 위치에 복사한다.
즉, 이 이미지는 Nginx + 사용자 정의 정적 페이지 구조를 가진다.
먼저 Ubuntu 서버에 접속한다.
sudo apt update
sudo apt upgrade -y
apt update는 설치 가능한 패키지 목록을 최신화한다.apt upgrade -y는 현재 설치된 패키지를 최신 버전으로 갱신한다.sudo apt install -y openjdk-21-jdk
설치 확인:
java-version
Jenkins는 Java 기반 애플리케이션이다.
즉, Java 런타임이 없으면 Jenkins 자체가 동작할 수 없다.
sudo wget -O /etc/apt/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2026.key
Jenkins 공식 저장소의 서명 키를 시스템에 등록한다.
이 키는 이후 Jenkins 패키지를 신뢰 가능한 출처에서 받았는지 검증하는 데 사용된다.
echo "deb [signed-by=/etc/apt/keyrings/jenkins-keyring.asc]" \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
다시 패키지 목록을 갱신한다.
sudo apt update
sudo apt install -y jenkins
설치 후 서비스 상태 확인:
sudo systemctl status jenkins
Active: active (running) 이 보이면 정상 실행 상태다.sudo ufw status
sudo ufw allow 8080/tcp
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/rpm-stable/jenkins.repo
sudo dnf upgrade
# Add required dependencies for the jenkins package
sudo dnf install java-21-amazon-corretto-devel -y
sudo dnf install jenkins
sudo systemctl daemon-reload
Jenkins 기본 웹 포트는 8080이다.
외부 브라우저에서 접속하려면 서버 방화벽과 클라우드 보안그룹 모두 8080 허용이 필요하다.
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
이 값을 복사해둔다.
브라우저에서 접속:
http://서버IP:8080
또는
http://도메인:8080
초기 설정 절차:





Jenkins 서버에서 Git과 Docker가 필요하다.
sudo apt install -y git | sudo dnf install -y git
git --version
Jenkins가 GitHub 저장소를 checkout 하려면 Git CLI가 필요하다.
# Add Docker's official GPG key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo dnf install -y docker
서비스 상태 확인:
sudo systemctl status docker
버전 확인:
sudo docker version
Jenkins Job 안에서 Docker build를 하려면 jenkins 사용자에게 Docker 접근 권한이 필요하다.
sudo usermod -aG docker ec2-user
sudo usermod -aG docker jenkins
usermod -aG docker jenkins 는 jenkins 사용자를 docker 그룹에 추가한다.sudo systemctl restart jenkins
필요 시 Docker도 재시작한다.
sudo systemctl restart docker
그룹 반영 확인:
id jenkins
출력 결과에 docker 그룹이 포함되어 있어야 한다.

예시 사용자명:
mydockerid
실습용 비밀번호 대신 Docker Hub Access Token 사용을 권장한다.
Jenkins에 레지스트리 인증정보를 안전하게 저장한다.
환경에 따라 System → Global credentials 구조가 보일 수 있다.
다음 값 입력:
Username with passworddockerhub-credsDocker Hub registry credentials저장한다.
이 Credential은 이후 Pipeline에서 참조한다.
절대로 Jenkinsfile이나 Shell 스크립트 안에 비밀번호를 직접 적지 않는다.
jenkins-ci-pipeline
이번 실습 전체 CI 흐름
이런 흐름은 Freestyle보다 Pipeline으로 관리하는 편이 훨씬 구조적이다.
즉, Stage 단위로 나눠서 실행 흐름을 보기 쉽게 만들 수 있다.
이번 실습에서는 Jenkins UI 안에 직접 Script를 넣는 방식으로 시작한다.
실무에서는 Git 저장소 안 Jenkinsfile로 분리하는 것이 일반적이지만, 지금은 흐름 이해가 우선이다.
Pipeline 섹션에서 Pipeline script 선택 후 아래 코드를 입력한다.
pipeline {
agent any
environment {
// Docker Hub 계정 ID와 Credential ID를 환경변수로 빼두면 관리가 편합니다.
DOCKERHUB_USER = '사용자명' // 실제 본인의 ID로 수정하세요
DOCKERHUB_CREDS = 'dockerhub-creds'
IMAGE_NAME = 'sample-docker-app'
IMAGE_TAG = 'latest'
// 최종 이미지 전체 경로 (변수 조합)
REGISTRY_IMAGE = "${DOCKERHUB_USER}/${IMAGE_NAME}:${IMAGE_TAG}"
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/사용자명/jenkins-ci-lab.git'
}
}
stage('Build & Tag Image') {
steps {
script {
// docker DSL을 사용하여 객체지향적으로 빌드
def myApp = docker.build("${REGISTRY_IMAGE}")
// (선택 사항) 빌드된 이미지의 ID 등을 확인하고 싶을 때
sh "docker images | grep ${IMAGE_NAME}"
}
}
}
stage('Push Image') {
steps {
script {
// 강사님이 말씀하신 세련된 방식!
// 로그인-푸시-로그아웃을 한 번에 처리합니다.
docker.withRegistry('', DOCKERHUB_CREDS) {
sh "docker push ${REGISTRY_IMAGE}"
// 만약 빌드 번호를 태그로 추가하고 싶다면?
// sh "docker tag ${REGISTRY_IMAGE} ${DOCKERHUB_USER}/${IMAGE_NAME}:${env.BUILD_NUMBER}"
// sh "docker push ${DOCKERHUB_USER}/${IMAGE_NAME}:${env.BUILD_NUMBER}"
}
}
}
}
stage('Verify') {
steps {
echo "Successfully pushed to: ${REGISTRY_IMAGE}"
// 빌드 후에 로컬 이미지를 정리하고 싶다면 아래 주석 해제
// sh "docker rmi ${REGISTRY_IMAGE}"
}
}
}
post {
success {
echo "✅ Jenkins CI Pipeline 실행 성공: ${REGISTRY_IMAGE}"
}
failure {
echo '❌ Jenkins CI Pipeline 실행 실패'
}
always {
echo '🏁 Pipeline 종료'
}
}
}
이 코드는 길어 보이지만, 흐름은 매우 단순하다.
agent any이 Pipeline을 실행 가능한 Jenkins 환경에서 돌리겠다는 뜻이다.
현재 실습 구조에서는 Jenkins Controller 서버에서 직접 실행된다고 이해하면 된다.
environmentenvironment {
IMAGE_NAME = 'sample-docker-app'
IMAGE_TAG = 'latest'
REGISTRY_IMAGE = ''
}
Pipeline 안에서 반복해서 사용할 값을 변수처럼 정의한다.
IMAGE_NAME: 로컬 이미지 이름IMAGE_TAG: 태그REGISTRY_IMAGE: 최종 레지스트리용 이미지 이름Checkout Stagegit branch: 'main', url: 'https://github.com/사용자명/jenkins-ci-lab.git'
GitHub 저장소의 main 브랜치를 Jenkins Workspace로 가져온다.
즉, 이 Stage가 성공해야 다음 Build 단계에서 Dockerfile과 index.html 파일을 사용할 수 있다.
Prepare Stage이 Stage에서는 다음을 확인한다.
특히 docker version은 매우 중요하다.
여기서 실패하면 권한 또는 Docker 서비스 문제일 가능성이 높다.
Build Image Stagedocker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${REGISTRY_IMAGE}
즉, 로컬 빌드 이미지와 레지스트리용 이미지 이름은 구분해서 이해하는 것이 좋다.
Push Image Stage이 Stage는 가장 중요하다.
withCredentials([usernamePassword(...)]) {
sh '''
echo "${DOCKERHUB_PASS}" | docker login -u "${DOCKERHUB_USER}" --password-stdin
docker push ${DOCKERHUB_USER}/${IMAGE_NAME}:${IMAGE_TAG}
'''
}
withCredentialsJenkins에 저장해둔 Credential을 실행 시점에만 환경변수로 주입한다.
docker login --password-stdin비밀번호를 명령행에 직접 쓰지 않고 안전하게 전달한다.
docker push최종 이미지를 Docker Hub에 업로드한다.
즉, 이 Stage가 성공해야 비로소 다른 환경에서도 사용할 수 있는 배포 가능한 이미지가 된다.
Verify Stage이 Stage에서는 빌드 결과를 다시 확인한다.
즉, 빌드 결과를 다시 한 번 명시적으로 보여주는 검증 단계다.
post이 블록은 전체 Pipeline 종료 후 실행된다.
success: 전체 성공 시 메시지 출력failure: 전체 실패 시 메시지 출력always: 성공/실패와 관계없이 항상 실행이 구조는 나중에 Slack 알림, 메일, 리포트 업로드 등으로 확장 가능하다.
먼저 Webhook 없이 수동으로 한 번 실행해본다.
Stage가 순서대로 실행된다.
Checkout
→ Prepare
→ Build Image
→ Push Image
→ Verify
성공 시 로그에서 다음을 확인한다.
load build definition from DockerfileCOPY index.html ...naming to ... sample-docker-app:latestLogin SucceededThe push refers to repository ...digest: sha256: ...최종 성공 상태 확인
Docker Hub 웹사이트에서 다음을 확인한다.
sample-docker-app 저장 여부latest 태그 존재 여부이 단계는 매우 중요하다.
Jenkins 콘솔 로그만으로 끝내지 않고, 실제 레지스트리에 결과물이 올라갔는지 외부 시점에서 확인해야 한다.
이제 코드 push 시 Jenkins가 자동 실행되도록 연결한다.
Job 설정으로 들어간다.
Build Triggers 섹션에서 아래 항목 체크:
GitHub hook trigger for GITScm polling
저장한다.
GitHub 저장소로 이동:
입력값:
http://JENKINS주소/github-webhook/
예:
http://203.0.113.10:8080/github-webhook/
application/json
Just the push event
체크 상태 유지
저장한다.

이제 로컬 저장소에서 파일을 하나 수정하고 push 한다.
예:
echo"<p>Webhook triggered build</p>" >> index.html
git add index.html
git commit-m"test: trigger jenkins webhook build"
git push origin main
Jenkins Build History에서 새 Build를 클릭한다.
즉, 지금부터 Jenkins는 사람이 버튼을 누르는 시스템이 아니라
코드 변경에 반응하는 이벤트 기반 CI 서버가 된다.
이번 실습이 정상적으로 끝났다면 다음이 모두 충족되어야 한다.
sudo systemctl status jenkins
sudo ss-tulnp |grep8080
sudo ufw status
예:
permission denied while trying to connect to the Docker daemon socket
jenkins 사용자가 docker 그룹에 없음sudo usermod-aG docker jenkins
sudo systemctlrestart jenkins
id jenkins
예:
unauthorized: incorrect username or password
credentialsId: 'dockerhub-creds' 오타 확인/github-webhook/ 경로 누락즉, 직접 설치하고 운영해야 한다.
이번 실습에서는 checkout, build, push까지 연결했다.
운영에 반영 가능한 표준 배포 단위 역할을 한다.
즉, 빌드 성공과 배포 준비 완료는 다르다.
인증정보를 Job 스크립트에 하드코딩하면 안 된다.
즉, 사람이 수동으로 빌드 버튼을 누르지 않아도 된다.
이번 실습을 통해 다음을 완료했음.