개발 공부/백엔드

제미나이와 게시판 만들기: (8) Docker로 배포 준비하기 🐳

baby-t 2025. 10. 11. 19:39

지난 포스팅에서는 Swagger를 이용해 API를 문서화하는 과정까지 마쳤습니다. 이제 로컬 환경에서 완벽하게 동작하는 우리만의 백엔드 서버가 완성되었습니다.

이번 포스팅에서는 이 서버를 내 컴퓨터가 아닌, 실제 클라우드 서버에 배포하기 위한 첫 번째 단계인 Docker 컨테이너화에 대해 알아보겠습니다. "제 컴퓨터에서는 잘 되는데요..."라는 말을 영원히 없애줄 강력한 기술, Docker의 세계로 함께 떠나보시죠!

### 1. Docker가 뭐고, 어디에 쓰나요?

Docker는 애플리케이션과 그 실행에 필요한 모든 환경을 '컨테이너(Container)'라는 표준화된 상자에 담아주는 기술입니다.

'레시피와 도시락' 비유를 생각하면 쉽습니다.

  • 내 PC: 온갖 재료와 조리도구가 있는 나만의 주방
  • 문제점: 내 주방에서 만든 요리가, 친구네 주방(다른 서버)에 가면 재료나 도구가 달라 맛이 변하거나 아예 만들 수조차 없습니다.
  • Docker 이미지: 요리에 필요한 모든 재료, 조리도구, 완벽한 레시피까지 하나로 묶어놓은 **'밀키트'**입니다.
  • Docker 컨테이너: 그 밀키트로 만든 **완성된 '도시락'**입니다. 바로 열어서 먹기만 하면 됩니다.

Docker를 사용하면, 어떤 컴퓨터에서든 똑같이 실행되는 '프로그램 도시락'을 만들 수 있어 배포가 매우 간단하고 안정적이게 됩니다.

### 2. Docker 컨테이너화

#### 1단계: Dockerfile 작성 (레시피 만들기)

프로젝트의 루트 폴더에 Dockerfile이라는 이름의 파일을 만들고, 우리 애플리케이션을 어떻게 '밀키트'로 만들지 레시피를 작성합니다.

Dockerfile
 
# 1. Java 17 버전을 기반으로 이미지를 만듭니다.
FROM openjdk:17-jdk-slim
# 2. 빌드된 Jar 파일의 경로를 변수로 지정합니다.
ARG JAR_FILE=build/libs/*.jar
# 3. Jar 파일을 app.jar 라는 이름으로 이미지 안에 복사합니다.
COPY ${JAR_FILE} app.jar
# 4. 애플리케이션이 8080 포트를 사용함을 외부에 알립니다.
EXPOSE 8080
# 5. 컨테이너가 시작될 때 이 명령을 실행하여 애플리케이션을 구동합니다.
ENTRYPOINT ["java","-jar","/app.jar"]

Q: Dockerfile을 만들었는데 no such file or directory 오류가 떴어요. A: 파일 이름이 Dockerfile.txt처럼 확장자가 붙어있지 않은지 확인해야 합니다. Docker는 확장자가 없는 Dockerfile이라는 이름의 파일만 인식합니다.

#### 2단계: 프로젝트 빌드 (./gradlew build) 및 첫 번째 난관

Docker 이미지를 만들려면 먼저 프로젝트를 빌드해서 실행 가능한 .jar 파일로 만들어야 합니다. 터미널에서 아래 명령어를 실행했습니다.

 

Bash
./gradlew build

하지만, > Task :test FAILED라는 메시지와 함께 빌드가 실패했습니다! 원인은 테스트 코드가 DB에 연결하지 못했기 때문이었습니다.

#### 3단계: 테스트 환경 분리 (문제 해결의 시작)

./gradlew build는 빌드 과정에서 모든 테스트를 실행합니다. 이때 테스트가 실제 DB(서버 모드 H2)에 의존하면, H2 서버가 켜져 있지 않을 때 빌드가 실패하게 됩니다.

  • 해결책: 테스트는 외부 환경에 의존하지 않도록, 테스트 전용 인메모리 DB를 사용하도록 설정을 분리했습니다. src/test/resources 폴더를 만들고, 그 안에 application.properties 파일을 생성하여 아래와 같이 작성했습니다.
     
    Properties
    # 테스트 환경에서는 인메모리 H2 데이터베이스를 사용
    spring.datasource.url=jdbc:h2:mem:testdb
    
    이렇게 하면 테스트는 외부 DB 서버 없이도 항상 독립적으로 실행될 수 있습니다.

#### 4단계: 두 번째 난관과 최종 해결

테스트 환경을 분리한 후에도 PlaceholderResolutionException이라는 오류가 발생했습니다. 원인은 테스트 환경에서 JwtUtil이 필요로 하는 jwt.secret 값을 찾지 못했기 때문이었습니다.

  • 최종 해결책: 테스트용 application.properties에 JWT 설정 값을 추가해주었습니다.
    Properties
     
    # ... DB 설정 ...
    
    # JWT Settings (테스트용)
    jwt.secret=MyNickNameisSpringBootAndSpringFrameWorkForTest
    jwt.expiration_time=3600000
    
    이 모든 과정을 거친 후에야 test 작업이 성공하며 빌드가 완료되었습니다.
  • src/test/resources/application.properties

Q: 빌드가 계속 실패해서 결국 ./gradlew build -x test를 썼어요. A: -x test 옵션은 테스트를 건너뛰고 빌드를 강제로 진행하는 방법입니다. 당장 .jar 파일이 필요할 땐 유용하지만, 이는 임시 방편일 뿐 좋은 방법은 아닙니다. 실제 프로젝트에서는 모든 테스트가 통과해야만 빌드가 성공하는 것이 원칙입니다!

 

#### 5단계: Docker 이미지 생성 및 컨테이너 실행

빌드가 성공한 후, 아래 명령어로 Docker 이미지를 만들고 컨테이너를 실행했습니다.

Bash
# Docker 이미지 빌드
docker build -t my-board-app .

# Docker 컨테이너 실행
docker run -p 8080:8080 my-board-app

Q: 컨테이너 이름이 왜 charming_ramai 같은 이상한 이름인가요? A: docker run 시 --name 옵션으로 이름을 주지 않으면, Docker가 재미있는 이름을 무작위로 지어줍니다!

Docker가 잘 실행된 모습

### 3. 문제 해결: 컨테이너가 3초 만에 꺼져요! 😱

docker run을 실행하자, 컨테이너가 몇 초 만에 그냥 꺼져버리는 현상을 만났습니다. Docker Desktop을 확인해보니 컨테이너가 실행되지 않고 있었습니다.

이것은 컨테이너 안의 스프링 부트 앱이 시작되다가 오류가 발생하여 강제 종료되었기 때문입니다. 원인은 바로 데이터베이스 연결 문제였습니다.

#### 문제 1: localhost의 의미

컨테이너는 격리된 환경이라, 컨테이너 안에서 localhost는 **'컨테이너 자기 자신'**을 의미합니다. 하지만 우리가 연결하려는 H2 DB 서버는 컨테이너 **바깥쪽(내 PC)**에 있습니다.

  • 해결: src/main/resources/application.properties의 DB URL을 localhost에서 host.docker.internal로 수정했습니다. 이 주소는 컨테이너 내부에서 외부 호스트 PC를 가리키는 특별한 이름입니다.
  •  
    spring.datasource.url=jdbc:h2:tcp://host.docker.internal/~/community_board
    
  • Properties

#### 문제 2: 외부 연결 거부

수정 후에도 컨테이너가 계속 종료되었습니다. 이유는 H2 DB 서버가 보안상 외부(컨테이너)에서의 접속을 거부하고 있었기 때문입니다.

  • 해결: H2 콘솔을 실행할 때, '다른 컴퓨터에서 접속 허용' 옵션을 체크하여 외부 연결을 허용하도록 설정했습니다.

이 두 가지 문제를 해결하고 다시 docker run을 실행하니, 드디어 컨테이너가 종료되지 않고 계속 실행되는 것을 확인할 수 있었습니다!

#### 가장 먼저 해볼 것 - H2 DB 켜기

### Q&A: Docker에 대한 추가 궁금증

Q: H2 DB를 항상 켜둬야 하나요? 다른 사람도?

A: 아니요. 지금의 방식은 로컬 개발용 임시 구성입니다. 실제 서버(AWS 등)에 배포할 때는, 데이터베이스(MySQL 등)도 별도의 Docker 컨테이너로 함께 실행하고 docker-compose라는 도구로 둘을 연결합니다. 따라서 내 PC의 H2와는 전혀 상관없어집니다.

Q: Docker를 처음 켰는데 왜 예전 프로젝트가 보이나요?

A: Docker Desktop은 GitHub가 아닌, 내 PC에 설치된 모든 컨테이너 목록을 보여줍니다. 이전에 다른 프로젝트를 위해 Docker를 사용했다면 그 기록이 남아있는 것이며, 현재 프로젝트와는 무관합니다.


이것으로 Docker 컨테이너화 단계를 성공적으로 마쳤습니다. 이제 우리 프로젝트는 어디서든 동일하게 실행될 수 있는 '표준화된 도시락'이 되었습니다.

다음 포스팅에서는 이 도시락을 올려둘 실제 클라우드 서버, AWS에 인프라를 구축하는 과정을 다뤄보겠습니다.