Spring Boot Docker ๋ฐฐํฌ ๊ฐ€์ด๋“œ – Dockerfile๋ถ€ํ„ฐ docker-compose๊นŒ์ง€

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” Spring Boot Docker ๋ฐฐํฌ์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•˜๊ณ ์ž ํ•œ๋‹ค. ๋กœ์ปฌ์—์„œ ์ž˜ ๋™์ž‘ํ•˜๋˜ Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„œ๋ฒ„์— ์˜ฌ๋ฆด ๋•Œ ๊ฐ€์žฅ ๋งŽ์ด ์„ ํƒํ•˜๋Š” ๋ฐฉ์‹์ด Docker ์ปจํ…Œ์ด๋„ˆ ๋ฐฐํฌ๋‹ค. Dockerfile ์ž‘์„ฑ๋ถ€ํ„ฐ docker-compose๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํ•จ๊ป˜ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•๊นŒ์ง€, ์‹ค์ œ ๋™์ž‘ํ•˜๋Š” ์„ค์ • ํŒŒ์ผ์„ ์ค‘์‹ฌ์œผ๋กœ ์‚ดํŽด๋ณธ๋‹ค.


Spring Boot Docker ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

Spring Boot Docker ๋ฐฐํฌ์— ์•ž์„œ ๊ธฐ๋ณธ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•œ๋‹ค.

my-spring-app/
โ”œโ”€โ”€ src/
โ”œโ”€โ”€ build.gradle (๋˜๋Š” pom.xml)
โ”œโ”€โ”€ Dockerfile
โ”œโ”€โ”€ docker-compose.yml
โ”œโ”€โ”€ docker-compose.override.yml   # ๋กœ์ปฌ ๊ฐœ๋ฐœ์šฉ ์˜ค๋ฒ„๋ผ์ด๋“œ
โ””โ”€โ”€ .env                           # ํ™˜๊ฒฝ๋ณ€์ˆ˜ (git์— ํฌํ•จํ•˜์ง€ ์•Š์Œ)
Plaintext

Gradle ๊ธฐ์ค€ ์˜์กด์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

// build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“œ๋ผ์ด๋ฒ„ (์šด์˜: MySQL, ๋กœ์ปฌ: H2)
    runtimeOnly 'com.mysql:mysql-connector-j'
    runtimeOnly 'com.h2database:h2'

    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Groovy

Dockerfile ์ž‘์„ฑ

๊ธฐ๋ณธ Dockerfile

๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ํ˜•ํƒœ์˜ Dockerfile์ด๋‹ค. JAR ํŒŒ์ผ์„ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ ์‹คํ–‰ํ•œ๋‹ค.

# ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€: Eclipse Temurin JDK 21 (๊ณต์‹ OpenJDK ๋ฐฐํฌํŒ)
FROM eclipse-temurin:21-jre-jammy

# ์ปจํ…Œ์ด๋„ˆ ๋‚ด ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ ์„ค์ •
WORKDIR /app

# Gradle ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฌผ ๋ณต์‚ฌ (JAR ํŒŒ์ผ๋ช…์€ ํ”„๋กœ์ ํŠธ์— ๋”ฐ๋ผ ๋‹ค๋ฆ„)
COPY build/libs/*.jar app.jar

# ์ปจํ…Œ์ด๋„ˆ ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ํฌํŠธ ์„ ์–ธ (์‹ค์ œ ํฌํŠธ ๋งคํ•‘์€ docker run ๋˜๋Š” compose์—์„œ ์„ค์ •)
EXPOSE 8080

# ์ปจํ…Œ์ด๋„ˆ ์‹œ์ž‘ ์‹œ ์‹คํ–‰ํ•  ๋ช…๋ น์–ด
ENTRYPOINT ["java", "-jar", "app.jar"]
Dockerfile

์ด ๋ฐฉ์‹์€ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ปค์งˆ ์ˆ˜ ์žˆ๋‹ค. ์ „์ฒด JDK ๋Œ€์‹  JRE๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ๋งŒ์œผ๋กœ๋„ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ƒ๋‹นํžˆ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ Dockerfile

Spring Boot Docker ๋ฐฐํฌ์—์„œ ๊ถŒ์žฅํ•˜๋Š” ๋ฐฉ์‹์€ ๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋‹ค. ๋นŒ๋“œ ํ™˜๊ฒฝ๊ณผ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ๋ถ„๋ฆฌํ•ด์„œ ์ตœ์ข… ์ด๋ฏธ์ง€๋ฅผ ์ตœ์†Œํ™”ํ•œ๋‹ค.

# โ”€โ”€ 1๋‹จ๊ณ„: ๋นŒ๋“œ ์Šคํ…Œ์ด์ง€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Gradle ๋นŒ๋“œ์— ํ•„์š”ํ•œ JDK๊ฐ€ ํฌํ•จ๋œ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
FROM eclipse-temurin:21-jdk-jammy AS builder

WORKDIR /app

# ์˜์กด์„ฑ ์บ์‹ฑ: gradle wrapper์™€ ๋นŒ๋“œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋จผ์ € ๋ณต์‚ฌ
# ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ์˜์กด์„ฑ์ด ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฉด ์ด ๋ ˆ์ด์–ด๋Š” ์บ์‹œ ์žฌ์‚ฌ์šฉ
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .

# ์˜์กด์„ฑ ๋‹ค์šด๋กœ๋“œ๋งŒ ๋จผ์ € ์ˆ˜ํ–‰ (์บ์‹œ ๋ ˆ์ด์–ด ๋ถ„๋ฆฌ)
RUN ./gradlew dependencies --no-daemon

# ์‹ค์ œ ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ ํ›„ ๋นŒ๋“œ
COPY src src
RUN ./gradlew bootJar --no-daemon -x test

# โ”€โ”€ 2๋‹จ๊ณ„: ์‹คํ–‰ ์Šคํ…Œ์ด์ง€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ๋นŒ๋“œ ์‚ฐ์ถœ๋ฌผ๋งŒ ๊ฐ€์ ธ์˜ค๊ณ  JDK๋Š” ํฌํ•จํ•˜์ง€ ์•Š์Œ (์ด๋ฏธ์ง€ ํฌ๊ธฐ ๊ฐ์†Œ)
FROM eclipse-temurin:21-jre-jammy

WORKDIR /app

# ๋ณด์•ˆ์„ ์œ„ํ•ด root ๋Œ€์‹  ๋ณ„๋„ ์‚ฌ์šฉ์ž๋กœ ์‹คํ–‰
RUN addgroup --system spring && adduser --system --ingroup spring spring
USER spring:spring

# ๋นŒ๋“œ ์Šคํ…Œ์ด์ง€์—์„œ ์ƒ์„ฑ๋œ JAR๋งŒ ๋ณต์‚ฌ
COPY --from=builder /app/build/libs/*.jar app.jar

# Spring Boot Actuator health check ์„ค์ • (์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ ํ™•์ธ์šฉ)
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:8080/actuator/health || exit 1

EXPOSE 8080

# exec ํ˜•์‹ ์‚ฌ์šฉ: SIGTERM์„ Java ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ง์ ‘ ๋ฐ›์•„ graceful shutdown ์ฒ˜๋ฆฌ
ENTRYPOINT ["java", \
  "-XX:+UseContainerSupport", \
  "-XX:MaxRAMPercentage=75.0", \
  "-Djava.security.egd=file:/dev/./urandom", \
  "-jar", "app.jar"]
Dockerfile

-XX:+UseContainerSupport๋Š” JVM์ด ์ปจํ…Œ์ด๋„ˆ์˜ ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ์„ ์ธ์‹ํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ์˜ต์…˜์ด๋‹ค. Java 10 ์ดํ›„๋กœ๋Š” ๊ธฐ๋ณธ๊ฐ’์ด์ง€๋งŒ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•ด๋‘๋ฉด ์˜๋„๊ฐ€ ๋ช…ํ™•ํ•ด์ง„๋‹ค. -XX:MaxRAMPercentage=75.0์€ ์ปจํ…Œ์ด๋„ˆ์— ํ• ๋‹น๋œ ๋ฉ”๋ชจ๋ฆฌ์˜ 75%๋ฅผ JVM ํž™์œผ๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค.


Spring Boot Docker ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ application.yml ์„ค์ •

ํ™˜๊ฒฝ๋ณ„๋กœ ๋‹ค๋ฅธ ์„ค์ •์„ ์ฃผ์ž…๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก application.yml์„ ๊ตฌ์„ฑํ•œ๋‹ค.

# src/main/resources/application.yml
spring:
  application:
    name: my-spring-app

  datasource:
    # ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ฃผ์ž…๋ฐ›์Œ (Docker ํ™˜๊ฒฝ์—์„œ๋Š” ์ปจํ…Œ์ด๋„ˆ๋ช…์„ ํ˜ธ์ŠคํŠธ๋กœ ์‚ฌ์šฉ)
    url: ${DB_URL:jdbc:h2:mem:testdb}
    username: ${DB_USERNAME:sa}
    password: ${DB_PASSWORD:}
    driver-class-name: ${DB_DRIVER:org.h2.Driver}

  jpa:
    hibernate:
      ddl-auto: ${JPA_DDL_AUTO:create-drop}
    show-sql: false
    properties:
      hibernate:
        format_sql: true

server:
  port: 8080
  # Graceful shutdown: ์ฒ˜๋ฆฌ ์ค‘์ธ ์š”์ฒญ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ์ข…๋ฃŒ
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

# Spring Boot Actuator: ์ปจํ…Œ์ด๋„ˆ ํ—ฌ์Šค์ฒดํฌ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง์šฉ
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      show-details: when-authorized
YAML

${DB_URL:jdbc:h2:mem:testdb} ํŒจํ„ด์€ ํ™˜๊ฒฝ๋ณ€์ˆ˜ DB_URL์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ H2 ์ธ๋ฉ”๋ชจ๋ฆฌ DB๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์˜๋ฏธ๋‹ค. ๋กœ์ปฌ ๊ฐœ๋ฐœ ์‹œ์—๋Š” H2, Docker ํ™˜๊ฒฝ์—์„œ๋Š” MySQL๋กœ ์ž๋™ ์ „ํ™˜๋œ๋‹ค.


docker-compose.yml ์ž‘์„ฑ

Spring Boot Docker ๋ฐฐํฌ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ•จ๊ป˜ ๊ด€๋ฆฌํ•˜๋ ค๋ฉด docker-compose๊ฐ€ ํŽธ๋ฆฌํ•˜๋‹ค.

# docker-compose.yml
services:
  app:
    build:
      context: .          # Dockerfile์ด ์œ„์น˜ํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ
      dockerfile: Dockerfile
    container_name: spring-app
    ports:
      - "8080:8080"       # ํ˜ธ์ŠคํŠธ:์ปจํ…Œ์ด๋„ˆ ํฌํŠธ ๋งคํ•‘
    environment:
      # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •๊ฐ’์„ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ฃผ์ž…
      DB_URL: jdbc:mysql://db:3306/mydb?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8
      DB_USERNAME: ${MYSQL_USER}
      DB_PASSWORD: ${MYSQL_PASSWORD}
      DB_DRIVER: com.mysql.cj.jdbc.Driver
      JPA_DDL_AUTO: update
      # Spring ํ”„๋กœํŒŒ์ผ ํ™œ์„ฑํ™”
      SPRING_PROFILES_ACTIVE: prod
    depends_on:
      db:
        # db ์ปจํ…Œ์ด๋„ˆ๊ฐ€ healthy ์ƒํƒœ๊ฐ€ ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ (๋‹จ์ˆœ started ๋ณด๋‹ค ์•ˆ์ „)
        condition: service_healthy
    networks:
      - backend-network
    # JVM ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ (์ปจํ…Œ์ด๋„ˆ ๋ฉ”๋ชจ๋ฆฌ 1GB ํ• ๋‹น)
    deploy:
      resources:
        limits:
          memory: 1g

  db:
    image: mysql:8.0
    container_name: spring-db
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: mydb
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    ports:
      - "3306:3306"       # ๋กœ์ปฌ์—์„œ DB ์ง์ ‘ ์ ‘๊ทผ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋…ธ์ถœ
    volumes:
      # named volume์œผ๋กœ ๋ฐ์ดํ„ฐ ์˜์†์„ฑ ๋ณด์žฅ (์ปจํ…Œ์ด๋„ˆ ์žฌ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ ์œ ์ง€)
      - mysql-data:/var/lib/mysql
      # ์ดˆ๊ธฐํ™” SQL ์Šคํฌ๋ฆฝํŠธ ์ž๋™ ์‹คํ–‰
      - ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s   # MySQL ์ดˆ๊ธฐ ๊ธฐ๋™ ์‹œ๊ฐ„ ํ™•๋ณด
    networks:
      - backend-network

volumes:
  mysql-data:              # ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธ๋œ named volume

networks:
  backend-network:
    driver: bridge
YAML

depends_on์˜ condition: service_healthy๋Š” MySQL์ด ์‹ค์ œ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ›์„ ์ค€๋น„๊ฐ€ ๋์„ ๋•Œ Spring Boot๋ฅผ ๊ธฐ๋™์‹œํ‚จ๋‹ค. ๋‹จ์ˆœํžˆ depends_on: db๋งŒ ์“ฐ๋ฉด MySQL ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹œ์ž‘๋์ง€๋งŒ ์•„์ง ์ค€๋น„๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ Spring Boot๊ฐ€ ์ ‘์†์„ ์‹œ๋„ํ•˜๋‹ค ์‹คํŒจํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค.

.env ํŒŒ์ผ

๋ฏผ๊ฐํ•œ ์„ค์ •๊ฐ’์€ .env ํŒŒ์ผ์— ๋ถ„๋ฆฌํ•œ๋‹ค. ์ด ํŒŒ์ผ์€ .gitignore์— ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.
docker-compose.yml ํŒŒ์ผ์ด ์‹คํ–‰๋  ๋•Œ ํ•ด๋‹น ๋””๋ ‰ํ† ๋ฆฌ์˜ .env ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์ธ์‹ํ•œ๋‹ค.
.env ํŒŒ์ผ์— ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๊ฐ™์ด ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์™ธ๋ถ€ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์— ์ปค๋ฐ‹์„ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

# .env (git์— ํฌํ•จํ•˜์ง€ ์•Š์Œ)
MYSQL_ROOT_PASSWORD=rootpassword123
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword123
ShellScript
# .env.example (git์— ํฌํ•จ - ํŒ€์›์—๊ฒŒ ํ•„์š”ํ•œ ๋ณ€์ˆ˜ ๋ชฉ๋ก ๊ณต์œ )
MYSQL_ROOT_PASSWORD=
MYSQL_USER=
MYSQL_PASSWORD=
ShellScript

Spring Boot Docker ๋ฐฐํฌ ์‹คํ–‰

์ด๋ฏธ์ง€ ๋นŒ๋“œ ๋ฐ ์‹คํ–‰

# ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ (ํ…Œ์ŠคํŠธ ์ œ์™ธ)
./gradlew bootJar -x test

# docker-compose๋กœ ์ „์ฒด ์Šคํƒ ์‹คํ–‰ (๋ฐฑ๊ทธ๋ผ์šด๋“œ)
docker-compose up -d --build

# ๋กœ๊ทธ ํ™•์ธ
docker-compose logs -f app

# ํŠน์ • ์„œ๋น„์Šค๋งŒ ์žฌ์‹œ์ž‘
docker-compose restart app

# ์ „์ฒด ์ข…๋ฃŒ (๋ณผ๋ฅจ ์œ ์ง€)
docker-compose down

# ์ „์ฒด ์ข…๋ฃŒ + ๋ณผ๋ฅจ ์‚ญ์ œ (DB ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”)
docker-compose down -v
ShellScript

์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ ํ™•์ธ

# ์‹คํ–‰ ์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ ๋ชฉ๋ก
docker-compose ps

# ํ—ฌ์Šค์ฒดํฌ ์ƒํƒœ ํฌํ•จ ์ƒ์„ธ ์ •๋ณด
docker inspect spring-app | grep -A 10 Health

# Actuator health ์—”๋“œํฌ์ธํŠธ๋กœ ์•ฑ ์ƒํƒœ ํ™•์ธ
curl http://localhost:8080/actuator/health
ShellScript

๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๋ถ„๋ฆฌ (docker-compose.override.yml)

์šด์˜์šฉ docker-compose.yml์„ ๊ทธ๋Œ€๋กœ ๋‘๊ณ , ๋กœ์ปฌ ๊ฐœ๋ฐœ์—์„œ๋งŒ ํ•„์š”ํ•œ ์„ค์ •์€ override ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค.

# docker-compose.override.yml (๋กœ์ปฌ ๊ฐœ๋ฐœ์šฉ, git์— ํฌํ•จ ๊ฐ€๋Šฅ)
services:
  app:
    # ๋กœ์ปฌ์—์„œ๋Š” ์ด๋ฏธ์ง€ ๋นŒ๋“œ ์—†์ด JAR ์ง์ ‘ ๋งˆ์šดํŠธํ•ด์„œ ๋น ๋ฅธ ๊ฐœ๋ฐœ ๊ฐ€๋Šฅ
    volumes:
      - ./build/libs:/app/libs
    environment:
      SPRING_PROFILES_ACTIVE: local
      # ๋กœ์ปฌ์—์„œ๋Š” SQL ๋กœ๊น… ํ™œ์„ฑํ™”
      SPRING_JPA_SHOW_SQL: "true"

  db:
    ports:
      # ๋กœ์ปฌ์—์„œ๋Š” DB ํฌํŠธ๋ฅผ ์™ธ๋ถ€์— ๋…ธ์ถœํ•ด DB ํด๋ผ์ด์–ธํŠธ ์ง์ ‘ ์ ‘๊ทผ ํ—ˆ์šฉ
      - "3306:3306"
YAML

docker-compose.override.yml์€ docker-compose up ์‹œ ์ž๋™์œผ๋กœ ๋ณ‘ํ•ฉ๋œ๋‹ค. Docker Compose ๊ณต์‹ ๋ฌธ์„œ์—์„œ merge ๋™์ž‘ ๋ฐฉ์‹์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


Spring Boot Docker ๋ฐฐํฌ ์‹œ ์ž์ฃผ ๊ฒช๋Š” ๋ฌธ์ œ

JVM ํž™ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ

์ปจํ…Œ์ด๋„ˆ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ œํ•œํ–ˆ๋Š”๋ฐ OOMKilled๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด -XX:MaxRAMPercentage๋ฅผ ๋‚ฎ์ถ”๊ฑฐ๋‚˜ ์ปจํ…Œ์ด๋„ˆ ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ์„ ๋Š˜๋ ค์•ผ ํ•œ๋‹ค.

# ์ปจํ…Œ์ด๋„ˆ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ชจ๋‹ˆํ„ฐ๋ง
docker stats spring-app
ShellScript

MySQL ์—ฐ๊ฒฐ ์‹คํŒจ (Communications link failure)

depends_on์— healthcheck condition์„ ์„ค์ •ํ–ˆ๋Š”๋ฐ๋„ ์—ฐ๊ฒฐ ์‹คํŒจ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค. Spring Boot์— ์žฌ์‹œ๋„ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•˜๋‹ค.

# application.yml
spring:
  datasource:
    hikari:
      # DB ์—ฐ๊ฒฐ ์‹คํŒจ ์‹œ ์ตœ๋Œ€ ๋Œ€๊ธฐ ์‹œ๊ฐ„ (MySQL ๊ธฐ๋™ ์™„๋ฃŒ ์ „์— ์—ฐ๊ฒฐ ์‹œ๋„ ๋Œ€๋น„)
      connection-timeout: 30000
      initialization-fail-timeout: 60000
YAML

Spring Boot 3.x์—์„œ๋Š” spring-retry๋ฅผ ํ™œ์šฉํ•œ DataSource ์—ฐ๊ฒฐ ์žฌ์‹œ๋„๋„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์ ํ™”

๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ ํ›„์—๋„ ์ด๋ฏธ์ง€๊ฐ€ ํฌ๋‹ค๋ฉด Spring Boot์˜ ๋ ˆ์ด์–ด๋“œ JAR ๋ฐฉ์‹์„ ํ™œ์šฉํ•˜๋ฉด ๋ ˆ์ด์–ด ์บ์‹ฑ ํšจ์œจ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

# ๋ ˆ์ด์–ด๋“œ JAR ๋ถ„ํ•ด ํ›„ ๋ณต์‚ฌํ•˜๋Š” ๋ฐฉ์‹
FROM eclipse-temurin:21-jre-jammy AS builder
WORKDIR /app
COPY build/libs/*.jar app.jar
# JAR๋ฅผ ๋ ˆ์ด์–ด๋ณ„๋กœ ๋ถ„ํ•ด (์˜์กด์„ฑ, ์Šค๋ƒ…์ƒท, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋“ฑ)
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
# ๋ณ€๊ฒฝ ๋นˆ๋„๊ฐ€ ๋‚ฎ์€ ๋ ˆ์ด์–ด๋ถ€ํ„ฐ ๋ณต์‚ฌ (์บ์‹œ ํšจ์œจ ๊ทน๋Œ€ํ™”)
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
Dockerfile

๋งˆ์น˜๋ฉฐ

์ง€๊ธˆ๊นŒ์ง€ Spring Boot Docker ๋ฐฐํฌ์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•ด ๋ณด์•˜๋‹ค. ๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋กœ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์ด๊ณ , docker-compose์˜ healthcheck๋กœ ์„œ๋น„์Šค ๊ธฐ๋™ ์ˆœ์„œ๋ฅผ ์ œ์–ดํ•˜๋Š” ๋ถ€๋ถ„์ด ์‹ค๋ฌด์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ๋†“์น˜๋Š” ํฌ์ธํŠธ์ธ ๊ฒƒ ๊ฐ™๋‹ค. ์ด ๊ตฌ์„ฑ์„ ๊ธฐ๋ฐ˜์œผ๋กœ GitHub Actions๋‚˜ Jenkins ํŒŒ์ดํ”„๋ผ์ธ์— ์—ฐ๊ฒฐํ•˜๋ฉด Spring Boot Docker ๋ฐฐํฌ ์ž๋™ํ™”๋„ ์–ด๋ ต์ง€ ์•Š๋‹ค.