| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- SQL Mapper
- 티스토리챌린지
- 혼공얄코
- BFS
- 프로그래머스 2개 이하로 다른 비트
- AWS배포
- 스프링부트 배포
- systemd
- hackerrank
- 프로그래머스
- java
- 분할정복 알고리즘
- spring security 설정
- 둘만의 암호
- Ubuntu서버
- 알고리즘
- 오블완
- DFS
- 자바의 정석
- LocalDate
- 리눅스
- 둘만의 암호 자바
- 백트래킹
- 프로그래머스 둘만의 암호
- 분할정복
- Comparable
- EC2배포
- spring security
- 서버초기설정
- 자바의정석
- Today
- Total
쉽게 쉽게
[AWS] AWS에 프로젝트 배포하기 (3) - 배포 간략화 본문
- 수동 배포 5단계 → 명령어 1줄로 축소 — 빌드 후
bash ~/deploy.sh한 번으로 전송·교체·재시작·헬스체크까지 자동 처리 - 빌드는 IntelliJ, 배포는 WSL에서 분리 실행 — WSL에 Java가 없어도 문제없고, 컴파일 오류를 IDE에서 바로 확인 가능
- 자동 백업으로 롤백 대비 — 배포할 때마다 이전 JAR를
app-prev.jar로 보관해 문제 발생 시 즉시 복구 가능
1 자동화 개요
이전 배포과정을 통해 Spring Boot 프로젝트를 AWS EC2에 배포하는 데 성공했을 겁니다.
다만 배포 및 재기동은 단 한 번만 필요한 것이 아니라 주기적으로 발생할 수 있는 일입니다.
때문에 복잡한 수동 배포 과정을 자동화하여 향후 프로젝트 관리에 도움이 될 수 있도록 하려고 합니다.
2 왜 자동화가 필요했을까?
자동화 스크립트를 만들기 전, 코드를 수정할 때마다 아래 5단계를 손으로 직접 입력해야 했습니다.
자동화 전 — 수동 배포 5단계
./gradlew clean bootJar
# 2. 서버로 파일 전송
scp -i "~/.ssh/your-key.pem" "/mnt/c/.../your-app.jar" ubuntu@YOUR_IP:~/app.jar
# 3. 서버 접속
ssh -i "~/.ssh/your-key.pem" ubuntu@YOUR_IP
# 4. 파일 교체 및 권한 설정
sudo mv ~/app.jar /opt/your-app/app.jar
sudo chown appuser:appuser /opt/your-app/app.jar
# 5. 서비스 재시작
sudo systemctl restart your-app
3 전체 스크립트 한눈에 보기
먼저 완성된 스크립트 전체를 보여드립니다.
대문자로 표시된 설정 변수 블록만 본인 환경에 맞게 수정하면 바로 사용할 수 있습니다.
각 단계가 무엇을 하는지는 바로 아래 표와 이후 섹션에서 자세히 설명합니다.
#!/usr/bin/env bash
set -euo pipefail
# ── 설정 변수 (이 블록만 수정하세요) ──────────────
SERVER="YOUR_SERVER_IP"
SSH_USER="ubuntu"
PEM="$HOME/.ssh/your-key.pem"
APP_DIR="/opt/your-app"
SERVICE_NAME="your-app"
APP_USER="appuser"
JAR_PATH="/mnt/c/deploy/app.jar"
# ──────────────────────────────────────────────────
# ── 색상 출력 헬퍼 ────────────────────────────────
info() { echo -e "\033[34m[INFO]\033[0m $*"; }
ok() { echo -e "\033[32m[OK]\033[0m $*"; }
error() { echo -e "\033[31m[ERROR]\033[0m $*"; exit 1; }
# STEP 1. JAR 파일 존재 확인
info "STEP 1 — 빌드 파일 확인"
if [[ ! -f "$JAR_PATH" ]]; then
error "JAR 파일이 없습니다. IntelliJ에서 './gradlew clean bootJar' 먼저 실행하세요."
fi
ok "JAR 확인 완료"
# STEP 2. 서버로 전송
info "STEP 2 — 서버로 JAR 전송"
scp -i "$PEM" "$JAR_PATH" "${SSH_USER}@${SERVER}:~/app.jar"
ok "전송 완료"
# STEP 3. 백업 · 교체 · 권한 · 재시작
info "STEP 3 — 서비스 교체 및 재시작"
ssh -i "$PEM" "${SSH_USER}@${SERVER}" bash << EOF
set -e
if [[ -f "${APP_DIR}/app.jar" ]]; then
sudo mv "${APP_DIR}/app.jar" "${APP_DIR}/app-prev.jar"
fi
sudo mv ~/app.jar "${APP_DIR}/app.jar"
sudo chown ${APP_USER}:${APP_USER} "${APP_DIR}/app.jar"
sudo systemctl restart ${SERVICE_NAME}
EOF
ok "재시작 요청 완료"
# STEP 4. 헬스 체크 (5초 간격, 최대 60초)
info "STEP 4 — 기동 대기 및 헬스 체크 (최대 60초)"
for i in {1..12}; do
sleep 5
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://${SERVER}/" || echo "000")
if [[ "$HTTP_STATUS" == "200" ]]; then
ok "배포 성공! (HTTP ${HTTP_STATUS})"
exit 0
fi
echo " 기동 대기 중... (${i}/12, 현재 HTTP ${HTTP_STATUS})"
done
error "60초 내 기동 실패 — 로그 확인: sudo journalctl -u ${SERVICE_NAME} -n 50"
4 스크립트가 하는 일 한눈에 보기
변수를 설정하고 나면 스크립트가 아래 4단계를 순서대로 자동 실행합니다.
| 단계 | 하는 일 | 왜 필요한가 |
|---|---|---|
| STEP 1 | 빌드 파일 확인 | 빌드를 깜빡하고 스크립트를 실행하는 실수를 방지합니다. JAR 파일이 없으면 즉시 중단해 엉뚱한 배포를 막습니다. |
| STEP 2 | 서버로 JAR 전송 | pem 키 인증으로 로컬의 빌드 결과물을 서버 홈 디렉토리로 안전하게 복사합니다. 앱 디렉토리에 직접 쓸 수 없어 홈을 경유합니다. |
| STEP 3 | 파일 교체 · 권한 설정 · 서비스 재시작 | 기존 JAR를 app-prev.jar로 백업한 뒤 새 파일로 교체합니다. 소유자를 앱 전용 계정으로 맞추고 systemd 서비스를 재시작합니다. |
| STEP 4 | 헬스 체크 | Spring Boot 기동에 시간이 걸리므로 5초 간격으로 최대 60초간 HTTP 200 응답을 확인합니다. 실패 시 로그 확인 명령을 안내합니다. |
5 빌드와 배포, 왜 분리했을까?
자동화 스크립트를 만들 때 "빌드(소스코드 → JAR 파일 변환)도 스크립트 안에서 처리할까?"라고 생각할 수 있습니다.
하지만 빌드는 IntelliJ에서, 배포는 WSL 스크립트로 분리하는 것이 좋습니다.
scp, ssh 같은 Linux 명령어를 Windows에서 실행할 수 있습니다.| 구분 | WSL에서 빌드하면? | IntelliJ에서 빌드하면? |
|---|---|---|
| Java 설치 | 별도 설치 필요 | JDK 내장 |
| 오류 확인 | 터미널 텍스트만 | IDE 강조 표시 |
| 빌드 속도 | 환경 설정 필요 | 증분 빌드 지원 |
6 각 단계 자세히 살펴보기
사전 준비 — JAR 출력 경로 고정하기
스크립트의 JAR_PATH에는 빌드 결과물인 JAR 파일의 경로를 지정해야 합니다.
기본 설정 그대로 빌드하면 경로가 아래처럼 길고, 버전 번호까지 포함되어 있습니다.
JAR_PATH="/mnt/c/Users/YourName/your-project/build/libs/your-app-0.0.1-SNAPSHOT.jar"
# 문제 1: 경로가 너무 길다
# 문제 2: 버전이 바뀌면 (0.0.1 → 0.0.2) 스크립트도 매번 수정해야 한다
프로젝트 build.gradle의 bootJar 블록에 출력 경로와 파일명을 고정하면 이 문제를 한 번에 해결할 수 있습니다.
bootJar {
destinationDirectory = file('C:/deploy') // JAR가 저장될 폴더
archiveFileName = 'app.jar' // 버전 번호 없이 고정 파일명
}
// bootJar: ./gradlew clean bootJar 실행 시 동작하는 태스크 설정 블록
// destinationDirectory: JAR 파일이 저장될 경로 (C:/deploy 폴더가 자동 생성됨)
// archiveFileName: 생성되는 파일 이름 고정 (기본값은 앱이름-버전.jar)
이제 JAR_PATH를 아래처럼 짧게 쓸 수 있습니다.
JAR_PATH="/mnt/c/deploy/app.jar"
# C:/deploy 폴더를 WSL 경로로 표현한 것 (/mnt/c/deploy)
# 버전이 바뀌어도 이 경로는 수정할 필요가 없습니다
destinationDirectory에 지정한 폴더가 없어도 ./gradlew clean bootJar 실행 시 Gradle이 자동으로 생성합니다. 별도로 폴더를 만들 필요가 없습니다.
설정 변수 블록 — 여기만 수정하면 됩니다
스크립트 맨 위의 변수 블록에 값을 한 번만 채워두면, 이후에 서버 IP나 경로가 바뀌어도 이 블록만 수정하면 됩니다.
SERVER="YOUR_SERVER_IP" # EC2 퍼블릭 IP (또는 Elastic IP)
SSH_USER="ubuntu" # SSH 접속 계정 (AWS EC2 기본값: ubuntu)
PEM="$HOME/.ssh/your-key.pem"
# pem: AWS에서 발급한 SSH 인증 키 파일
APP_DIR="/opt/your-app" # 서버에서 앱이 저장된 디렉토리
SERVICE_NAME="your-app" # systemd에 등록한 서비스 이름
APP_USER="appuser" # 서버에서 앱을 실행하는 전용 계정
JAR_PATH="/mnt/c/deploy/app.jar"
# build.gradle bootJar 블록에서 출력 경로를 고정
# 버전이 바뀌어도 파일명이 app.jar로 유지됩니다
C:\deploy\는 WSL에서 /mnt/c/deploy/로 씁니다. C:\가 /mnt/c/로 바뀌고, 역슬래시(\)가 슬래시(/)로 바뀝니다.STEP 1 — JAR 파일 존재 확인
빌드를 깜빡하고 스크립트를 실행하는 실수를 막기 위해, 배포 전 JAR 파일이 실제로 있는지 먼저 확인합니다.
파일이 없으면 안내 메시지와 함께 즉시 중단합니다.
if [[ ! -f "$JAR_PATH" ]]; then
error "JAR 파일이 없습니다. IntelliJ에서 './gradlew clean bootJar' 먼저 실행하세요."
fi
# [[ ! -f "$JAR_PATH" ]]: 해당 경로에 파일이 없으면(-f의 반대, !) 조건 성립
# error 함수: 메시지 출력 후 exit 1 (스크립트 강제 종료)
STEP 2 — 서버로 파일 전송 (scp)
로컬의 JAR 파일을 서버로 복사합니다. scp는 SSH 방식으로 파일을 안전하게 전송하는 명령어입니다.
앱 디렉토리(/opt/your-app)는 전용 계정만 쓸 수 있어서, 일단 ubuntu 홈 디렉터리(~/)로 보낸 뒤 다음 단계에서 sudo로 이동합니다.
scp -i "$PEM" "$JAR_PATH" "${SSH_USER}@${SERVER}:~/app.jar"
# scp: SSH 기반 파일 복사 명령어 (Secure Copy Protocol)
# -i "$PEM": 비밀번호 대신 pem 키 파일로 인증
# ~/app.jar: 서버의 ubuntu 홈 디렉토리에 app.jar 이름으로 저장
STEP 3 — 교체 · 권한 설정 · 서비스 재시작
SSH로 서버에 접속해 명령을 실행합니다.
<< EOF ... EOF 형태(heredoc)로 여러 명령을 묶으면 SSH 접속을 한 번만 열어도 됩니다.
ssh -i "$PEM" "${SSH_USER}@${SERVER}" bash << EOF
# ① 기존 app.jar를 app-prev.jar로 백업 (롤백 대비)
if [[ -f "${APP_DIR}/app.jar" ]]; then
sudo mv "${APP_DIR}/app.jar" "${APP_DIR}/app-prev.jar"
fi
# ② 홈에 올려둔 새 JAR를 앱 디렉토리로 이동
sudo mv ~/app.jar "${APP_DIR}/app.jar"
# ③ 소유자를 앱 실행 계정으로 변경 (chown = change owner)
sudo chown ${APP_USER}:${APP_USER} "${APP_DIR}/app.jar"
# ④ systemd 서비스 재시작 (systemd = 리눅스 서비스 관리 도구)
sudo systemctl restart ${SERVICE_NAME}
EOF
<< EOF ... EOF)<< EOF부터 EOF까지의 내용을 하나의 텍스트 덩어리로 넘기는 셸 문법입니다. 여기서는 SSH에 연결된 서버가 이 구간 안의 명령들을 순서대로 실행합니다. SSH 접속을 여러 번 열지 않아도 되어 훨씬 효율적입니다.STEP 4 — 헬스 체크
Spring Boot 앱은 시작하는 데 수 초가 걸립니다. 재시작 직후 바로 확인하면 아직 뜨지 않은 상태라 실패로 오해할 수 있습니다.
그래서 5초 간격으로 최대 12번(60초)까지 HTTP 응답을 기다립니다.
for i in {1..12}; do
sleep 5
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://${SERVER}/")
# curl: URL로 HTTP 요청을 보내는 명령어
# -s: 진행 상황 숨김 -o /dev/null: 응답 본문 버림 -w "%{http_code}": 상태코드만 출력
if [[ "$HTTP_STATUS" == "200" ]]; then
ok "배포 성공! (HTTP ${HTTP_STATUS})"
exit 0
fi
echo " 기동 대기 중... (${i}/12, 현재 HTTP ${HTTP_STATUS})"
done
error "60초 내 기동 실패 — 로그 확인: sudo journalctl -u ${SERVICE_NAME} -n 50"
http://SERVER/)로 확인해야 실제 사용자 접속 경로와 동일하게 검증할 수 있습니다.
7 자동 백업과 롤백 방법
스크립트는 배포할 때마다 기존 app.jar를 app-prev.jar로 이름을 바꿔 보관합니다.
새 버전에 문제가 생겼을 때 이 파일로 즉시 되돌릴 수 있습니다.
ssh -i "~/.ssh/your-key.pem" ubuntu@YOUR_SERVER_IP \
'sudo mv /opt/your-app/app-prev.jar /opt/your-app/app.jar \
&& sudo chown appuser:appuser /opt/your-app/app.jar \
&& sudo systemctl restart your-app'
app-prev.jar가 덮어써집니다. 즉, 바로 직전 버전으로만 롤백할 수 있습니다. 불안정한 코드를 연속으로 배포하지 않도록 주의하세요.
8 처음 설정하고 사용하는 방법
스크립트 저장 위치
스크립트는 두 곳에 보관합니다. 프로젝트 폴더 안에는 Git으로 관리되는 원본을, WSL 홈에는 실행용 사본을 둡니다.
# 1. 프로젝트 안에 스크립트 보관 (Git 관리)
your-project/
└── deploy/
└── deploy.sh # 원본 (팀원과 공유, Git에 커밋)
# 2. WSL 홈으로 복사 (실행용)
cp "/mnt/c/Users/YourName/your-project/deploy/deploy.sh" ~/deploy.sh
cp 실행 시 No such file or directory 오류가 발생한다면, deploy.sh를 먼저 C:\deploy\ 폴더에 스크립트를 복사해둔 뒤 아래 명령어를 사용하세요.이후 배포 루틴 — 딱 2단계
# STEP 1. IntelliJ 터미널에서 빌드
./gradlew clean bootJar
# STEP 2. WSL에서 배포 스크립트 실행 — 이게 전부입니다
bash ~/deploy.sh
9 정리 — 자동화로 얻은 것
| 항목 | 자동화 전 | 자동화 후 |
|---|---|---|
| 배포 단계 | 5단계 (직접 입력) | 2단계 (빌드 + 스크립트) |
| 실수 가능성 | 높음 (경로·계정·권한) | 낮음 (변수 블록만 수정) |
| 백업 | 없음 | 자동 (app-prev.jar) |
| 배포 확인 | 수동 (브라우저 새로고침) | 자동 (헬스 체크) |
| 잘못된 내용이 있다면 지적부탁드립니다. 방문해주셔서 감사합니다. |

'인프라 & 배포 > AWS' 카테고리의 다른 글
| [AWS] AWS에 프로젝트 배포하기 (2) - 서비스 실행, Nginx 설정 (0) | 2026.06.19 |
|---|---|
| [AWS] AWS에 프로젝트 배포하기 (1) - EC2 생성, 초기 설정, 배포 준비 (0) | 2026.06.19 |
