
도입 배경
예전에 Spring boot를 공부하며 만들었었던 암호화폐 모의투자 개인 프로젝트는 배포를 수동으로 진행 중이다. 프로젝트에 수정이 있을 때마다 빌드 & 배포 과정을 수동으로 진행하는 것에 시간이 지속적으로 투자된다. 이 과정을 자동화하여 빌드 & 배포에 소요되는 시간을 줄여서 효율적으로 개발할 수 있도록 개선해 보자.
기존 배포 방식
빌드부터 배포까지 직접 수동으로 진행. 프로젝트가 잦은 수정이 있다면 이 과정을 계속 반복해야 한다. 비효율적이라고 할 수 있다.
- 로컬 IntelliJ 에서 개발
- github 레포지토리에 push
- 오라클 클라우드(Ubuntu) ssh 접속
- 레포지토리 git clone 후 gradle 사용하여 직접 빌드
- 빌드 후 jar 파일 -> Dockerfile 기반으로 도커 이미지 생성
- 생성한 이미지로 Spring boot 컨테이너 실행
개선할 배포 방식
Jenkins도 고려했지만, CI 서버를 따로 구축해야 하는 점 때문에 Github Actions를 채택하였다. Github에서 CI서버를 제공해 주기 때문에 쉽게 도입이 가능하다.
- 로컬 IntelliJ 에서 개발
- github 레포지토리에 push
- (자동화) github actions 감지 -> 프로젝트 빌드 -> 도커 image build -> 도커 허브에 image push -> 오라클 클라우드(Ubuntu)에서 image pull -> Spring boot 컨테이너 실행(배포)
✔️ Github Actions Workflow 작성
아래는 워크플로우 전체 코드이다. Github Actions를 활용하여 CI(빌드)를 진행하고, Docker로 서버에 배포를 진행한다. Dockerfile은 이전 인프라 개선에서 추가해 뒀었다.
name: Java CI with Gradle & CD with Docker
on:
push:
branches: [ "main" ]
jobs:
# 1. Build
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# JDK setting - github actions에서 사용할 JDK 설정
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# yml 파일 생성 & SSL 인증서 복사
- name: make application.yml
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION_YML }}" > ./application.yml
touch ./keystore.p12
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.p12
shell: bash
# gradle 세팅
- name: Setup Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# 프로젝트 빌드
- name: Build with Gradle
run: ./gradlew build -x test # 테스트 코드 제외
# Docker image build & Push
- name: Docker build & push to Docker hub
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }} .
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}
# 2. Deploy Job (의존성: Dockerize Job이 끝난 후 실행)
deploy:
runs-on: ubuntu-latest
needs: build # dockerize job이 완료된 후 실행
steps:
# docker image pull & deploy
- name: Docker image pull & Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.INSTANCE_HOST }} # 인스턴스 퍼블릭IP
username: ${{ secrets.INSTANCE_USERNAME }}
password: ${{ secrets.INSTANCE_PASSWORD }}
# 도커 이미지 pull & deploy 작업
script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker rm -f $(docker ps -q --filter "name=spring-app")
docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}:latest
docker run -d --name spring-app --network bitrun -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}:latest
docker image prune -f
application.yml / SSL 인증서 복사
주요한 코드를 살펴보자. 배포 application.yml 파일을 github secrets 변수에 저장해 두었다. 빌드 전에 yml 파일 내용을 복사해 온다. 그리고 무료 SSL인증서를 Certbot(https://certbot.eff.org/) 프로그램으로 발급받아 사용 중이다. 인증서 파일을 base64로 인코딩하여 secrets 변수에 저장해 두었고, base64로 디코딩하여 프로젝트에 포함시켜 빌드한다.
# yml 파일 생성 & SSL 인증서 복사
- name: make application.yml
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION_YML }}" > ./application.yml
touch ./keystore.p12
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.p12
shell: bash
Dockerfile 기반으로 이미지 생성 및 Docker Hub에 push
프로젝트에 포함되어 있는 Dockerfile을 기반으로 이미지를 만들고 도커 허브에 이미지를 push 한다. 도커 계정명, 비밀번호, 레포지토리명은 secrets 변수에 저장해 두었다.
# Docker image build & Push
- name: Docker build & push to Docker hub
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }} .
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}
배포(deploy)
현재 오라클 클라우드 프리티어를 사용하여 배포하고 있다. 클라우드 서버에 ssh 접속을 하여 docker hub에 push 했던 이미지를 가져와 Spring boot 컨테이너를 실행시킨다. 기존에 실행 중인 컨테이너는 docker rm -f $(docker ps -q --filter "name=spring-app")를 사용하여 컨테이너를 제거한다.
# docker image pull & deploy
- name: Docker image pull & Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.INSTANCE_HOST }} # 인스턴스 퍼블릭IP
username: ${{ secrets.INSTANCE_USERNAME }}
password: ${{ secrets.INSTANCE_PASSWORD }}
# 도커 이미지 pull & deploy 작업
script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker rm -f $(docker ps -q --filter "name=spring-app")
docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}:latest
docker run -d --name spring-app --network bitrun -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}:latest
docker image prune -f
✔️ 빌드 및 배포 확인
프로젝트 github에서 Actions 탭에 들어가면 작업이 잘 완료되었는지 확인할 수 있다.

Docker Hub 이미지 업로드 확인
docker hub에 이미지가 바뀌었는지 확인한다.

서버 ssh 접속 후 Spring boot 컨테이너 실행 확인
ssh 접속하여 docker ps 명령어로 서버에서 Spring boot 서버가 잘 떴는지도 확인해 보자. 최신 시간으로 새롭게 컨테이너가 실행된 모습이다.

사이트 접속
배포 사이트 https://coinrun.kr/ 에 잘 접속이 되는지까지 확인해보자.

✔️ 빌드/배포 자동화 도입 후 최종 아키텍처
빌드/배포 자동화 도입 후 아키텍처 모습을 그려보았다. 아래 순서는 최종적으로 적용되는 순서이다.
(수동) IntelliJ 개발 -> Github 레포지토리에 push -> (자동화) Github Actions 감지 -> gradle build -> Dockerfile 기반으로 도커 이미지 생성 -> docker hub에 push -> Oracle Cloud ssh로 docker hub에서 이미지 pull -> Spring boot 컨테이너 실행
결과적으로 Github Actions 도입 후, 프로젝트 수정이 있을 때마다 빌드 및 배포가 자동으로 진행된다. 추후에 수정이 자주 일어나더라도 시간적으로 효율적인 개발이 기대된다.

📌 추가 개선해야 할 일
지금 구축된 빌드/배포 자동화는 배포되는 동안 Spring boot 서버가 실행되지 않는다. 배포되는 동안 사용자들의 요청을 받을 수 없다는 얘기이다. 서버가 중단되지 않는 무중단 빌드/배포 자동화를 도입해야 한다.
- 프로젝트 Github
GitHub - yoonion/cryptocurrency-mock-investment-Spring-Boot: 📈 업비트 실시간 데이터 기반 암호화폐 모의투자
📈 업비트 실시간 데이터 기반 암호화폐 모의투자 사이트. Contribute to yoonion/cryptocurrency-mock-investment-Spring-Boot development by creating an account on GitHub.
github.com
- 프로젝트 배포
Main Page
암호화폐 모의투자, 무료로 시작하세요. 본 거래소는 업비트(https://www.upbit.com)의 실시간 데이터를 기반으로 운영됩니다. 상업적인 목적이 없는 개인이 운영하는 거래소입니다. 거래소 둘러보기
coinrun.kr

도입 배경
예전에 Spring boot를 공부하며 만들었었던 암호화폐 모의투자 개인 프로젝트는 배포를 수동으로 진행 중이다. 프로젝트에 수정이 있을 때마다 빌드 & 배포 과정을 수동으로 진행하는 것에 시간이 지속적으로 투자된다. 이 과정을 자동화하여 빌드 & 배포에 소요되는 시간을 줄여서 효율적으로 개발할 수 있도록 개선해 보자.
기존 배포 방식
빌드부터 배포까지 직접 수동으로 진행. 프로젝트가 잦은 수정이 있다면 이 과정을 계속 반복해야 한다. 비효율적이라고 할 수 있다.
- 로컬 IntelliJ 에서 개발
- github 레포지토리에 push
- 오라클 클라우드(Ubuntu) ssh 접속
- 레포지토리 git clone 후 gradle 사용하여 직접 빌드
- 빌드 후 jar 파일 -> Dockerfile 기반으로 도커 이미지 생성
- 생성한 이미지로 Spring boot 컨테이너 실행
개선할 배포 방식
Jenkins도 고려했지만, CI 서버를 따로 구축해야 하는 점 때문에 Github Actions를 채택하였다. Github에서 CI서버를 제공해 주기 때문에 쉽게 도입이 가능하다.
- 로컬 IntelliJ 에서 개발
- github 레포지토리에 push
- (자동화) github actions 감지 -> 프로젝트 빌드 -> 도커 image build -> 도커 허브에 image push -> 오라클 클라우드(Ubuntu)에서 image pull -> Spring boot 컨테이너 실행(배포)
✔️ Github Actions Workflow 작성
아래는 워크플로우 전체 코드이다. Github Actions를 활용하여 CI(빌드)를 진행하고, Docker로 서버에 배포를 진행한다. Dockerfile은 이전 인프라 개선에서 추가해 뒀었다.
name: Java CI with Gradle & CD with Docker
on:
push:
branches: [ "main" ]
jobs:
# 1. Build
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# JDK setting - github actions에서 사용할 JDK 설정
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# yml 파일 생성 & SSL 인증서 복사
- name: make application.yml
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION_YML }}" > ./application.yml
touch ./keystore.p12
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.p12
shell: bash
# gradle 세팅
- name: Setup Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# 프로젝트 빌드
- name: Build with Gradle
run: ./gradlew build -x test # 테스트 코드 제외
# Docker image build & Push
- name: Docker build & push to Docker hub
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }} .
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}
# 2. Deploy Job (의존성: Dockerize Job이 끝난 후 실행)
deploy:
runs-on: ubuntu-latest
needs: build # dockerize job이 완료된 후 실행
steps:
# docker image pull & deploy
- name: Docker image pull & Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.INSTANCE_HOST }} # 인스턴스 퍼블릭IP
username: ${{ secrets.INSTANCE_USERNAME }}
password: ${{ secrets.INSTANCE_PASSWORD }}
# 도커 이미지 pull & deploy 작업
script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker rm -f $(docker ps -q --filter "name=spring-app")
docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}:latest
docker run -d --name spring-app --network bitrun -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}:latest
docker image prune -f
application.yml / SSL 인증서 복사
주요한 코드를 살펴보자. 배포 application.yml 파일을 github secrets 변수에 저장해 두었다. 빌드 전에 yml 파일 내용을 복사해 온다. 그리고 무료 SSL인증서를 Certbot(https://certbot.eff.org/) 프로그램으로 발급받아 사용 중이다. 인증서 파일을 base64로 인코딩하여 secrets 변수에 저장해 두었고, base64로 디코딩하여 프로젝트에 포함시켜 빌드한다.
# yml 파일 생성 & SSL 인증서 복사
- name: make application.yml
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION_YML }}" > ./application.yml
touch ./keystore.p12
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.p12
shell: bash
Dockerfile 기반으로 이미지 생성 및 Docker Hub에 push
프로젝트에 포함되어 있는 Dockerfile을 기반으로 이미지를 만들고 도커 허브에 이미지를 push 한다. 도커 계정명, 비밀번호, 레포지토리명은 secrets 변수에 저장해 두었다.
# Docker image build & Push
- name: Docker build & push to Docker hub
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }} .
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}
배포(deploy)
현재 오라클 클라우드 프리티어를 사용하여 배포하고 있다. 클라우드 서버에 ssh 접속을 하여 docker hub에 push 했던 이미지를 가져와 Spring boot 컨테이너를 실행시킨다. 기존에 실행 중인 컨테이너는 docker rm -f $(docker ps -q --filter "name=spring-app")를 사용하여 컨테이너를 제거한다.
# docker image pull & deploy
- name: Docker image pull & Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.INSTANCE_HOST }} # 인스턴스 퍼블릭IP
username: ${{ secrets.INSTANCE_USERNAME }}
password: ${{ secrets.INSTANCE_PASSWORD }}
# 도커 이미지 pull & deploy 작업
script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker rm -f $(docker ps -q --filter "name=spring-app")
docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}:latest
docker run -d --name spring-app --network bitrun -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO_NAME }}:latest
docker image prune -f
✔️ 빌드 및 배포 확인
프로젝트 github에서 Actions 탭에 들어가면 작업이 잘 완료되었는지 확인할 수 있다.

Docker Hub 이미지 업로드 확인
docker hub에 이미지가 바뀌었는지 확인한다.

서버 ssh 접속 후 Spring boot 컨테이너 실행 확인
ssh 접속하여 docker ps 명령어로 서버에서 Spring boot 서버가 잘 떴는지도 확인해 보자. 최신 시간으로 새롭게 컨테이너가 실행된 모습이다.

사이트 접속
배포 사이트 https://coinrun.kr/ 에 잘 접속이 되는지까지 확인해보자.

✔️ 빌드/배포 자동화 도입 후 최종 아키텍처
빌드/배포 자동화 도입 후 아키텍처 모습을 그려보았다. 아래 순서는 최종적으로 적용되는 순서이다.
(수동) IntelliJ 개발 -> Github 레포지토리에 push -> (자동화) Github Actions 감지 -> gradle build -> Dockerfile 기반으로 도커 이미지 생성 -> docker hub에 push -> Oracle Cloud ssh로 docker hub에서 이미지 pull -> Spring boot 컨테이너 실행
결과적으로 Github Actions 도입 후, 프로젝트 수정이 있을 때마다 빌드 및 배포가 자동으로 진행된다. 추후에 수정이 자주 일어나더라도 시간적으로 효율적인 개발이 기대된다.

📌 추가 개선해야 할 일
지금 구축된 빌드/배포 자동화는 배포되는 동안 Spring boot 서버가 실행되지 않는다. 배포되는 동안 사용자들의 요청을 받을 수 없다는 얘기이다. 서버가 중단되지 않는 무중단 빌드/배포 자동화를 도입해야 한다.
- 프로젝트 Github
GitHub - yoonion/cryptocurrency-mock-investment-Spring-Boot: 📈 업비트 실시간 데이터 기반 암호화폐 모의투자
📈 업비트 실시간 데이터 기반 암호화폐 모의투자 사이트. Contribute to yoonion/cryptocurrency-mock-investment-Spring-Boot development by creating an account on GitHub.
github.com
- 프로젝트 배포
Main Page
암호화폐 모의투자, 무료로 시작하세요. 본 거래소는 업비트(https://www.upbit.com)의 실시간 데이터를 기반으로 운영됩니다. 상업적인 목적이 없는 개인이 운영하는 거래소입니다. 거래소 둘러보기
coinrun.kr