
docker-compose를 통해서 Dockerfile 하나보다 여러 컨테이너들을 묶어서 편하게 관리하기가 아주 좋습니다.
이제 docker-compose를 위한 실습을 해봅시다.
UserEntity를 가지고 CURD를 하는 SpringBoot RESTAPI 프로젝트를 만들어보았습니다. 이 프로젝트는 MySql DB와 연동을 해야 하는데 docker-compose.yml 파일로 쉽게 연동할 수 있음을 실습해보겠습니다.
Mysql 사용시 로컬에서 port 3306를 중지시키고 docker-compose 실습을 하기 바랍니다. 이것때문에 port를 docker-compose에서 아무리 바꿔도 바뀌지 않습니다!...
심신건강을 위해 3306를 꺼두고 진행해주세요! => 작업관리자에서 꺼주시길!
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// mysql
runtimeOnly 'com.mysql:mysql-connector-j'
// test에서 lombok 사용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
// dotenv
implementation 'io.github.cdimascio:java-dotenv:5.1.1'
}
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDto {
private String username;
private String password;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserResult {
private Long id;
private String username;
private String password;
public UserResult(UserEntity users) {
this.id = users.getId();
this.username = users.getUsername();
this.password = users.getPassword();
}
}
public interface UserRepository extends JpaRepository<UserEntity, Long> {
}
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
public UserEntity create(UserDto userDto) {
UserEntity userEntity = UserEntity.builder()
.username(userDto.getUsername())
.password(userDto.getPassword())
.build();
return userRepository.save(userEntity);
}
public UserEntity readOne(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}
UserController
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class UserController {
private final UserService userService;
@PostMapping
public ResponseEntity create(@RequestBody UserDto userDto) {
log.info("create Users");
UserEntity users = userService.create(userDto);
return ResponseEntity.ok(new UserResult(users));
}
@GetMapping("{id}")
public ResponseEntity getUser(@PathVariable Long id) {
log.info("readOne");
UserEntity users = userService.readOne(id);
return ResponseEntity.ok(new UserResult(users));
}
}
DB ORM은 JPA를 사용합니다.
application.yml 파일에서 JPA 연동 설정을 해야합니다.
database 설정은 docker-compose.yml에서 설정해줄 거기 때문에 application.yml에서 뺐습니다.
✅ application.yml
⭐ applciation 파일에 localhost -> DOCKER_HOST(=host.docker.internal) 로 다 대체해야 합니다!
spring:
# 주석을 해도 안해도 상관x, 설사 틀린내용이라도. 우선순위가 docker-compose내용으로 하기 때문
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${DOCKER_HOST}:3307/${MYSQL_DATABASE}?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8&allowPublicKeyRetrieval=true
username: ${MYSQL_USER}
password: ${MYSQL_PASSWORD}
jpa:
open-in-view: false # controller 에서 지연로딩을 사용할 때 false로 설정
hibernate:
ddl-auto: update # 테이블 생성 및 업데이트 전략 (create, create-drop, update, validate, none)
properties:
hibernate:
format_sql: true # SQL 포맷팅
highlight_sql: true # 하이라이트 SQL 출력
use_sql_comments: true # 실제 JPQL SQL 주석 사용
build.gradle
⭐ jar 파일을 하나로 만들기 반드시 필요한 설정!
jar {
enabled = false
}
#DB
LOCAL_HOST=localhost
# ⭐ Docker 컨테이너 내부의 데이터베이스 hostname 설정
DOCKER_HOST=host.docker.internal
# Mysql
MYSQL_ROOT_PASSWORD=1234
MYSQL_DATABASE=post
MYSQL_USER=post
MYSQL_PASSWORD=post
@Slf4j
@Configuration
public class DotEnvConfig {
@Bean
public Dotenv dotenv() {
log.info(".env file loading...");
// .env 파일을 읽어서 환경변수로 사용
return Dotenv.configure().directory("./")
.ignoreIfMissing() // .env 파일이 없어도 에러 발생 안함
.load();
}
}
자 이제 Dockerfile과 docker-compse.yml을 보겠습니다.
이 둘은 꼭 같은 경로에 있어야 합니다!
꼭 ./gradlew clean build로 jar를 새로 생성하고 실행해야 오류가 안납니다.
Dockerfile
FROM openjdk:17-jdk
ARG JAR_FILE=build/libs/*SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
✅ gradlew clean build 처리 까지한 Dockerfile
위에 있는 걸로 하면 docker 실행시, 항상 gradlew build 작업을 수동으로 해줘야 합니다!
⭐ 아래 gradlew build 처리까지 처리하는 Dockerfile을 추천합니다!
Dockerfile
# 1단계: 빌드
FROM eclipse-temurin:17-jdk-alpine AS build
WORKDIR /app
COPY . .
RUN chmod +x ./gradlew
RUN ./gradlew clean build -x test
# 2단계: 실행
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
docker-compse-dev.yml
version: "3.9"
services:
mysql_db:
image: mysql:8.0
container_name: deploy-db
env_file:
- .env
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Seoul
ports:
- "3306:3306"
# volumes:
# - mysql_data:/var/lib/mysql
# - ./mysql/initdb:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER} -p${MYSQL_PASSWORD} || exit 1"]
interval: 10s
timeout: 5s
retries: 5
networks:
- spring_mysql_api
spring_app:
build:
context: .
dockerfile: Dockerfile
container_name: spring_app
env_file:
- .env
environment:
SPRING_PROFILES_ACTIVE: dev
SPRING_DATASOURCE_URL: jdbc:mysql://mysql_db:3306/${MYSQL_DATABASE}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
SPRING_DATASOURCE_USERNAME: ${MYSQL_USER}
SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD}
ports:
- "8080:8080"
depends_on:
mysql_db:
condition: service_healthy
restart: unless-stopped
networks:
- spring_mysql_api
#volumes:
# mysql_data:
networks:
spring_mysql_api:
driver: bridge
이 상태에서 docker-compose 파일을 실행해보겠습니다.
docker-compose up
⭐ Dockerfile을 새로 build하고 싶다면 빌드 옵션을 사용하면 됩니다.(docker-compose 내 build 작업이 있다면 반드시 아래 명령어로 실행해주셔야 합니다!)
docker-compose up -d --build
만약 다른 경로에 있거나 다른 파일명을 사용하고 싶을 경우 -f 옵션으로 docker-compose 파일을 지정해주면 됩니다.
docker-compose -f [다른 docker-compose.yml] up
# 예시
docker-compose -f docker-compose.v3.yml up
-d 옵션을 주면 백그라운드로 실행시킬 수 있습니다.
docker-compose -d up
만약 docker-compose 실행을 닫고 싶다면
docker-compose down
이 명령어를 치면 됩니다.
log 명령어를 통해 올라간 springboot 프로젝트가 제대로 실행되는지 확인할 수 있습니다.
docker-compose logs

제대로 실행되는지
postman 으로 api를 보내는 테스트를 진행해봅시다.
post : localhost:8080/api
get : localhost:8080/api/{id}
네, 둘다 제대로 진행되는지 확인했습니다.


3307로 변경했더라도, 로컬에서 실행 중인 MySQL 서비스의 3306 포트가 충돌을 일으킬 수 있습니다.| 항목 | 내용 | 중요도 |
|---|---|---|
| JAR 파일 빌드 | Docker 실행 전에 꼭 ./gradlew clean build 명령을 실행하여 최신 JAR 파일을 생성해야 합니다. | 높음 |
| 파일 경로 | Dockerfile과 docker-compose.yml 파일은 반드시 같은 경로에 위치해야 합니다. | 높음 |
build.gradle | JAR 파일을 생성해야 Dockerfile에서 복사할 수 있습니다. jar { enabled = false } 설정이 있다면 제거하거나 enabled = true로 변경해야 합니다. | 중요 |
| 파일 | 설정 항목 | 주의점 및 핵심 내용 |
|---|---|---|
.env | PROD_HOST | 컨테이너 간 통신 주소: Spring Boot 컨테이너가 MySQL 컨테이너에 접근하려면, localhost 대신 **host.docker.internal**로 설정해야 합니다. (예: PROD_HOST=host.docker.internal) |
application.yml | DB URL | .env 파일의 변수를 참조하도록 **${PROD_HOST}**와 같은 환경 변수 Placeholder 문법을 사용해야 합니다. |
docker-compose.yml (DB) | env_file | .env 파일에 정의된 DB 계정 정보 등을 컨테이너 내부에 전달하기 위해 env_file: - .env 옵션을 명시해야 합니다. |
docker-compose.yml (App) | depends_on | Spring Boot 앱이 DB보다 먼저 실행되어 오류가 나는 것을 막기 위해, deploy-db 서비스가 condition: service_healthy 상태가 될 때까지 기다리도록 설정해야 합니다. |
docker-compose.yml (App) | extra_hosts | host.docker.internal 주소로 DB에 접근이 가능하도록 extra_hosts: ["host.docker.internal:host-gateway"] 설정을 추가해야 합니다. |
-d 옵션을 사용하여 컨테이너를 백그라운드에서 실행합니다.docker-compose up -dDockerfile을 수정했거나 이미지를 처음부터 다시 빌드하고 싶다면, 다음 --build 옵션을 사용해야 합니다.docker-compose up -d --builddocker-compose logs