프로젝트 자체를 이미지로 구워버리면 다른 사람이 내려받았을 때 JDK 설치나 DB 설정 없이 바로 실행할 수 있다.
프로젝트를 맨 처음 생성하고 이 프로젝트 자체를 도커 이미지로 굽는 단계를 기록하겠다.
IntelliJ에서 프로젝트 생성


추가한 라이브러리들
- 우선 공부할 거리를 미리 쟁여둔다는 마음으로 추가했다.
카테고리 라이브러리 명칭 용도 기본/생산성 lombok, devtools, configuration processor 코드 다이어트용, 자동 재시작, 설정 파일 편의성 핵심 웹 spring web, thymeleaf, validation, openAPI API 개발, 화면 렌더링, 입력값 검증, 스웨거 문서화 데이터/인프라 jpa, mysql driver, redis, kafka DB 관리, 캐싱, 실시간 메시지 처리 인증/보안 spring security, oauth2 client 로그인, 카카오 소셜 로그인 구현 특수 기능 websocket, spring batch 실시간 채팅/알림, 대용량 정산 작업 등 운영/테스트 docker compose, testcontainers 도커 연동, 격리된 환경에서의 TDD
프로젝트를 생성했다면,
/src/main/resources/application.properties 파일을 삭제한 후 동일 위치에 application.yml 파일을 생성한다.

spring:
application:
name: honeydduk-api
# Docker Compose Support 설정
docker:
compose:
enabled: true
file: compose.yaml
# Database 설정 (도커 컨테이너 이름인 'db'로 연결)
datasource:
url: jdbc:mysql://db:3306/honeydduk?serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
use_sql_comments: true # SQL 로그에 반인딩 된 파라미터 값(실제 값) 보여줌
default_batch_fetch_size: 100
order_inserts: true
order_updates: true
# Redis 설정
data:
redis:
host: redis
port: 6379
# Kafka 설정
kafka:
bootstrap-servers: kafka:29092
# 배치를 위한 설정 (메타데이터 테이블 자동 생성)
batch:
jdbc:
initialize-schema: always
job:
enabled: false # 서버 켤 때 배치가 바로 실행되지 않게 방지
jackson:
serialization:
fail-on-empty-beans: false
default-property-inclusion: non_null # null인 필드는 응답에서 아예 제외
logging:
level:
org.hibernate.orm.jdbc.bind: trace # 쿼리에 들어가는 파라미터 값을 로그로 찍어줌
* 현재는 로컬 개발 단계라 환경변수를 compose.yaml, application.yml에 환경변수들을 적어놨지만 나중에 개발하면서 env 파일에 모아 따로 관리할 것이다.
그 다음, 파일 최상단에 dockerfile을 생성한다.

# 빌드 스테이지
FROM eclipse-temurin:21-jdk-jammy AS build
WORKDIR /app
# 빌드에 필요한 파일들만 먼저 복사
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src
# 실행 권한 부여 및 빌드
RUN chmod +x ./gradlew
RUN ./gradlew clean bootJar -x test
# 실행 스테이지
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
# jar 파일만 빼서 가져오기
COPY --from=build /app/build/libs/*.jar app.jar
# 컨테이너가 켜질 때 실행할 명령어
ENTRYPOINT ["java", "-jar", "app.jar"]
이후 파일 최상단에 compose.yaml 파일 내용을 아래와 같이 수정한다.
나의 앱 서비스가 다른 서비스(DB, Redis 등)에 의존하도록 설정하는 것이 핵심이다.

services:
# 1. Spring Boot App
app:
build: .
container_name: honeydduk-api-server
ports:
- "8080:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/honeydduk?serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&useSSL=false
- SPRING_DATA_REDIS_HOST=redis
- SPRING_KAFKA_BOOTSTRAP_SERVERS=kafka:29092
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
kafka:
condition: service_started
# 2. Database (MySQL 8.4 LTS)
db:
image: mysql:8.4
platform: linux/amd64
container_name: honeydduk-db
ports:
- "3307:3306"
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=honeydduk
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- ./data/mysql:/var/lib/mysql
# 3. Redis (7.4 안정 버전 고정)
redis:
image: redis:7.4-alpine
container_name: honeydduk-redis
ports:
- "6379:6379"
# 4. Kafka (7.6.1 KRaft 안정 버전 고정)
kafka:
image: confluentinc/cp-kafka:7.6.1
container_name: honeydduk-kafka
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: 'broker,controller'
KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka:29093'
KAFKA_LISTENERS: 'PLAINTEXT://kafka:29092,CONTROLLER://kafka:29093,PLAINTEXT_HOST://0.0.0.0:9092'
KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT'
KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT'
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk'
restart: always
도커 인프라 세팅
우선 도커 데스크탑을 실행 후 인텔리제이 터미널에서 아래 명령어를 실행한다.
docker compose up --build -d

(base) soheepark@kkultteog honeydduk-API % docker compose up --build -d
[+] up 43/43
✔ Image mysql:8.4 Pulled 46.4s
✔ Image confluentinc/cp-kafka:latest Pulled 28.4s
✔ Image confluentinc/cp-zookeeper:latest Pulled 59.0s
[+] Building 93.8s (21/21) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 542B 0.0s
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 594B 0.0s
=> [internal] load metadata for docker.io/library/eclipse-temurin:21-jdk-jammy 2.7s
=> [internal] load metadata for docker.io/library/eclipse-temurin:21-jre-jammy 2.7s
=> [auth] library/eclipse-temurin:pull token for registry-1.docker.io 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [build 1/9] FROM docker.io/library/eclipse-temurin:21-jdk-jammy@sha256:9119073a0b783fd380fbf4b131a40955525c8e2a66083681c87fb15a39ae01d0 17.2s
=> => resolve docker.io/library/eclipse-temurin:21-jdk-jammy@sha256:9119073a0b783fd380fbf4b131a40955525c8e2a66083681c87fb15a39ae01d0 0.0s
=> => sha256:9119073a0b783fd380fbf4b131a40955525c8e2a66083681c87fb15a39ae01d0 5.25kB / 5.25kB 0.0s
=> => sha256:86cbb2dbf3b68d3c30280722d5597afc845af596fe7f6398db56e5c9f9e0bc4e 1.94kB / 1.94kB 0.0s
=> => sha256:b3aadbf953f8337a9a81487d7a8a93b72853d9e50781b984c57c7552c8778c7a 5.94kB / 5.94kB 0.0s
=> => sha256:b1cba2e842ca52b95817f958faf99734080c78e92e43ce609cde9244867b49ed 29.54MB / 29.54MB 2.3s
=> => sha256:1dde4b555a697f85138e99e7759480373b71f47cbad9f7c0fa6cba34f2f5fe1e 20.69MB / 20.69MB 4.4s
=> => extracting sha256:b1cba2e842ca52b95817f958faf99734080c78e92e43ce609cde9244867b49ed 3.9s
=> => sha256:2049ec1cef96e43c3946f1be57d5efb77052e16d9e7f1fa3d7c8e4030155eac7 158B / 158B 3.3s
=> => sha256:878e917e8d81357d9636aab51478d4fe889d01e89988159f87e55dbc3bba337b 157.87MB / 157.87MB 7.1s
=> => sha256:9760149be10a5530ec0649fba4393ef8c2a058a9a97978c7cf43287b890dfcc0 2.28kB / 2.28kB 3.8s
=> => extracting sha256:1dde4b555a697f85138e99e7759480373b71f47cbad9f7c0fa6cba34f2f5fe1e 3.4s
=> => extracting sha256:878e917e8d81357d9636aab51478d4fe889d01e89988159f87e55dbc3bba337b 6.2s
=> => extracting sha256:2049ec1cef96e43c3946f1be57d5efb77052e16d9e7f1fa3d7c8e4030155eac7 0.0s
=> => extracting sha256:9760149be10a5530ec0649fba4393ef8c2a058a9a97978c7cf43287b890dfcc0 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 62.72kB 0.0s
=> [stage-1 1/3] FROM docker.io/library/eclipse-temurin:21-jre-jammy@sha256:fe1e0a543b295a1c9b56fd34dcbe8fcab35389f7456eae6f3023fa085cad5af2 13.5s
=> => resolve docker.io/library/eclipse-temurin:21-jre-jammy@sha256:fe1e0a543b295a1c9b56fd34dcbe8fcab35389f7456eae6f3023fa085cad5af2 0.0s
=> => sha256:6d575991a97516fe461d4e4036a37490052a3d42ec380135d014249e16b1d678 16.15MB / 16.15MB 2.3s
=> => sha256:7f70e2ff0d31ec24265820e65fb54d456b519fdb231448a0215d65703962a577 52.99MB / 52.99MB 2.3s
=> => sha256:fe1e0a543b295a1c9b56fd34dcbe8fcab35389f7456eae6f3023fa085cad5af2 5.25kB / 5.25kB 0.0s
=> => sha256:517f85ad038f8998f5f8a829cd0f441e3929ebbfccd6b2bf1b405db0d66c8de7 1.94kB / 1.94kB 0.0s
=> => sha256:a515031533400222c0474bb564b168f4255f4b69439d72f2249b42a98f5afcc0 5.55kB / 5.55kB 0.0s
=> => sha256:b1cba2e842ca52b95817f958faf99734080c78e92e43ce609cde9244867b49ed 29.54MB / 29.54MB 2.3s
=> => sha256:219ba00cee2ad0168cf5988afb99d1aebb4e9016bee9e9391836ea3460fbd969 160B / 160B 2.8s
=> => sha256:9d8f3670b78ac53ea39b08a6d287c35de5e8b733eb3f29916247010553254e8d 2.28kB / 2.28kB 2.8s
=> => extracting sha256:b1cba2e842ca52b95817f958faf99734080c78e92e43ce609cde9244867b49ed 3.9s
=> => extracting sha256:6d575991a97516fe461d4e4036a37490052a3d42ec380135d014249e16b1d678 3.1s
=> => extracting sha256:7f70e2ff0d31ec24265820e65fb54d456b519fdb231448a0215d65703962a577 2.8s
=> => extracting sha256:219ba00cee2ad0168cf5988afb99d1aebb4e9016bee9e9391836ea3460fbd969 0.0s
=> => extracting sha256:9d8f3670b78ac53ea39b08a6d287c35de5e8b733eb3f29916247010553254e8d 0.0s
=> [stage-1 2/3] WORKDIR /app 2.3s
=> [build 2/9] WORKDIR /app 0.1s
=> [build 3/9] COPY gradlew . 0.0s
=> [build 4/9] COPY gradle gradle 0.0s
=> [build 5/9] COPY build.gradle . 0.0s
=> [build 6/9] COPY settings.gradle . 0.0s
=> [build 7/9] COPY src src 0.0s
=> [build 8/9] RUN chmod +x ./gradlew 0.3s
=> [build 9/9] RUN ./gradlew clean bootJar -x test 71.3s
=> [stage-1 3/3] COPY --from=build /app/build/libs/*.jar app.jar 0.3s
=> exporting to image 0.3s
[+] up 50/50ting layers 0.3s
✔ Image mysql:8.4 Pulled 46.4s
✔ Image confluentinc/cp-kafka:latest Pulled 28.4s
✔ Image confluentinc/cp-zookeeper:latest Pulled 59.0s
✔ Image honeydduk-api-app Built 94.1s
✔ Network honeydduk-api_default Created 0.1s
✔ Container honeydduk-api-zookeeper-1 Created 0.2s
✔ Container honeydduk-redis Created 0.1s
... 3 more
(base) soheepark@kkultteog honeydduk-API %
모든 컨테이너가 살아있는지 확인해보자.
docker ps

(base) soheepark@kkultteog honeydduk-API % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
13c3428223d0 honeydduk-api-app "java -jar app.jar" 4 minutes ago Up 4 minutes 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp honeydduk-api-server
bdcb8a3e9a09 mysql:8.4 "docker-entrypoint.s…" 4 minutes ago Up 4 minutes (healthy) 0.0.0.0:3307->3306/tcp, [::]:3307->3306/tcp honeydduk-db
11acaa7165c2 redis:latest "docker-entrypoint.s…" 4 minutes ago Up 4 minutes 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp honeydduk-redis
97dfcf5a7cb1 confluentinc/cp-zookeeper:latest "/etc/confluent/dock…" 4 minutes ago Up 4 minutes 2181/tcp, 2888/tcp, 3888/tcp honeydduk-api-zookeeper-1
honeydduk-db가 healthy 상태라서 정상으로 뜬 것 같다.
근데 리스트를 보니 kafka가 안보여서 처음에 이미지 받으면서 꼬였나보다.
그래서 compose.yaml에서 주키퍼가 살아있는지 체크하는 소스를 healthcheck를 통해 보강하였다.
compose.yaml을 수정하고 한 번 싹 밀고 다시 띄웠다.
# 다 내리기
docker compose down
# 다시 띄우기
docker compose up -d

이제 다시 상태를 체크해보자.
(base) soheepark@kkultteog honeydduk-API % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dc58298a33b3 honeydduk-api-app "java -jar app.jar" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp honeydduk-api-server
c9b940eb1544 mysql:8.4 "docker-entrypoint.s…" About a minute ago Up About a minute (healthy) 0.0.0.0:3307->3306/tcp, [::]:3307->3306/tcp honeydduk-db
3e71c624acb2 confluentinc/cp-zookeeper:latest "/etc/confluent/dock…" About a minute ago Up About a minute (unhealthy) 2181/tcp, 2888/tcp, 3888/tcp honeydduk-api-zookeeper-1
f8d84217e7f0 redis:latest "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp honeydduk-redis
zookeeper 상태에 unhealthy가 떠 있어서 카프카가 아예 실행조차 못 하고 대기 중이었던 것.
근데 kafka가 자꾸 실행도 안되고 죽어서 docker logs를 이용해서 원인을 발견했다.
docker logs honeydduk-kafka
로그에서 아래와 같이 결정적인 메시지가 나왔다.
Error: environment variable "KAFKA_PROCESS_ROLES" is not set
error in executing the command: environment variable
"KAFKA_PROCESS_ROLES" is not set
여기서 최근 confluentinc/cp-kafka:latest 이미지는 주키퍼 없이 돌아가는 kraft 모드를 기본으로 지원하고 있어 문제가 되었던 것이다.
주키퍼 기반의 기존 설정을 그대로 쓰면서 이미지 버전을 최신 버전으로 쓰면, 카프카는 kraft 설정 내놓으라고 에러를 뱉고 죽어버리는데, 그것이 원인이었던 것이다.
그래서 주키퍼를 지우고 kraft 모드로 전환했다.
1. compose.yaml에서 zookeeper 서비스 삭제.
2. kafka 서비스에 kRaft 필수 환경 변수(KAFKA_PROCESS_ROLES, KAFKA_NODE_ID, CLUSTER_ID 등) 추가.
3. KAFKA_ADVERTISED_LISTENERS를 통해 내부용(29092)과 외부용(9092) 포트 분리.
docker logs를 통해 컨테이너가 죽는 진짜 이유를 직접 확인하는게 정말 중요한 것 같다.
latest는편리하지만, 메이저 업데이트가 일어나면 기존 설정과 충돌할 수 있는 것 같다.
주키퍼를 없애면서 로컬 환경의 메모리도 아끼고 구성도 훨씬 깔끔해졌다.
그래서 latest를 소스에서 모두 지웠다.
yaml 소스를 변경해서 기존 컨테이너와 네트워크를 청소했다.
docker compose down --remove-orphans
이후 latest가 아니라 고정된 버전 이미지를 새로 다운받는다.
docker compose up -d
띄우고 나서 문제없는지 확인한다.
docker ps
(base) soheepark@kkultteog honeydduk-API % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4ba7abc85c01 honeydduk-api-app "java -jar app.jar" 15 seconds ago Up 3 seconds 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp honeydduk-api-server
0dd3211289c0 confluentinc/cp-kafka:7.6.1 "/etc/confluent/dock…" 16 seconds ago Up 14 seconds 0.0.0.0:9092->9092/tcp, [::]:9092->9092/tcp honeydduk-kafka
61aea4d1a55a mysql:8.4 "docker-entrypoint.s…" 16 seconds ago Up 14 seconds (healthy) 0.0.0.0:3307->3306/tcp, [::]:3307->3306/tcp honeydduk-db
43f8b67b8a7a redis:7.4-alpine "docker-entrypoint.s…" 16 seconds ago Up 14 seconds 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp honeydduk-redis
(base) soheepark@kkultteog honeydduk-API %
모두 up 잘 떴으니 마무리 !
도커 인프라 구축 완료
도커 컴포즈로 MySQL, Redis, Kafka(KRaft) 컨테이너 구성을 완료하였다.

'[Project] > 사이드 프로젝트' 카테고리의 다른 글
| [꿀떡볶이 프로젝트] 요구사항 분석 및 도메인 모델링 설계 (0) | 2026.03.18 |
|---|---|
| [꿀떡볶이 프로젝트] 안녕 꿀떡볶이! & 스프링 시큐리티 접근제어 해제 (0) | 2026.03.17 |
| [꿀떡볶이 프로젝트] 사이드 웹프로젝트 기술 스택 선정 (0) | 2026.03.16 |
| [꿀떡볶이 프로젝트] docker로 개발 환경 구축하기 (0) | 2026.03.12 |
| [Mac/intellij] 사용하지 않는 import문 자동 삭제 해제 (0) | 2023.12.12 |