
이번 작업은 단순히 Spring Boot 애플리케이션을 실행하는 수준이 아니라, 실제 운영 구조를 염두에 두고 CoreERP 백엔드를 AWS 환경으로 옮기는 과정이었다. 기존에는 로컬 환경에서 Docker 기반으로 Spring Boot, Redis, MariaDB를 함께 테스트하고 있었다면, 이번 단계에서는 Database를 RDS로 분리하고, Backend는 EC2 위 Docker 컨테이너로 실행하는 구조로 전환했다. 최종적으로는 Frontend와 Backend, Database의 역할이 분리되는 구조를 만들기 위한 준비 단계라고 볼 수 있다.
이번 배포 작업의 핵심 목표는 다음과 같았다.
즉 이번 단계는 Backend 서버를 단순히 띄우는 것이 아니라, Frontend와 분리된 독립적인 API 서버를 실제 운영 구조에 맞게 준비하는 과정이었다.
이번 배포에서 잡은 최종 구조는 다음과 같다.
이 구조에서 중요한 점은, Frontend가 직접 Database와 통신하지 않는다는 것이다. React Frontend는 HTTP 요청을 통해 Spring Boot Backend API를 호출하고, Backend는 JPA를 통해 RDS와 통신하며 데이터를 저장하거나 조회한다. Redis는 JWT Refresh Token 관리에 사용되며, 인증 로직의 상태 저장 역할을 수행한다.
즉 데이터 흐름은 아래처럼 정리할 수 있다.
EC2는 Ubuntu 기반으로 생성했고, Spring Boot 애플리케이션과 Redis 컨테이너를 실행할 서버로 사용했다. 이 단계에서 가장 중요했던 부분은 보안 그룹 설정이었다. 처음에는 SSH, HTTP, HTTPS, 8080 포트에 대한 허용 정책이 헷갈릴 수 있었지만, 실제로는 역할에 따라 분리해서 이해하면 된다.
이 구성이 중요한 이유는 운영 환경에서 모든 포트를 전체 오픈하면 안 되기 때문이다. SSH는 관리용 포트라 특정 IP만 허용해야 하고, 80과 443은 실제 웹 서비스 트래픽이 들어와야 하므로 전체 허용이 필요하다. 8080은 Spring Boot 자체 포트지만, 이 단계에서는 테스트 목적이므로 내 IP로 제한했다. 나중에는 Nginx 같은 Reverse Proxy를 두고 80, 443만 외부에 노출하는 구조로 바꾸는 것이 자연스럽다.
EC2 생성 후 가장 먼저 했던 일은 SSH 접속이었다. 여기서 브라우저 기반 SSH 연결이 실패했고, Windows 환경에서 ssh 명령 자체가 동작하지 않는 문제도 겪었다. 결과적으로 OpenSSH Client를 사용하고 직접 ssh.exe 절대경로로 접속하면서 해결했다.
이 단계는 단순 접속이 아니라, 이후 모든 배포 작업의 시작점이 되는 단계였다. 서버에 직접 들어간 뒤에는 패키지 업데이트, Docker 설치, Docker Compose 설치까지 순서대로 진행했다.
즉 이 단계는 Backend 애플리케이션을 실행할 Linux 운영 환경을 준비하는 과정이었다.
Spring Boot 백엔드는 EC2에 직접 Java를 설치해서 실행하는 방식이 아니라, Docker 컨테이너로 실행하는 방식으로 잡았다. 이 방식의 장점은 로컬 개발 환경과 배포 환경의 차이를 줄일 수 있다는 점이다. 로컬에서 Compose로 app + redis 구조를 이미 검증했기 때문에, 서버에서도 동일한 구조를 재현하는 것이 훨씬 안정적이었다.
설치가 끝난 후에는 docker 그룹 권한을 부여하고, 실제로 Docker와 Docker Compose가 정상 동작하는지 확인했다. 이 과정을 통해 EC2는 더 이상 단순한 가상 서버가 아니라, 실제 Spring Boot 서비스를 올릴 수 있는 실행 환경이 되었다.
배포를 진행하면서 GitHub 레포지토리 안에 남아 있던 Database 관련 파일도 정리했다. 여기서 중요한 판단은 무엇을 남기고 무엇을 제거할지 구분하는 것이었다.
처음에는 database/coreerp.sql 과 schema.sql 이 있었는데, 운영 구조를 RDS로 가져가기로 결정한 뒤에는 로컬 dump 성격이 강한 coreerp.sql 파일은 제거하는 방향이 더 맞다고 판단했다. 반면 schema.sql 같은 구조 정의 파일은 남겨두는 것이 의미가 있다. 왜냐하면 실제 운영 DB는 RDS에 존재하지만, DB 구조와 생성 기준은 Git에 남아 있어야 하기 때문이다.
즉 Git에는 DB 데이터 자체가 아니라, DB 구조와 생성 이력을 남기는 것이 맞다. 이 과정은 배포를 위한 정리 작업인 동시에, 프로젝트를 포트폴리오 관점에서 더 깔끔하게 만드는 작업이기도 했다.
이번 단계의 핵심은 로컬 MariaDB를 그대로 쓰는 것이 아니라, AWS RDS MariaDB를 생성해 운영 DB를 분리한 것이다. 이 작업을 하면서 Database 역할이 완전히 분리되었다.
RDS 생성 시에는 MariaDB 엔진, 프리티어 인스턴스, 자체 관리 비밀번호, 내부 연결 기반 보안 그룹 설정 등을 사용했다. 특히 중요한 점은 퍼블릭 액세스를 허용하지 않았다는 것이다. 즉 외부에서 DB에 직접 붙는 구조가 아니라, 같은 VPC 안의 EC2만 RDS에 접근할 수 있도록 설정했다.
이 구조는 실무에서도 매우 중요하다. 데이터베이스는 가능한 한 외부에 직접 노출하지 않고, 애플리케이션 서버를 통해서만 접근하도록 하는 것이 기본적인 보안 구조이기 때문이다.
RDS를 만들었다고 해서 기존 데이터가 자동으로 들어가는 것은 아니다. 그래서 기존 로컬 MariaDB에 있던 CoreERP 데이터를 RDS로 옮기는 작업이 필요했다.
처음에는 Git 레포지토리에 남아 있던 coreerp.sql을 활용할까 고민했지만, 이미 삭제한 상태였기 때문에 결국 로컬 DB에서 새롭게 dump를 떠서 RDS로 import 하는 방식으로 진행했다. 이 과정에서 DBeaver의 dump 기능을 사용해 coreerp_dump.sql 파일을 만들고, SCP로 EC2 서버에 업로드한 뒤, EC2에서 RDS로 import 했다.
이 과정이 중요한 이유는, 단순히 구조만 복원하는 것이 아니라 로컬에 쌓여 있던 실제 데이터까지 운영 DB로 가져오는 작업이었기 때문이다. 즉 CoreERP는 이제 더 이상 빈 시스템이 아니라, 실제 데이터가 들어 있는 상태에서 운영 환경으로 넘어가게 되었다.
배포 과정에서 핵심 역할을 한 것이 바로 .env 파일이었다. 로컬 개발 환경에서는 host.docker.internal 같은 값을 사용했지만, 운영 환경에서는 RDS endpoint와 Redis container를 기준으로 새롭게 구성해야 했다.
최종적으로는 다음 역할을 하는 값들이 필요했다.
이 단계에서 특히 중요한 것은 JWT secret이었다. 값이 비어 있으면 JwtTokenProvider 생성 시점에 바로 실패하며, 실제로 WeakKeyException이 발생했다. 이 문제는 충분히 긴 secret key를 추가한 뒤 해결되었다.
RDS 연결 후 처음으로 Spring Boot를 띄웠을 때, 애플리케이션이 바로 죽지 않고 로그를 통해 원인을 보여주었는데, 핵심 메시지는 아래와 같았다.
Connections using insecure transport are prohibited while --require_secure_transport=ON
이 에러는 RDS가 SSL 연결을 강제하고 있는데, 애플리케이션은 일반 연결로 붙으려고 했기 때문에 발생한 Configuration Error였다. 결국 JDBC URL에 SSL 관련 옵션을 추가해 해결했다.
이 과정은 단순한 에러 해결이 아니라, 운영 Database와 통신할 때 보안 연결이 필수라는 점을 이해하게 해준 과정이었다.
RDS와 Redis 설정을 마친 뒤에는 Docker Compose를 이용해 coreerp-app 과 coreerp-redis 를 함께 실행했다. 중간에 DB 연결 실패, JWT secret 누락, SSL 연결 문제 등 여러 단계의 오류를 하나씩 정리한 뒤, 최종적으로 Spring Boot 애플리케이션이 정상 기동되었다.
로그 기준으로 확인된 정상 상태는 다음과 같았다.
즉 이 시점부터는 EC2 위 Docker 컨테이너에서 Spring Boot API 서버가 실제로 살아 있는 상태가 되었다.
배포 후 처음에는 브라우저에서 접근했을 때 401 응답이 보여서 문제가 있는 것처럼 느껴질 수 있었다. 하지만 이건 서버 장애가 아니라 보호된 API에 인증 없이 접근했기 때문에 발생한 정상 보안 응답이었다.
또한 /api/auth/login 에 GET 요청을 보냈을 때는 405가 나왔는데, 이것도 로그인 API가 POST만 받도록 설계된 상태라 정상 응답이었다. 실제로 POST 요청으로 로그인 테스트를 하자 accessToken, refreshToken, user 정보가 정상적으로 반환되었고, 이로써 JWT 인증 흐름까지 정상 동작하는 것이 확인되었다.
Swagger 역시 정상 접근이 가능했고, 이로써 Backend, Security, DB, Redis까지 전체 흐름이 한 번에 검증된 상태가 되었다.
처음에는 EC2 브라우저 접속이 실패했고, Windows CMD와 PowerShell에서도 ssh 명령이 바로 동작하지 않았다. 이 문제는 OpenSSH Client 설치 및 절대 경로 실행으로 해결했다. 이 과정에서 인프라 문제가 아니라 로컬 개발 환경 문제라는 점을 구분하는 것이 중요했다.
AWS 콘솔의 브라우저 접속은 계속 실패했지만, 실제로는 로컬 SSH로는 접속이 가능했다. 이 문제 때문에 인스턴스를 다시 만들 필요는 없었고, 접속 방식만 바꾸면 해결되는 문제였다.
RDS는 보안상 SSL 연결을 강제하고 있었고, Spring Boot JDBC URL에는 처음에 SSL 옵션이 없었다. 이 때문에 DB 연결에서 실패했으며, SSL 옵션을 URL에 추가하면서 해결되었다.
애플리케이션이 기동된 뒤에도 JwtTokenProvider 생성 시 secret 값이 비어 있어 WeakKeyException이 발생했다. 이 문제는 JWT secret 값을 충분히 긴 문자열로 설정하면서 해결했다.
레포지토리의 coreerp.sql 파일을 정리한 뒤에는 그 파일을 import 하는 방식이 불가능해졌다. 대신 로컬 DB에서 새 dump를 떠서 RDS로 이관하는 방향으로 전환했고, 결과적으로 이 방식이 더 최신 데이터 기준으로 정리하기에 적절했다.
이번 작업으로 CoreERP는 단순한 로컬 프로젝트를 넘어서, 실제 운영 구조를 가진 백엔드 서비스 형태에 가까워졌다. 특히 다음과 같은 점에서 의미가 있다.
즉 이번 단계는 기능 개발이 아니라, 프로젝트를 진짜 서비스 형태로 옮겨가는 과정이었다고 볼 수 있다.
이제 백엔드 배포는 1차적으로 완료되었기 때문에, 다음 단계는 Frontend를 Cloudflare Pages 같은 정적 호스팅에 배포하고, EC2 백엔드와 연결하는 작업이 된다. 여기서 중요한 것은 API base URL과 CORS를 정확히 맞추는 것이다.
즉 다음 흐름은 다음과 같이 이어질 예정이다.
아래 명령어들은 이번 배포 과정에서 실제로 사용한 핵심 명령어들이다.
// EC2 접속
ssh -i "C:\\Users\\user\\Downloads\\coreerp-key.pem" ubuntu@3.34.199.124
// Docker 설치 확인
docker --version
docker compose version
// RDS 접속
mysql -h coreerp-db.cp2agwm0at6i.ap-northeast-2.rds.amazonaws.com -P 3306 -u admin -p
// DB 생성
CREATE DATABASE coreerp
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
// 로컬 dump를 EC2로 업로드
scp -i "C:\\Users\\user\\Downloads\\coreerp-key.pem" "C:\\Users\\user\\Downloads\\coreerp_dump.sql" ubuntu@3.34.199.124:~/
// RDS에 dump import
mysql -h coreerp-db.cp2agwm0at6i.ap-northeast-2.rds.amazonaws.com -P 3306 -u admin -p coreerp < ~/coreerp_dump.sql
// Docker 실행
docker compose down
docker compose up -d --build
// 로그 확인
docker compose logs -f
이번 배포 작업은 기능 구현보다 훨씬 많은 운영 관점의 판단이 필요했다. SSH 접속부터 보안 그룹, Docker 실행, RDS 이관, SSL 연결, JWT secret 설정까지 하나라도 빠지면 애플리케이션이 정상적으로 뜨지 않았다. 하지만 그만큼 이번 작업을 통해 백엔드 개발이 단순히 코드 작성만이 아니라, 실제 서비스가 돌아가는 환경 전체를 이해하는 과정이라는 점을 더 명확히 체감할 수 있었다.