코드잇 스프린트에서 진행한 미션에서
도커와 AWS를 적용하는 미션을 적용했는데
도커가 처음이라 너무 어렵고, 생각만큼 쉽게 해결되지 않았다.
암튼 성공했으니 그 과정과 겪은 문제점과 해결법을 정리해보려 한다.
https://syleeblog.tistory.com/35
이 글을 참고하면 된다.
이때는 별 에러 없이 끝날 거라 생각한 초반이라
트러블슈팅 글 하나 적으면 끝날 줄 알았는데..
나는 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"]
https://syleeblog.tistory.com/36
이 글에 정리하였다.
어떻게든 이미지 빌드와 컨테이너 실행에 성공하였으나
도커 컨테이너에서 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");
...
}
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처럼 = 전후로 띄어쓰기가 없어야 한다.
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
컨테이너를 실행했는데
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 타입과 관련해 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;