🚀 진짜 작동하는 GitHub Actions + EC2 CI/CD 배포기
아기자기한 EC2 프리미어와 내 배포 자동화 여정

✨ 글을 쓰게 된 계기
이번에 개인 프로젝트를 진행하면서,
“GitHub Actions로 EC2에 자동 배포하는 CI/CD 파이프라인”을 직접 구성했다.
단순히 배포만이 아니라,
- 테스트 돌리고
- Jasypt로 비밀번호를 암호화해서 보안도 지키고
- EC2에 jar 파일을 전송하고
- 앱을 껐다 켜는 것까지 한 번에 처리
하는 걸 목표로 했다.
그 과정에서 정말 다양한 시행착오를 겪었고,
이 글은 그 실전기록이다.
🎯 목표
- 코드 푸시 → 테스트 + 빌드 → EC2에 jar 전송 → 앱 재시작
- DB 접속 정보는 application-prod.yml에 Jasypt로 암호화
- GitHub Secrets를 통해 민감 정보 관리
⚙️ 내가 만든 최종
deploy.yml
name: Deploy to EC2
on:
push:
branches: [ "main" ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for Gradle
run: chmod +x ./gradlew
- name: Run tests
run: ./gradlew test -Dspring.profiles.active=test
- name: Build project (prod profile with jasypt)
run: ./gradlew build \
-Dspring.profiles.active=prod \
-Djasypt.encryptor.password=${{ secrets.JASYPT_SECRET }}
- name: Write PEM file
run: |
echo "${{ secrets.EC2_KEY }}" > private_key.pem
chmod 600 private_key.pem
- name: SCP .jar to EC2
run: |
scp -i private_key.pem -o StrictHostKeyChecking=no \
./build/libs/*.jar \
${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USER }}/app/
- name: Restart app on EC2
run: |
ssh -i private_key.pem -o StrictHostKeyChecking=no \
${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << EOF
pkill -f 'java -jar' || true
nohup java -jar /home/${{ secrets.EC2_USER }}/app/*.jar \
--spring.profiles.active=prod \
--jasypt.encryptor.password=${{ secrets.JASYPT_SECRET }} \
> /home/${{ secrets.EC2_USER }}/app/log.txt 2>&1 &
EOF
🔍 시행착오 & 배운 점들
1. Secrets에 PEM 키 내용을 복붙하는 게 처음엔 너무 낯설었다
보통 .pem은 파일로 갖고 있다가 terminal 열어서 ssh -i ~~ 하는데?
근데 GitHub Actions에서는 텍스트 형태로 저장해야 하니까
Secrets에 -----BEGIN RSA PRIVATE KEY-----부터 -----END ...까지 전부 복붙해서 저장해야 했다.
👉 해결:
- echo "$EC2_KEY" > private_key.pem
- chmod 600 private_key.pem
- 으로 Actions 안에서 임시로 .pem 파일을 만들어줬다.
2. 빌드할 때도 Jasypt 암호화 키가 필요하다는 걸 몰랐다
처음엔 --jasypt.encryptor.password=... 옵션은
앱 실행할 때만 필요한 줄 알았는데…
❌ ./gradlew build 할 때도 application-prod.yml을 로딩하면서 암호화된 값을 복호화하더라.
👉 해결:
./gradlew build -Dspring.profiles.active=prod -Djasypt.encryptor.password=...
3. Spring Profile을 빌드/실행 때 명확히 지정 안 하면 꼬인다
처음에는 그냥 ./gradlew build만 했는데,
application.yml이 기본으로 잡히니까 DB 설정 오류로 빌드 실패함.
👉 해결:
- 빌드: -Dspring.profiles.active=prod
- 실행: --spring.profiles.active=prod
4. EC2에 MySQL을 직접 설치할지, RDS를 쓸지 고민 많았음
처음엔 EC2 에 로컬 Mysql 설치하려다가 프리티어 EC2 특성상 1GB 밖에 안되는 메모리에
너무나도 작고 소중한 아이기 때문에 다른계정으로 RDS를 만들어서 연결해야하나 고민했다.
프리티어는 월 750시간 제한이 있어서 RDS + EC2 동시에 쓰면 금방 초과될줄 알았다.
하지만 이제 RDS 프리티어 따로 EC2 프리티어 따로라고 들음( by. 인프라 고수 )
그래서 결국 RDS로 MySQL을 옮기고 EC2에는 Spring Boot만 돌리게 했음
👉 장점: DB 접근 분리, 보안, 확장성
👉 단점: 설정 복잡 (보안그룹 열기, 엔드포인트 연결 등)
5. 순환 참조 조심할 것
application-test.yml 에
spring:
profiles:
active: test
이렇게 돌리니 순환참조가 되버리는것.. 이미 test로 들어왔는데 또 다시 test를 찾게되는거
그래서 Build 실패를 여러번 겪음
6. deploy.yml 문법
name: Deploy to EC2
on:
push:
branches: [ "main" ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for Gradle
run: chmod +x ./gradlew
- name: Run tests
run: ./gradlew test -Dspring.profiles.active=test
- name: Build project (prod profile with jasypt)
run: ./gradlew build \
-Dspring.profiles.active=prod \
-Djasypt.encryptor.password=${{ secrets.JASYPT_SECRET }}
- name: Write PEM file
run: |
echo "${{ secrets.EC2_KEY }}" > private_key.pem
chmod 600 private_key.pem
- name: SCP .jar to EC2
run: |
scp -i private_key.pem -o StrictHostKeyChecking=no \
./build/libs/*.jar \
${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USER }}/app/
- name: Restart app on EC2
run: |
ssh -i private_key.pem -o StrictHostKeyChecking=no \
${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << EOF
pkill -f 'java -jar' || true
nohup java -jar /home/${{ secrets.EC2_USER }}/app/*.jar \
--spring.profiles.active=prod \
--jasypt.encryptor.password=${{ secrets.JASYPT_SECRET }} \
> /home/${{ secrets.EC2_USER }}/app/log.txt 2>&1 &
EOF
여기 보다보면 run: | 이 있고 없는게 있다.
뭐가 있고 없냐면 |||||||| 이거 말이다. 이게 뭔놈의 역할인지 몰랐던것
이게 여러줄의 commend를 미리 선언하는거란다.
| <- 이게 없이 여러줄의 commend를 적어버리면 이제 인마가 잘못 해석해버리는것 그거때매 또 commit 2번했다.
🔐 GitHub Secrets 정리
이름용도
EC2_KEY | .pem 키 내용 전체 |
EC2_USER | EC2 사용자 (ex. ubuntu) |
EC2_HOST | 퍼블릭 IP |
JASYPT_SECRET | Jasypt 암호화/복호화 키 |
우선은 이렇게 집어넣었다.
✅ 결과
지금은 진짜 코드만 푸시하면
→ 테스트, 빌드, 배포, 실행까지
→ 전부 자동으로 굴러간다.
로그는 EC2에서 cat /home/ubuntu/app/log.txt로 확인 가능하고,
배포 실패 시에는 GitHub Actions에서 로그 트레이싱도 가능해서 디버깅이 훨씬 쉬워졌다.
🧠 정리하며
처음엔 하나하나 다 생소하고 어려웠지만,
막히는 지점마다 직접 원인 찾아가면서 내 손으로 문제를 풀어낸 과정이 너무 값졌다.
또한 과거 SSAFY 프로젝트에서 진행했던 Jenkins + Docker 배포보다는 한참 쉬웠다.
지금 이 글을 보고 있는 누군가가
내가 겪었던 어려움을 조금이라도 덜 겪었으면 좋겠다 😊