[Docker] 스프링 부트 + PostgreSQL Docker 컨테이너 배포 과정에서 겪은 문제점 기록

소영·2025년 4월 10일

Docker

목록 보기
3/3
post-thumbnail

들어가면서

코드잇 스프린트에서 진행한 미션에서
도커와 AWS를 적용하는 미션을 적용했는데

도커가 처음이라 너무 어렵고, 생각만큼 쉽게 해결되지 않았다.
암튼 성공했으니 그 과정과 겪은 문제점과 해결법을 정리해보려 한다.

환경

  • Spring Boot 3.4
  • PostgreSQL 14
  • 배포하려는 프로젝트 코드: 깃허브

목표

  • Dockerfile 작성
    • Dockerfile에서 jar 빌드, 이미지 빌드
  • Docker Compose 구성
    • 애플리케이션 컨테이너 빌드
    • PostgreSQL 포함
    • 환경변수 설정
    • 애플리케이션 볼륨: 파일 업로드 디렉토리
    • PostgreSQL 볼륨

Dockerfile

Dockerfile로 jar 빌드 중 에러

  • Unsupported class file major version 65 예외

https://syleeblog.tistory.com/35

이 글을 참고하면 된다.
이때는 별 에러 없이 끝날 거라 생각한 초반이라
트러블슈팅 글 하나 적으면 끝날 줄 알았는데..

jar 빌드 중 통합 테스트 실패

나는 src/test에서 단위 테스트, 슬라이스 테스트, 통합 테스트를 했었다.

프로젝트 구조는 아래와 같다.
내가 Run을 했을 때 테스트는 모두 통과했다.
또한 직접 ./graldew build를 했을 때도 문제 없었다.

그런데 Dockerifle을 통해 jar 빌드 중 통합 테스트가 전부 실패하며
테스트가 실패하여 빌드를 할 수 없다는 에러를 만났다.

이 경우, 아직 정확한 원인을 모르겠지만
통합 테스트 클래스들의 위치가 문제였다.

integratinoTests 패키지 안에 있던 통합 테스트 클래스들을
가장 위로 빼냈다.
그랬더니 Dockerfile을 통한 빌드가 됐다.

@SpringBootTest 어노테이션이 달린 클래스는 하위 패키지에 있으면 안 되는 걸까?
이 부분은 공부가 더 필요할 것 같다.


아래가 내 최종 Dockerfile이다.
docker build -t discodeit:local로 이미지 이름과 태그를 지정한 후 빌드하면 내가 따로 ./gradlew build하지 않아도
프로젝트 파일을 컨테이너로 복사하여 빌드하고, 이미지를 빌드한다.

FROM gradle:8.12.1-jdk17 AS builder
WORKDIR /app
COPY . /app
RUN gradle build --no-daemon 

FROM amazoncorretto:17
WORKDIR /app
EXPOSE 80
ENV PROJECT_NAME=discodeit
ENV PROJECT_VERSION=1.2-M8
ENV JVM_OPTS=""
COPY --from=builder /app/build/libs/${PROJECT_NAME}-${PROJECT_VERSION}.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

Docker-compose

환경변수 .env 적용 중 에러

  • 연결에 대한 설정이 pg_hba.conf 파일에 없습니다.

https://syleeblog.tistory.com/36

이 글에 정리하였다.

Connection to localhost:5432 refused

어떻게든 이미지 빌드와 컨테이너 실행에 성공하였으나
도커 컨테이너에서 PostgreSQL DB에 연결하는 게 불가능하다는 로그가 떴다.

여기서 localhost가 문제되는 부분이다.
나는 내 호스트의 PostgreSQL을 쓰도록 의도했기에 localhost가 문제될 것이라 생각하지 않았다.(컨테이너를 로컬 DB에 연결)

그러나 로그에서 말하는 localhost는 컨테이너 내부의 로컬을 의미한다.
즉, 컨테이너 입장에서 localhost:5432는 컨테이너 자기 자신의 5432 포트를 보는 거지, 호스트 머신의 DB가 아니다.

현재 도커 내에서 5432 포트로 돌아가는 PostgreSQL 컨테이너가 없어서 난 오류 같다.

Docker 컨테이너 안에서 호스트 머신의 DB에 접근하려면
yaml에서 localhost 대신 host.docker.internal을 사용하면 된다.

spring:
  datasource:
    url: jdbc:postgresql://host.docker.internal:5432/${DB_NAME}

볼륨: 파일 업로드 디렉토리

이 서비스에서는
사용자들의 프로필 사진, 또는 메시지 첨부 파일을
파일 시스템에 binary 형태로 저장하고 있다.

그 위치는 프로젝트 루트 위치의 localStorage이다.

도커 컨테이너에 올릴 때 이 디렉토리를 영속화하고자 볼륨을 사용하고 싶었다.

다른 컨테이너를 볼륨으로 올리는 게 아니라 나처럼 특정 디렉토리만 볼륨으로 올리려는 글이 별로 없어 고민했는데 생각보다 아주 간단했다.

localStorage 디렉토리는 애플리케이션 내에 있으므로
애플리케이션에서 volumes를 설정하기만 하면 된다.
{로컬에서의 경로}:{도커에서의 경로}

서버에서 /app/binaryContentStorage 경로에 파일을 저장하면
로컬 프로젝트 디렉토리의 ./localStorage에 파일이 저장된다.
서버를 재시작하거나 컨테이너를 새로 띄워도 파일이 계속 유지된다.

version: '3'
services:
  app:
    build: .
    image: discodeit:local
    # ✅ 이 부분!!!
    volumes:
      - ./localStorage:/app/binaryContentStorage
    environment:
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_NAME: ${DB_NAME}
    ports:
      - 8081:8080

이때 코드에서 경로 설정에 주의해야 한다.
서버에서 /app/binaryContentStorage에 저장할거라고 했으니
코드에서도 파일 저장 경로를 /app/binaryContentStorage로 수정한다.

public class LocalBinaryContentStorage implements BinaryContentStorage {

  private Path root = Paths.get("/app/binaryContentStorage");
  
  ...
  
}

PostgreSQL 볼륨

version: '3'
services:
  app:
  	(생략)
  db:
    image: postgres:14
    ports:
      - 5432:5432
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgresql-data:/var/lib/postgresql/data

volumes:
  postgresql-data:

이때 환경변수들은 .env 파일에 저장하였다.
.env 파일에서 환경변수를 작성할 때 띄어쓰기에 주의하자.
DB_USERNAME=discodeit_user처럼 = 전후로 띄어쓰기가 없어야 한다.

PostgreSQL 서비스 실행 후 schema.sql 자동 실행

volumes에 docker-entrypoint-initdb.d 를 사용한다.

{로컬 프로젝트의 schema.sql 위치}:{/docker-entrypoint-initdb.d/schema.sql}

    volumes:
      - postgresql-data:/var/lib/postgresql/data
      - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/schema.sql

Unable to determine Dialect without JDBC metadata

컨테이너를 실행했는데
JPA 설정이 누락됐는지 DB 연결이 불가능했다.

잘못된 docker-compose

version: '3'
services:
  app:
    build: .
    image: discodeit:local
    volumes:
      - ./localStorage:/app/binaryContentStorage
    ports:
      - 8081:8080
    depends_on:
      - db
  db:
    image: postgres:14
    ports:
      - 5432:5432
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgresql-data:/var/lib/postgresql/data
      - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/schema.sql

volumes:
  postgresql-data:

올바른 docker-compose

version: '3'
services:
  app:
    build: .
    image: discodeit:local
    volumes:
      - ./localStorage:/app/binaryContentStorage
    ✅ 이 부분!
    environment:
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_NAME: ${DB_NAME}
    ports:
      - 8081:8080
    depends_on:
      - db
  db:
    image: postgres:14
    ports:
      - 5432:5432
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgresql-data:/var/lib/postgresql/data
      - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/schema.sql

volumes:
  postgresql-data:

PostgreSQL에만 db명, user, password를 설정했었는데
애플리케이션에서도 yaml에 DB 정보를 써야 하니 환경변수 설정이 필요하다.

이렇게 최종 완성된 나의 docker-compose.yml 파일 코드는 아래와 같다.

version: '3'
services:
  app:
    build: .
    image: discodeit:local
    volumes:
      - ./localStorage:/app/binaryContentStorage
    environment:
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_NAME: ${DB_NAME}
    ports:
      - 8081:8080
    depends_on:
      - db
  db:
    image: postgres:14
    ports:
      - 5432:5432
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgresql-data:/var/lib/postgresql/data
      - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/schema.sql

volumes:
  postgresql-data:

추가: 컨테이너 실행 후 enum 타입 문제

로컬 개발 환경에서는 enum 타입과 관련해 SQL 에러가 발생한 적이 없는데

enum 타입이 컬럼으로 있는 데이터를 insert 시도하니까 에러가 발생했다.

2025-04-09 09:54:35.643 UTC [72] ERROR:  column "type" is of type channel_type but expression is of type character varying at character 88
2025-04-09 18:54:35 db-1   | 2025-04-09 09:54:35.643 UTC [72] HINT:  You will need to rewrite or cast the expression.
2025-04-09 18:54:35 db-1   | 2025-04-09 09:54:35.643 UTC [72] STATEMENT:  insert into channels (created_at,description,name,type,updated_at,id) values ($1,$2,$3,$4,$5,$6)

로컬 개발 환경에서는 enum 문제가 없는데
배포 환경에서 문제가 되는 경험을 저번에 해보아서
그렇게 어렵지 않게 해결했다.

엔티티 코드에서 두 개의 어노테이션을 단다.
@Enumerated(EnumType.STRING)@JdbcTypeCode(SqlTypes.NAMED_ENUM)을 단다.

조금 더 마음 편하게 가보자면 DB에서는 그냥 enum 안 쓰고 VARCHAR로 쓰는 게 나을 수도..


@Entity
@Table(name = "channels")
@NoArgsConstructor
@Getter
public class Channel extends BaseUpdatableEntity {

  String name;
  String description;
  💡 이 부분!
  @Enumerated(EnumType.STRING)
  @JdbcTypeCode(SqlTypes.NAMED_ENUM)
  ChannelType type;
profile
블로그 이전: https://syleeblog.tistory.com/

0개의 댓글