INTRO
여느 때와 다름없는 평화로운 금요일
결혼식을 한다고 2년 가까이 길었던 머리를
단발로 싹-둑 자르고 파마를 하러 미용실에 방문했다.
꾸벅꾸벅 졸고 있던 찰나, 휴대폰이 '징- 징-' 울렸다.
무심코 화면을 켰는데 순간 내 눈을 의심했다.
구글 클라우드 ~~~ 52,150원 결제 완료 카드사 결제 알림이 떠 있었다.

"대체 내가 뭘 돌렸길래 5만 원이나 결제가 된 거지?"
머리에 약을 바른 채 구글 클라우드 콘솔 앱을 켜고 한참을 뒤적거리며 찾아봤다.
네트워크 트래픽 과금인가? 무료 티어 기준을 초과한 사용량이 무엇 때문인지 분명치 않았다.
안 그래도 기존에 사용하던 GCP 무료 티어 서버는 너무 버벅거리고
접근할 때마다 느려서 개발하고 테스트하기가 여간 불편한 게 아니었다.
이 요금 과금 문자를 시작으로 평소 자주 떠들떠들 하는 개발자 오톡방에 클라우드 추천을 받았고
사람들이 추천한 오라클 클라우드(OCI)로 서버를 아예 이전하기로 했다.
무사히(?) OCI에 인프라 환경을 처음부터 새롭게 구축하고
모든 설정을 보란 듯이 마쳤지만, 기쁨도 잠시...😰
Admin 로그인 페이지에 접속하자마자 서버 전체가 완전히 뻗어버리는 치명적인 문제를 마주하게 되었다.
온갖 삽질 끝에 서버 가상 메모리(Swap Space) 할당이라는 인프라적 조치를 통해 이 사태를 해결해냈다.
Gemini랑 새벽 3시까지 작업한 인프라 이전 과정과 트러블슈팅의 전체 과정을 생생하게 기록해두려 한다.
(또 언제 할 줄 모르잖아 ?)
개발환경 및 클라우드 비교
이번 마이그레이션을 진행하며 기존의 AS-IS 환경과 새로운 TO-BE 환경의 차이는 꽤 컸다.
특히 단순 반복 배포에서 벗어나 최신 트렌드에 맞춘 Docker 환경을 추가 도입했다.
| 구분 | AS-IS (GCP 환경) | TO-BE (OCI 환경) |
|---|---|---|
| 운영체제 및 인프라 | Google Cloud Platform (무료 티어) | Oracle Cloud Infrastructure (무료 티어) |
| 배포 패러다임 | jar 파일 수동 빌드 > 업로드 등 단순 배포 | Docker Compose 기반 다중 컨테이너 자동화 환경 |
| 웹 프론트 엔드 | 웹 서버 부재 8080 등 개별 포트 직접 접근 |
Nginx 리버스 프록시 적용 (80/443 포트 브릿지) |
| 서버 시스템 자원 | 물리 RAM 1GB (빈번한 스와핑 발생) |
물리 RAM 1GB + Swap Memory 2GB 강제 할당 추가 |
사용 기술: Java 17, Spring Boot 3.3.2, PostgreSQL 15, RabbitMQ, Nginx, Certbot
💡 무료 티어 최강자 비교: GCP vs OCI
나처럼 토이 프로젝트나 레거시 개선 연습용 개인 서버를 찾는 개발자에게 무료 클라우드는 사막의 오아시스다.❤️
하지만 벤더사마다 제공하는 자원에는 명확한 차이가 존재한다.
| 항목 | GCP (e2-micro) 무료 티어 | OCI (VM.Standard.E2.1.Micro) 무료 티어 |
|---|---|---|
| RAM (메모리) | 1GB | 1GB (ARM 기반 Ampere는 24GB까지 주기도함) |
| CPU 코어 | 2 vCPU (0.25 코어 제한을 둔 버스팅) |
1/8 OCPU (AMD 마이크로 인스턴스 기준) (ARM 선택 시 최대 4 OCPU) |
| 네트워크 트래픽 | 북미 리전만 무료 (아시아는 과금) 월 1GB 무료 |
서울/춘천 리전 선택 가능 월 10TB까지 무료 |
| 디스크(스토리지) | 30GB 고정 (Standard HDD 수준 속도) |
최대 200GB 무료 제공 (Boot Volume 성능 압도적) |
| 총평 | 간단한 Node.js나 파이썬 연습용. 무거운 Java(Spring) + DB 버티기 힘듦. |
한국 리전을 무료로 주어 체감 접속 속도 빠름 디스크가 넉넉함. Java 프로젝트의 새로운 은신처로 각광. |
비교표를 보면 알겠지만, 한국 리전이 무료로 제공되면서
트래픽이 10TB나 허용되는 OCI는 개인 서버 용도로는 현재 적수가 없다.
OCI 환경 구축: 밑바닥부터 Nginx까지
가장 먼저 맞이한 과제는 그야말로 아무것도 깔려있지 않은
텅 빈 OCI 서버 깡통을, 내 프로젝트가 살아 숨 쉬며 운영 가능한 인프라로 탈바꿈시키는 것이었다.
이 과정은 다음 순서로 진행되었다.

- OCI 회원가입 및 우분투 인스턴스 생성
OCI에 가입하는 것부터가 난관이라고들 하지만 운 좋게 한 번에 통과했다.
이후 무료 티어(Always Free)가 제공되는VM.Standard.E2.1.Micro인스턴스를 하나 파냈다.
운영체제는 패키지 관리가 제일 편하고 레퍼런스가 방대한 Ubuntu 22.04 LTS 버전을 선택했다. - 고정 IP 할당 및 도메인 연결
클라우드 인스턴스는 재부팅을 하거나 중지했다가 켜면 가상 IP가 바뀌어버리는 현상이 발생한다.
이렇게 되면 도메인과 연결이 끊어지기 때문에 OCI 콘솔 메뉴에서예약된 공용 IP(Reserved Public IP)를 하나 발급받아
내 인스턴스의 가상 네트워크(VCN)에 고정으로 물려주었다.
그 후 내가 소유한 호스팅케이알(내 도메인 관리처) 관리자 페이지에 로그인해
이 새로운 고정 IP 주소로 도메인(http://[내부도메인])의 A 레코드를 매핑 연결해주었다. - 도커(Docker) 및 인프라 필수 패키지 설치
서버 내부의 폴더들이 파편화되고 의존성 라이브러리가 꼬이는 현상을 막고자
모든 컴포넌트를 Docker 컨테이너 위에서 깔끔하게 통제하기로 했다.
맥북 터미널을 키고 SSH로 우분투에 진입sudo apt-get update와 함께
Docker 데몬 및 여러 컨테이너를 한방에 띄워줄 수 있는 Docker Compose 패키지를 순차적으로 설치했다. - OCI 방화벽(보안 목록) 포트 개방
내부적으로 애플리케이션의 8080 포트와 웹 서버의 80 포트가 돌고 있어도
OCI 자체가 지닌 방화벽(Security List)이 꽉 막고 있다면 외부 인터넷에서는 절대 접속할 수가 없다.
OCI VCN의 서브넷 설정에 들어가서, HTTP 통신을 위한 80 포트와
HTTPS를 위한 443 포트, 원격 접속을 위한 22 포트에 대한 Ingress(수신) 규칙을
모두0.0.0.0/0(전 세계 누구든) 상태로 허용 포트를 개방해주었다. - Nginx 리버스 프록시 설정 및 서버 적용
기존에는 프론트에 WAS가 직접 노출되어 있어서http://[도메인]:8080처럼 주소 뒤에 포트 번호를 지저분하게 붙여야만 했다.
이를 개선하고자, 사용자는 깔끔하게 80 포트로 들어오고 그 트래픽을 백엔드의 8080 포트로 자연스럽게 토스해주는
'리버스 프록시(Reverse Proxy)' 설정을 위해 Nginx 웹 서버를 도입했다.
소스코드 레포지토리 루트에 nginx 폴더를 파고default.conf 설정 파일을 만들어 아래와 같은 설정을 부여했다.
server {
listen 80;
listen [::]:80;
server_name [내부도메인];
location / {
# app은 Docker Compose에서 명명한 Spring Boot 컨테이너 이름
proxy_pass http://app:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
이후 루트의 docker-compose.yml 파일에
Nginx 이미지를 받아오는 컨테이너 정의를 추가하고
호스트의 ./nginx 디렉토리를 컨테이너 속 /etc/nginx/conf.d 로 마운트하여
이 설정 파일을 자동으로 읽어오도록 구성했다.
- GitHub Actions 자동 배포 세팅 완료
손으로 매번 코드를 빌드하고 옮기고 재시작하는 수동 배포의 번거로움을 덜기 위해.github/workflows/deploy.yml파일을 작성해 CI/CD 파이프라인을 구축했다.
appleboy/ssh-action 플러그인을 활용해 OCI 서버에 은밀하게 접속한 다음docker-compose down -v 로 기존 컨테이너를 내리고
변경된 코드들과 함께 docker-compose up -d --build 명령어가
자동으로 실행되도록 스크립트를 조율했다.
설정한 모든 세팅을 마치고 떨리는 마음으로 크롬 브라우저에 http://[내부도메인] 주소를 쳐서 엔터를 눌렀다.
드디어 로컬 컴퓨터에서만 보던 우리 프로젝트의 반가운 웰컴 페이지가 OCI 망을 타고 아름답게 나타났다!

문제 상황 통보: 죽음의 무한 로딩
Nginx 경로 세팅이며 Docker 배포 자동화까지 모든 것이 너무도 교과서처럼 완벽하게 끝났다고 생각했다.
아주 흐뭇한 마음으로 개발 서버 대시보드를 둘러보기 위해 Admin 계정으로 로그인을 시도하는 버튼을 클릭했다.
그런데 화면 중앙의 로그인 로딩 스피너 창이 빙글빙글 돌기 시작했다.
처음엔 클라우드 데이터베이스 초기 로딩이 유독 느린가 싶어 기다려 보았다.
개발자 도구 네트워크 탭을 열어보니 pending 상태로 응답을 애타게 기다리고 있었다.
요상함을 느끼고 요청을 Esc 키로 강제 취소한 뒤 다시 가벼운 웰컴 페이지로 접속을 시도했으나
이제는 아예 빈 화면만 뜨고 브라우저가 아무런 반응조차 하지 않았다.

그렇게 하염없이 1분여를 기다리자
브라우저에는 504 Gateway Time-out 에러 화면이 출력되었다.
Nginx는 살아서 어떻게든 앞단을 지키고 있었지만 뒤에서 답을 줘야 할 Spring Boot가 기절했다는 확실한 표식이었다.
설상가상으로 구체적인 원인을 파악하기 위해 서버에 SSH 접속 툴(Mac의 iTerm)을 켜고 터미널 접근을 시도했지만
웬걸...

Connection timed out 에러 문구만 덩그러니 내뱉으며 심지어 터미널은 내 키보드 입력을 모두 무시했다.
어플리케이션(Spring Boot)만 죽은 게 아니었다.
OCI 리눅스(Ubuntu) 운영체제 껍데기 전체가 완전히 동태가 되버린 Kernel Freeze 상태인 것이다.
첫 번째 시도: 코드 변경 로그 확인 및 재배포 모니터링 (실패)

시도한 이유 ⁇
가장 처음 드는 합리적 의심은 방금 내가 직접 작성한 Nginx의 라우팅 설정 파일(default.conf)이
오타나 논리 오류로 인해 내부에서 통신 무한 루프 상태에 빠졌을 가능성이었다.
또는 JWT 인증이나 Spring Security 로직에서 토큰을 빙빙 돌리며
자원을 독식하는 결함이 배포 과정에서 함께 숨어 들어갔다고 의심했다.
적용 방법
- 터미널조차 연결되지 않는 이 깡통 상태를 풀기 위해 OCI 클라우드 콘솔 홈페이지에 직접 접속했다.
인스턴스 전원을 차단(Force Stop)하는 메뉴를 눌러 가상머신을 물리적으로 재부팅하고 다시 시작(Start)시켰다. - 서버가 간신히 켜지자마자, 다음 다운이 오기 전에
도커 컨테이너들의 로그 스트림(docker-compose logs -f) 명령어를 띄웠다.
그리고 Nginxproxy_pass설정에 잘못된 헤더나 Loop 옵션이 있는지 두 눈에 불을 켜고 확인했다.
문제점 및 한계
하지만 까보면 깔수록 Nginx 설정은 문법적 오류 하나 없이 너무나 완벽했다.
(Gemini가 써준거라 완벽한 줄 알았다😡)
에러 로그 한 줄 보이지 않았다.
서버가 막 재부팅된 직후에는 웰컴 페이지 접속이 아주 평온하고 빠르게 잘 진행되었다.
그러나, 다시 브라우저 창을 열고 Admin 인증을 거쳐 수많은 회원 테이블과 가입 통계를 조인해서 불러오는
'강력한 DB 내부 로직'이 담긴 API를 호출하는 순간
또다시 어김없이 터미널의 커서가 정지해버리며 서버 전체가 기절해버렸다.
예외(Exception) 추적 로그를 살펴보고 자시고 할 여유조차 주지 않고
또 다시 🔫 빵- 하고 OCI 서버가 마비된 것이다.

배운 점
Nginx 컨테이너가 끝끝내 사용자 브라우저에 에러 응답(504)을 잘 던져주고
뒷단 시스템 전체가 통제 불능이 된다는 것은
단순한 프록시 규칙 에러나 코드의 무한 연산 문제가 아니라는 걸 깨달았다.
이는 그 뒤에서 구동 중인 대형 WAS(Spring Boot) 및 다른 요소들이 서로 경합하다가
물리적인 서버의 핵심 리소스를 아예 고갈시켜버린 인프라 레벨의 치명상이라는 뜻이다.
최종 해결: 인공호흡기 가상 메모리(Swap File) 할당

근본적 원인 분석 (OOM 패닉 현상)
문제의 본질은 OCI 무료 티어 인스턴스가 가진 치명적인 사양적 한계 때문이었다.
CPU 아키텍처나 엄청난 디스크 용량이 전부가 아니다.
가장 중요한 워크스페이스인 물리적 메모리(RAM)가 단 1GB에 불과하다는 점이 재앙의 씨앗이었다.
현재 돌리고 있는 우리 프로젝트의 사양을 계산해보면 답이 나온다.
무거운 JVM 기반의 거대한 Spring Boot WAS 컨테이너 하나가 기본 400MB 이상을 점유하고
PostgreSQL 데이터베이스가 200MB, 비동기 통신을 제어하는 RabbitMQ가 또 200MB...
게다가 트래픽 앞단을 지키는 Nginx와 GitHub 배포를 위한 데몬 서비스들까지...
무려 5마리의 덩치 큰 프로세스들이 좁디좁은 1GB RAM 공간(자취방) 안에서 숨 막히게 공존하고 있었다.
평온하게 대기 중일 때는 이들이 어떻게든 구겨져서 아슬아슬하게 적재되어 있다.
(대만의 구룡성채나 다름없는 듯 😭)
하지만, 내가 '로그인'을 진행한 순간 Spring Data JPA 구현체가 대량의 테이블을 읽어
영속성 컨텍스트(메모리 캐시) 공간에 엔티티들을 대거 적재하려 든다.
이때 RAM 사용량이 순식간에 100% 임계점을 뚫고 나갈 기세로 되는 것 이다.
Linux 운영체제(커널)는 물리 메모리가 극도로 부족해 시스템이 위험해지면
가용한 공간을 확보하기 위해 가장 RAM을 많이 먹고 있는 녀석을 골라
강제로 사살해버리는 OOM(Out of Memory) 킬러 메커니즘을 발동시킨다.
이때 보통 Java(+Spring Boot) 프로세스가 제일 첫 번째 타깃🔫 이 되어 죽는다.
더 심각한 문제는 OCI 서버의 초기 우분투 세팅에는 스토리지 디스크(SSD)의 빈 공간을 잠시 빌려 쓰는
가상 메모리 영역인 Swap 메모리가 단 1Byte도 존재하지 않는 0B로 세팅되어 있었다는 점이다.
RAM 용량이 초과되었을 때 잠시 피난 갈 수 있는 최후의 보류(인공호흡기) 공간이 아예 없어서
OOM 킬러마저도 프로세스를 정리하지 못하고 커널 패닉에 빠져
서버 전체가 쇼크사(System Freeze) 해버린 것이다.
최종 적용 방법 및 구현 코드
이를 해결하기 위한 조치는 간단명료하다.
💰돈을 쓰면 된다.
정말 미니PC를 살까 고민도 했다.
그래도 일단 , 무료 티어로 버텨보자 싶었다.
그래서 하드디스크(SSD)의 넉넉한 200GB 무료 공간 중 자투리 2GB를 강제로 빼앗아 잘라내고
이를 가상 RAM처럼 작동하게 만들어주는 리눅스 Swap 영역 할당 설정을 수동으로 진행하면 된다.
OCI 콘솔에서 서버 강제 재부팅을 또다시 먹인 직후 도커 컨테이너들이 모두 구동되며
메모리를 잡아먹기 전의 골든 타임(약 2분) 안에 터미널을 열고 재빨리 아래 명령어들을 순차적으로 쏟아부었다.
# 1. 시스템 루트 파티션에 2GB 크기의 커다란 빈 파일(스왑용) 생성
# fallocate는 dd 명령어보다 할당 속도가 훨씬 빠르다
sudo fallocate -l 2G /swapfile
# 2. 스왑 파일의 권한 변경
# 관리자(root) 계정만 읽고 쓰기가 가능하도록 권한을 600으로 막아야 보안 경고가 뜨지 않는다.
sudo chmod 600 /swapfile
# 3. 방금 만든 일반 빈 파일을 스왑 공간 구조로 내부 포맷
sudo mkswap /swapfile
# 4. 포맷된 해당 공간을 가상 메모리로 리눅스 커널에 활성화 명령!
sudo swapon /swapfile
# 5. 마운트 테이블(/etc/fstab)에 영구 등록
# 이 설정을 잊으면 다음 재부팅 때 스왑 메모리가 날아간다.
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 6. 최종 할당 상태 확인
free -h
저 명령어들을 실행한 후 마지막으로 free -h를 입력하여 상태표를 보았을 때
눈앞에 나타난 글자는 이제 잘 수 있구나를 외쳤다 !

기존 Swap: 0 자리에 Swap: 2.0Gi 라는 수치가 뚜렷하게 잡혀
시스템에 든든한 보조 메모리 공간이 확보된 것이다.
이후 다시 Admin 환경에 접속하여 대략 2만건의 데이터를 긁어오는 복잡한 조인 쿼리 로직 버튼을 과감히 눌러 보았다.
터미널 모니터링 창에서 RAM 활용도가 1GB의 한계치인 95%를 뛰어넘으려 할 때마다
눈치 빠른 시스템이 즉각 백그라운드 프로세스들을 방금 만든 2GB의 Swap 영역으로 밀어내주기(Paging) 시작했다.
그 결과, 단 한 번의 에러 스파이크나 타임아웃 멈춤 현상 없이
Spring Boot 서비스가 마치 16GB 메모리 서버에 있는 양 아주 쾌적하게 끝까지 동작을 무사히 마쳤다!
한눈에 보는 OCI 서버 인프라 전체 설정 순서표
이번 인프라 환경 이전 및 요금 청구 사태로 시작된 OCI 마이그레이션과 장애 대응 전체 이력 타임라인을 적어보았다.
다음에 또 내가 서버를 구성할 때 참고하실 수 있도록 하나의 테이블으로 정리해 본다.
| 진행 순서 | 핵심 작업 항목 | 작업 상세 내용 요약 |
|---|---|---|
| Step 1 | OCI 회원가입 및 서버 세팅 | 오라클 클라우드 가입 후, Ubuntu 22.04 LTS 이미지를 사용 무료 제공 규격인 VM.Standard.E2.1.Micro 인스턴스 신규 생성함. |
| Step 2 | 네트워크 및 고정 IP 설정 | 재부팅 시 IP 유실 방지로 Reserved Public IP 발급 인스턴스에 고정 할당함. 발급받은 IP는 구매한 도메인 DNS에 A 레코드로 직접 매핑 연결. |
| Step 3 | 서버 필수 의존 패키지 설치 | SSH 원격 접속 후 인스턴스 내부에 패키지 관리자를 통해 Docker 데몬 Docker Compose 오케스트레이션 툴 Git을 설치 |
| Step 4 | OCI 보안 정책 및 방화벽 개방 | OCI VCN의 Security List 수정 메뉴에 접근 HTTP(80), 443 포트& SSH(22) 수신 룰 완전히 오픈 |
| Step 5 | 앞단 Nginx 웹 프록시 구성 | 루트 nginx/default.conf 파일을 작성사용자의 80 포트 요청을 백엔드 스프링부트 컨테이너 8080 포트로 내부에서 패스하도록 브릿지 생성. |
| Step 6 | 인프라 CI/CD 배포 파이프라인 | Github Actions의 deploy 스크립트를 작성 메인 브랜치 푸시 시 자동 서버 SSH 접속 및 docker-compose up 갱신 구동 파이프라인 구축 완료. |
| Step 7 | 치명적 메모리 부족 장애 발생 | OCI 환경 구축 완료 후 첫 관리자 로그인 시도 시 화면 무한 로딩, 504 Time-out 발생 및 서버 OOM 판정 우분투 전체가 다운되는 등 완전 먹통 현상 마주. |
| Step 8 | Swap 메모리 영역 강제 추가 | 장애 해결의 핵심! 터미널 접속 후 fallocate로 2GB 공간 스왑 영역 지정 권한 부여 및 fstab 영구 등록 , 이후 모든 프로세스 정상 동작 확인! |
배운 점

- 인프라 관리는 코드 너머의 숲을 보는 것이다.
백엔드 개발자라 할지라도 단순히 자바 코드와 어플리케이션 아키텍처 결함만 볼 게 아니다.
배포되는 클라우드 인스턴스의 베어메탈 급 물리적 시스템 리소스(RAM 사용률, CPU Idle 상태, Disk I/O)도
반드시 htop이나 top 명령어로 직접 들여다보는 모니터링 습관을 길러야 한다.
- 504 에러의 숨은 함정
Nginx 등 리버스 프록시 앞단을 세웠을 때 클라이언트 브라우저에서 맞는504 Gateway Time-out은
주로 백엔드 서버(WAS) 환경 시스템 자체가 아예 기절해서 응답을 줄 컴퓨팅 리소스가 없기 때문에 흔히 발생한다.
에러를 보자마자 다짜고짜 애먼 네트워크 인증 통신이나 라우팅 구성만 의심하고
파싱 코드를 뒤지면 하루 종일 답도 없는 삽질을 하게 된다.
- 무료 티어 클라우드의 현실적인 생존 필수값
해외 클라우드 벤더사(GCP, AWS, OCI 등)가 홍보성으로 제공하는 제일 저렴한 무료 마이크로 1GB 인스턴스는
RAM이 지독하게 쪼들린다. 만약 로컬에선 잘 되던 프로젝트가 이곳에 배포했을 때 수시로 죽는다면
Docker 여러 개와 무거운 JVM 기반 프레임워크를 올릴 땐 Swap 가상 메모리 우회 세팅을 하는 것이
선택 옵션이 아니라 그냥 생존을 위한 필수 전제조건임을 아주 뼈아프게 극복해내며 배웠다.
나의 삽질 및 실수 모음집 & 해결 팁

이번 인프라 구축 과정에서 정말 자잘하고 어이없는 실수들을 많이 저질렀다.
나름 잘 설정했다고 생각했는데 안 되어서 당황했던 포인트들을 모아 보았다.
1️⃣ Nginx 443 포트 매핑 누락
Nginx 설정 파일(default.conf)에는 listen 443 ssl; 이라고 적었다.
Nginx 자체가 HTTPS 포트를 열어두도록 완벽하게 잘 만들었다.
그런데 서버에 제일 바깥에서 컨테이너를 띄우는 docker-compose.yml 파일에서
정작 443:443 포트를 외부로 개방(매핑)하는 설정을 빼먹었다.
👉 해결 팁
도어락은 바꿨는데 현관문을 안 열어준 격이다.
docker-compose.yml 파일의 ports 설정에 꼭 443 포트를 추가하자.
2️⃣ 도커 빌드 실패(Build failed) - .jar 파일 2개 오지랖
로컬에서 잘 빌드되던 도커가 서버에서 자꾸 죽길래 봤더니
빌드 결과물 폴더에 기본 .jar 파일과 함께 -plain.jar 파일까지 2개가 생성되어 있었다.
도커 데몬 입장에서는 어떤 jar 파일을 카피해서 구동해야 할지 몰라 빌드가 실패한 것이다.
👉 해결 팁
로컬 프로젝트의 build.gradle 파일에 -plain.jar 파일은 아예 생성되지 않도록 설정
(jar { enabled = false })을 딱 한 줄 추가해 두어 깔끔하게 해결했다.
3️⃣ docker compose vs docker-compose 버전 충돌
어떤 가이드 문서에는 띄어쓰기가 있고 어떤 건 하이픈(-)이 있어서 헷갈렸는데,
결국 내가 설치한 서버 환경(구버전 호환)에서는 띄어쓰기를 한 docker compose 명령어가 작동하지 않았다.
👉 해결 팁
배포 스크립트의 명령어를 모두 docker-compose 구문으로 바꿔서 구동시켰다.
4️⃣ GitHub Actions Nginx 폴더 누락
CI/CD를 통해 배포가 쫙쫙 잘 된 줄 알았는데, Nginx 컨테이너가 켜지자마자 죽어버렸다.
로그를 까보니 호스트 서버에 nginx/default.conf 파일이 존재하지 않는다는 에러였다.
👉 해결 팁
deploy.yml 배포 스크립트 안에서 scp 커맨드로 파일을 클라우드 서버에 복사해 넘길 때
docker-compose.yml만 넘기고 nginx 디렉터리는 안 넘기고 있었다.
scp -r에 ./nginx 폴더를 포함시켜서 해결했다.
5️⃣ .env 파일 무자비한 덮어쓰기 대면책
서버 DB 비밀번호 같은 환경변수를 GitHub Actions의 Secrets로 주입할 때 초보적인 실수를 저질렀다.
echo "${{ secrets.DB_PASS }}" > .env 형식처럼 꺾쇠 화살표 한 개(>)를 쓰는 바람에,
기존에 만들어 둔 수많은 .env 속 변수들을 모두 싹둑 날려버리고 저거 단 한 줄만 덮어써 버린 것이다.
👉 해결 팁
파일 내용을 덮어쓰는 게 아니라 맨 밑에 '내용 추가' 만 하려면 꼭 화살표 두 개(>>)를
붙여서 써야 한다는 리눅스 커맨드의 무서움을 깨달았다.