์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿณ Spring Boot์™€ Docker๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ ์—ฌํ–‰

๐Ÿณ Spring Boot์™€ Docker๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ ์—ฌํ–‰

๊ฐœ๋ฐœ๋ถ€ํ„ฐ ๋ฐฐํฌ๊นŒ์ง€, ์ปจํ…Œ์ด๋„ˆ๋กœ ํ•œ ๋ฐฉ์— ํ•ด๊ฒฐํ•˜๊ธฐ

๐Ÿš€ ์™œ ์ง€๊ธˆ Docker์ธ๊ฐ€?

์นœ๊ตฌ์•ผ, ํ˜น์‹œ ์ด๋Ÿฐ ๊ฒฝํ—˜ ์žˆ์–ด? ๋กœ์ปฌ์—์„œ๋Š” ์™„๋ฒฝํ•˜๊ฒŒ ๋Œ์•„๊ฐ€๋˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์„œ๋ฒ„์— ์˜ฌ๋ฆฌ๋‹ˆ๊นŒ ๊ฐ‘์ž๊ธฐ ์•ˆ ๋Œ์•„๊ฐ€๋Š” ์ƒํ™ฉ ๋ง์ด์•ผ. ๐Ÿ˜ฑ

"์–ด? ๋‚ด ์ปดํ“จํ„ฐ์—์„œ๋Š” ์ž˜ ๋๋Š”๋ฐ..."๋ผ๋Š” ๋ง์€ ์ด์ œ ๊ฐœ๋ฐœ์ž๋“ค ์‚ฌ์ด์—์„œ ๋ฐˆ์ด ๋˜์–ด๋ฒ„๋ ธ์ง€. ๋ฐ”๋กœ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅํ•œ ๊ฒŒ Docker์•ผ.

Docker๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ปจํ…Œ์ด๋„ˆ๋ผ๋Š” ํ‘œ์ค€ํ™”๋œ ๋‹จ์œ„๋กœ ํŒจํ‚ค์ง•ํ•ด์„œ, ์–ด๋””์„œ๋“  ๋™์ผํ•˜๊ฒŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ”Œ๋žซํผ์ด์•ผ. ๋งˆ์น˜ ๋ฐฐ์†ก์šฉ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์–ด๋–ค ๋ฐฐ์— ์‹ค์–ด๋„ ๋˜‘๊ฐ™์ด ์šด์†ก๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ง์ด์•ผ. ๐Ÿšข

๐Ÿ’ก Docker์˜ ํ•ต์‹ฌ ์žฅ์  โœ… ํ™˜๊ฒฝ ์ผ๊ด€์„ฑ: ๊ฐœ๋ฐœ, ํ…Œ์ŠคํŠธ, ์šด์˜ ํ™˜๊ฒฝ์ด ์™„์ „ํžˆ ๋™์ผ
โœ… ๋น ๋ฅธ ๋ฐฐํฌ: ๋ช‡ ์ดˆ ๋งŒ์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ๊ฐ€๋Šฅ
โœ… ๋ฆฌ์†Œ์Šค ํšจ์œจ: ๊ฐ€์ƒ๋จธ์‹ ๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ€๋ณ๊ณ  ๋น ๋ฆ„
โœ… ํ™•์žฅ์„ฑ: ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์— ์ตœ์ ํ™”
โœ… ๊ฒฉ๋ฆฌ์„ฑ: ๊ฐ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰
Docker ์ปจํ…Œ์ด๋„ˆ vs ๊ฐ€์ƒ๋จธ์‹  ๊ฐ€์ƒ๋จธ์‹  (VM) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ Guest OS (์ˆ˜ GB) ํ•˜์ดํผ๋ฐ”์ด์ € Host OS ํ•˜๋“œ์›จ์–ด Docker ์ปจํ…Œ์ด๋„ˆ App 1 App 2 Docker Engine Host OS ํ•˜๋“œ์›จ์–ด ํ›จ์”ฌ ๊ฐ€๋ณ๊ณ  ๋น ๋ฆ„!

โ˜• Spring Boot์™€ Docker์˜ ํ™˜์ƒ ๊ถํ•ฉ

Spring Boot๋Š” Java ๊ธฐ๋ฐ˜์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์•ผ. ๊ทธ๋ฆฌ๊ณ  Docker๋Š” ์ด๋ ‡๊ฒŒ ๋งŒ๋“  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์–ด๋””์„œ๋“  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์ง€.

์ด ๋‘˜์˜ ์กฐํ•ฉ์ด ์™œ ์ข‹๋ƒ๊ณ ? Spring Boot๋Š” ์ด๋ฏธ ๋‚ด์žฅ ํ†ฐ์บฃ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ JAR ํŒŒ์ผ ํ•˜๋‚˜๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด. ์ด๊ฑธ Docker ์ด๋ฏธ์ง€๋กœ ๋งŒ๋“ค๋ฉด ์ •๋ง ์™„๋ฒฝํ•œ ๋ฐฐํฌ ๋‹จ์œ„๊ฐ€ ๋˜๋Š” ๊ฑฐ์•ผ! ๐ŸŽฏ

๐ŸŽจ Spring Boot + Docker ์›Œํฌํ”Œ๋กœ์šฐ

1๋‹จ๊ณ„: Spring Boot๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ๐Ÿ‘จโ€๐Ÿ’ป
2๋‹จ๊ณ„: Dockerfile ์ž‘์„ฑ์œผ๋กœ ์ด๋ฏธ์ง€ ์ •์˜ ๐Ÿ“
3๋‹จ๊ณ„: Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ ๐Ÿ”จ
4๋‹จ๊ณ„: ์ปจํ…Œ์ด๋„ˆ๋กœ ์‹คํ–‰ ๋ฐ ๋ฐฐํฌ ๐Ÿš€
5๋‹จ๊ณ„: ํ•„์š”์‹œ Docker Hub์— ๊ณต์œ  ๐ŸŒ

๐Ÿ› ๏ธ ์‹ค์ „! Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ Dockerํ™”ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹ค์Šต์„ ํ•ด๋ณด์ž! ๊ฐ„๋‹จํ•œ Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ Docker ์ปจํ…Œ์ด๋„ˆ๋กœ ๋งŒ๋“œ๋Š” ์ „ ๊ณผ์ •์„ ํ•จ๊ป˜ ๋”ฐ๋ผ๊ฐ€๋ณด์ž. ๐Ÿ˜Š

๐Ÿ“ฆ 1๋‹จ๊ณ„: Spring Boot ํ”„๋กœ์ ํŠธ ์ค€๋น„

๋จผ์ € ๊ฐ„๋‹จํ•œ Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ•„์š”ํ•ด. Spring Initializr(start.spring.io)์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, ๊ธฐ์กด ํ”„๋กœ์ ํŠธ๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ผ.

์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํ•œ REST API๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ:

// Application.java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    @GetMapping("/")
    public String hello() {
        return "Hello from Docker Container! ๐Ÿณ";
    }
    
    @GetMapping("/health")
    public String health() {
        return "Application is running smoothly!";
    }
}
๐Ÿ’ก Pro Tip: Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ Dockerํ™”ํ•  ๋•Œ๋Š” application.properties๋‚˜ application.yml์—์„œ ํฌํŠธ ์„ค์ •์„ ํ™•์ธํ•ด์•ผ ํ•ด. ๊ธฐ๋ณธ๊ฐ’์€ 8080์ด์ง€๋งŒ, ํ•„์š”์— ๋”ฐ๋ผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ—๏ธ 2๋‹จ๊ณ„: Dockerfile ์ž‘์„ฑํ•˜๊ธฐ

Dockerfile์€ Docker ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ ˆ์‹œํ”ผ์•ผ. ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์— Dockerfile์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

# ๊ธฐ๋ณธ ๋ฐฉ์‹ - ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๊ฐ€ ํผ
FROM openjdk:17-jdk-slim

WORKDIR /app

COPY target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

์œ„ Dockerfile์„ ํ•œ ์ค„์”ฉ ์„ค๋ช…ํ•ด๋ณผ๊ฒŒ:

FROM openjdk:17-jdk-slim - OpenJDK 17 ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•ด. slim ๋ฒ„์ „์€ ๋ถˆํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์ œ๊ฑฐํ•ด์„œ ์šฉ๋Ÿ‰์ด ์ž‘์•„.

WORKDIR /app - ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ ์ž‘์—…ํ•  ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ /app์œผ๋กœ ์„ค์ •ํ•ด.

COPY target/*.jar app.jar - ๋นŒ๋“œ๋œ JAR ํŒŒ์ผ์„ ์ปจํ…Œ์ด๋„ˆ๋กœ ๋ณต์‚ฌํ•ด.

EXPOSE 8080 - ์ปจํ…Œ์ด๋„ˆ๊ฐ€ 8080 ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๋ฌธ์„œํ™”ํ•ด. (์‹ค์ œ๋กœ ํฌํŠธ๋ฅผ ์—ฌ๋Š” ๊ฑด ์•„๋‹ˆ๊ณ  ํžŒํŠธ๋ฅผ ์ฃผ๋Š” ๊ฑฐ์•ผ)

ENTRYPOINT - ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์‹คํ–‰ํ•  ๋ช…๋ น์–ด๋ฅผ ์ง€์ •ํ•ด.

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ: ์œ„ ๋ฐฉ์‹์€ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๊ฐ€ ํฌ๊ณ  ๋ณด์•ˆ์ƒ ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์–ด. ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„!

๐ŸŽฏ 3๋‹จ๊ณ„: ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋กœ ์ตœ์ ํ™”ํ•˜๊ธฐ

๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋Š” Docker์˜ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์•ผ. ๋นŒ๋“œ ํ™˜๊ฒฝ๊ณผ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ๋ถ„๋ฆฌํ•ด์„œ ์ตœ์ข… ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๋Œ€ํญ ์ค„์ผ ์ˆ˜ ์žˆ์–ด. ๐Ÿš€

# ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ - ์ตœ์ ํ™”๋œ ๋ฐฉ์‹
# Stage 1: Build
FROM maven:3.8.5-openjdk-17 AS build

WORKDIR /app

# pom.xml ๋จผ์ € ๋ณต์‚ฌํ•ด์„œ ์˜์กด์„ฑ ์บ์‹ฑ
COPY pom.xml .
RUN mvn dependency:go-offline

# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ ๋ฐ ๋นŒ๋“œ
COPY src ./src
RUN mvn clean package -DskipTests

# Stage 2: Runtime
FROM openjdk:17-jdk-slim

WORKDIR /app

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

# ๋ณด์•ˆ์„ ์œ„ํ•œ non-root ์‚ฌ์šฉ์ž ์ƒ์„ฑ
RUN addgroup --system spring && adduser --system spring --ingroup spring
USER spring:spring

EXPOSE 8080

# JVM ์˜ต์…˜ ์ตœ์ ํ™”
ENTRYPOINT ["java", \
    "-XX:+UseContainerSupport", \
    "-XX:MaxRAMPercentage=75.0", \
    "-Djava.security.egd=file:/dev/./urandom", \
    "-jar", "app.jar"]

์ด ๋ฐฉ์‹์˜ ์žฅ์ ์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ:

โœจ ์ด๋ฏธ์ง€ ํฌ๊ธฐ ๊ฐ์†Œ: Maven๊ณผ ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ์ตœ์ข… ์ด๋ฏธ์ง€์— ํฌํ•จ๋˜์ง€ ์•Š์•„
โœจ ๋นŒ๋“œ ์บ์‹ฑ: pom.xml์„ ๋จผ์ € ๋ณต์‚ฌํ•ด์„œ ์˜์กด์„ฑ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์บ์‹ฑํ•  ์ˆ˜ ์žˆ์–ด
โœจ ๋ณด์•ˆ ๊ฐ•ํ™”: non-root ์‚ฌ์šฉ์ž๋กœ ์‹คํ–‰ํ•ด์„œ ๋ณด์•ˆ ๋ฆฌ์Šคํฌ๋ฅผ ์ค„์—ฌ
โœจ ์„ฑ๋Šฅ ์ตœ์ ํ™”: JVM ์˜ต์…˜์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์— ์ตœ์ ํ™”

๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ ํ”„๋กœ์„ธ์Šค Stage 1: Build Maven + OpenJDK 17 ํฌ๊ธฐ: ์•ฝ 700MB ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ pom.xml + src/ ์˜์กด์„ฑ ๋‹ค์šด๋กœ๋“œ mvn dependency:go-offline ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋นŒ๋“œ mvn clean package JAR ๋ณต์‚ฌ Stage 2: Runtime OpenJDK 17 Slim ํฌ๊ธฐ: ์•ฝ 200MB JAR ๋ณด์•ˆ ์„ค์ • non-root user ์ตœ์ข… ์ด๋ฏธ์ง€ ํฌ๊ธฐ: ์•ฝ 250MB 70% ์šฉ๋Ÿ‰ ์ ˆ๊ฐ!

๐Ÿ”จ 4๋‹จ๊ณ„: Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œํ•˜๊ธฐ

Dockerfile์ด ์ค€๋น„๋์œผ๋ฉด ์ด์ œ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•  ์ฐจ๋ก€์•ผ. ํ„ฐ๋ฏธ๋„์—์„œ ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™ํ•œ ๋‹ค์Œ ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด:

# ๊ธฐ๋ณธ ๋นŒ๋“œ
docker build -t my-spring-app:1.0 .

# ๋นŒ๋“œ ์บ์‹œ ์—†์ด ์ƒˆ๋กœ ๋นŒ๋“œ
docker build --no-cache -t my-spring-app:1.0 .

# ํŠน์ • Dockerfile ์‚ฌ์šฉ
docker build -f Dockerfile.prod -t my-spring-app:1.0 .

๋ช…๋ น์–ด๋ฅผ ๋ถ„์„ํ•ด๋ณด๋ฉด:

-t my-spring-app:1.0 - ์ด๋ฏธ์ง€์— ํƒœ๊ทธ๋ฅผ ๋ถ™์—ฌ. ์—ฌ๊ธฐ์„œ๋Š” ์ด๋ฆ„์ด my-spring-app์ด๊ณ  ๋ฒ„์ „์ด 1.0์ด์•ผ.

. - ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋นŒ๋“œ ์ปจํ…์ŠคํŠธ๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์˜๋ฏธ์•ผ.

๋นŒ๋“œ๊ฐ€ ์ง„ํ–‰๋˜๋ฉด ๊ฐ ๋‹จ๊ณ„๋ณ„๋กœ ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋ผ. ์ฒ˜์Œ์—๋Š” ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ฆฌ์ง€๋งŒ, ๋‘ ๋ฒˆ์งธ๋ถ€ํ„ฐ๋Š” ์บ์‹œ ๋•๋ถ„์— ํ›จ์”ฌ ๋น ๋ฅด๊ฒŒ ๋นŒ๋“œ๋ผ! โšก

๐Ÿ’ก .dockerignore ํ™œ์šฉํ•˜๊ธฐ: .dockerignore ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์„œ ๋ถˆํ•„์š”ํ•œ ํŒŒ์ผ๋“ค์„ ๋นŒ๋“œ ์ปจํ…์ŠคํŠธ์—์„œ ์ œ์™ธํ•  ์ˆ˜ ์žˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋นŒ๋“œ ์†๋„๊ฐ€ ๋นจ๋ผ์ง€๊ณ  ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋„ ์ค„์–ด๋“ค์–ด.

target/
.git/
.idea/
*.md
.env
node_modules/

๐Ÿš€ 5๋‹จ๊ณ„: ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰ํ•˜๊ธฐ

์ด๋ฏธ์ง€๊ฐ€ ๋นŒ๋“œ๋์œผ๋ฉด ์ด์ œ ์ปจํ…Œ์ด๋„ˆ๋กœ ์‹คํ–‰ํ•ด๋ณด์ž:

# ๊ธฐ๋ณธ ์‹คํ–‰
docker run -p 8080:8080 my-spring-app:1.0

# ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ (detached mode)
docker run -d -p 8080:8080 --name my-app my-spring-app:1.0

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜์™€ ํ•จ๊ป˜ ์‹คํ–‰
docker run -d \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DATABASE_URL=jdbc:mysql://db:3306/mydb \
  --name my-app \
  my-spring-app:1.0

# ๋ณผ๋ฅจ ๋งˆ์šดํŠธ์™€ ํ•จ๊ป˜ ์‹คํ–‰
docker run -d \
  -p 8080:8080 \
  -v /path/to/logs:/app/logs \
  --name my-app \
  my-spring-app:1.0

์ฃผ์š” ์˜ต์…˜๋“ค์„ ์‚ดํŽด๋ณด์ž:

-p 8080:8080 - ํ˜ธ์ŠคํŠธ์˜ 8080 ํฌํŠธ๋ฅผ ์ปจํ…Œ์ด๋„ˆ์˜ 8080 ํฌํŠธ๋กœ ๋งคํ•‘ํ•ด.

-d - ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ํ•ด. (detached mode)

--name my-app - ์ปจํ…Œ์ด๋„ˆ์— ์ด๋ฆ„์„ ๋ถ™์—ฌ. ์ด๋ฆ„์ด ์—†์œผ๋ฉด ๋žœ๋ค์œผ๋กœ ์ƒ์„ฑ๋ผ.

-e - ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ด. Spring Boot์˜ ํ”„๋กœํŒŒ์ผ์ด๋‚˜ ์„ค์ •์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ์–ด.

-v - ๋ณผ๋ฅจ์„ ๋งˆ์šดํŠธํ•ด. ๋กœ๊ทธ ํŒŒ์ผ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ˜ธ์ŠคํŠธ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ” 6๋‹จ๊ณ„: ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌํ•˜๊ธฐ

์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹คํ–‰๋˜๋ฉด ๋‹ค์–‘ํ•œ ๋ช…๋ น์–ด๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด:

# ์‹คํ–‰ ์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ ํ™•์ธ
docker ps

# ๋ชจ๋“  ์ปจํ…Œ์ด๋„ˆ ํ™•์ธ (์ค‘์ง€๋œ ๊ฒƒ ํฌํ•จ)
docker ps -a

# ์ปจํ…Œ์ด๋„ˆ ๋กœ๊ทธ ํ™•์ธ
docker logs my-app

# ์‹ค์‹œ๊ฐ„ ๋กœ๊ทธ ํ™•์ธ
docker logs -f my-app

# ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ์ ‘์†
docker exec -it my-app /bin/bash

# ์ปจํ…Œ์ด๋„ˆ ์ค‘์ง€
docker stop my-app

# ์ปจํ…Œ์ด๋„ˆ ์‹œ์ž‘
docker start my-app

# ์ปจํ…Œ์ด๋„ˆ ์žฌ์‹œ์ž‘
docker restart my-app

# ์ปจํ…Œ์ด๋„ˆ ์‚ญ์ œ
docker rm my-app

# ๊ฐ•์ œ ์‚ญ์ œ (์‹คํ–‰ ์ค‘์ด์–ด๋„)
docker rm -f my-app
๐ŸŽฏ ์‹ค์ „ ํŒ: ํ—ฌ์Šค ์ฒดํฌ ์ถ”๊ฐ€ํ•˜๊ธฐ

Dockerfile์— ํ—ฌ์Šค ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ปจํ…Œ์ด๋„ˆ์˜ ์ƒํƒœ๋ฅผ ์ž๋™์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์–ด:

HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Docker๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ /health ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ •์ƒ์ธ์ง€ ํ™•์ธํ•ด. ๐Ÿฅ

๐ŸŽผ Docker Compose๋กœ ๋ฉ€ํ‹ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌํ•˜๊ธฐ

์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋ณดํ†ต ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๋กœ ๊ตฌ์„ฑ๋ผ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด Spring Boot ์•ฑ, MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, Redis ์บ์‹œ ๋“ฑ์ด ํ•จ๊ป˜ ๋™์ž‘ํ•˜์ง€. ์ด๋Ÿด ๋•Œ Docker Compose๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ•œ ๋ฒˆ์— ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽต

๐Ÿ“ docker-compose.yml ์ž‘์„ฑํ•˜๊ธฐ

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— docker-compose.yml ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

version: '3.8'

services:
  # Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/mydb
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=secret
      - SPRING_REDIS_HOST=redis
    depends_on:
      - db
      - redis
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
  db:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=secret
      - MYSQL_DATABASE=mydb
    ports:
      - "3306:3306"
    volumes:
      - db-data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis ์บ์‹œ
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

networks:
  app-network:
    driver: bridge

volumes:
  db-data:
  redis-data:

์ด ์„ค์ • ํŒŒ์ผ์˜ ์ฃผ์š” ํฌ์ธํŠธ๋ฅผ ์‚ดํŽด๋ณด์ž:

services: ์‹คํ–‰ํ•  ์ปจํ…Œ์ด๋„ˆ๋“ค์„ ์ •์˜ํ•ด. ์—ฌ๊ธฐ์„œ๋Š” app, db, redis ์„ธ ๊ฐœ์˜ ์„œ๋น„์Šค๊ฐ€ ์žˆ์–ด.

depends_on: ์„œ๋น„์Šค ๊ฐ„ ์˜์กด์„ฑ์„ ์ •์˜ํ•ด. app์€ db์™€ redis๊ฐ€ ๋จผ์ € ์‹œ์ž‘๋˜์–ด์•ผ ํ•ด.

networks: ์ปจํ…Œ์ด๋„ˆ๋“ค์ด ์„œ๋กœ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ๋„คํŠธ์›Œํฌ๋ฅผ ๋งŒ๋“ค์–ด.

volumes: ๋ฐ์ดํ„ฐ๋ฅผ ์˜๊ตฌ์ ์œผ๋กœ ์ €์žฅํ•  ๋ณผ๋ฅจ์„ ์ •์˜ํ•ด. ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‚ญ์ œ๋˜์–ด๋„ ๋ฐ์ดํ„ฐ๋Š” ๋‚จ์•„์žˆ์–ด.

healthcheck: ๊ฐ ์„œ๋น„์Šค์˜ ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด.

๐ŸŽฎ Docker Compose ๋ช…๋ น์–ด

Docker Compose๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์š” ๋ช…๋ น์–ด๋“ค์ด์•ผ:

# ๋ชจ๋“  ์„œ๋น„์Šค ์‹œ์ž‘ (๋ฐฑ๊ทธ๋ผ์šด๋“œ)
docker-compose up -d

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

# ๋นŒ๋“œ์™€ ํ•จ๊ป˜ ์‹œ์ž‘
docker-compose up -d --build

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

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

# ์‹คํ–‰ ์ค‘์ธ ์„œ๋น„์Šค ํ™•์ธ
docker-compose ps

# ์„œ๋น„์Šค ์ค‘์ง€
docker-compose stop

# ์„œ๋น„์Šค ์ค‘์ง€ ๋ฐ ์ปจํ…Œ์ด๋„ˆ ์‚ญ์ œ
docker-compose down

# ๋ณผ๋ฅจ๊นŒ์ง€ ์‚ญ์ œ
docker-compose down -v

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

# ์„œ๋น„์Šค ์Šค์ผ€์ผ๋ง
docker-compose up -d --scale app=3
๐ŸŒŸ Docker Compose์˜ ์žฅ์ 

1๏ธโƒฃ ๊ฐ„ํŽธํ•œ ๊ด€๋ฆฌ: ์—ฌ๋Ÿฌ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ•˜๋‚˜์˜ ๋ช…๋ น์–ด๋กœ ์ œ์–ด
2๏ธโƒฃ ํ™˜๊ฒฝ ์ผ๊ด€์„ฑ: ๊ฐœ๋ฐœ, ํ…Œ์ŠคํŠธ, ์šด์˜ ํ™˜๊ฒฝ์„ ๋™์ผํ•˜๊ฒŒ ๊ตฌ์„ฑ
3๏ธโƒฃ ๋„คํŠธ์›Œํ‚น ์ž๋™ํ™”: ์ปจํ…Œ์ด๋„ˆ ๊ฐ„ ํ†ต์‹ ์ด ์ž๋™์œผ๋กœ ์„ค์ •๋จ
4๏ธโƒฃ ๋ฒ„์ „ ๊ด€๋ฆฌ: YAML ํŒŒ์ผ๋กœ ์ธํ”„๋ผ๋ฅผ ์ฝ”๋“œ๋กœ ๊ด€๋ฆฌ
5๏ธโƒฃ ๋น ๋ฅธ ๊ฐœ๋ฐœ: ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ๋ช‡ ์ดˆ ๋งŒ์— ๊ตฌ์ถ•
Docker Compose ์•„ํ‚คํ…์ฒ˜ app-network Spring Boot App Port: 8080 REST API Business Logic Data Access depends_on: db, redis MySQL Database Port: 3306 Volume: db-data Persistent Storage Redis Cache Port: 6379 Volume: redis-data Health Checks App: /health DB: mysqladmin ping Redis: redis-cli ping Interval: 30s Timeout: 10s Retries: 3 SQL Cache

๐Ÿ” ๋ณด์•ˆ๊ณผ ์ตœ์ ํ™” ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

Docker ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ณด์•ˆ๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ์ •๋ง ์ค‘์š”ํ•ด. ์‹ค์ „์—์„œ ๊ผญ ์•Œ์•„์•ผ ํ•  ํŒ๋“ค์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿ›ก๏ธ

๐Ÿ”’ ๋ณด์•ˆ ๊ฐ•ํ™” ์ „๋žต

๋ณด์•ˆ ํ•ญ๋ชฉ ์„ค๋ช… ๊ตฌํ˜„ ๋ฐฉ๋ฒ•
Non-root ์‚ฌ์šฉ์ž root ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ธฐ USER spring:spring
์ตœ์†Œ ๊ถŒํ•œ ์›์น™ ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌ --cap-drop=ALL
์ฝ๊ธฐ ์ „์šฉ ํŒŒ์ผ์‹œ์Šคํ…œ ์ปจํ…Œ์ด๋„ˆ ํŒŒ์ผ์‹œ์Šคํ…œ์„ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ --read-only
์‹œํฌ๋ฆฟ ๊ด€๋ฆฌ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌ Docker Secrets ๋˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜
์ด๋ฏธ์ง€ ์Šค์บ” ์ทจ์•ฝ์  ์ž๋™ ๊ฒ€์‚ฌ docker scan
๋„คํŠธ์›Œํฌ ๊ฒฉ๋ฆฌ ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ๋…ธ์ถœ ์ฐจ๋‹จ ์ปค์Šคํ…€ ๋„คํŠธ์›Œํฌ ์‚ฌ์šฉ

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ธฐ๋ฒ•

1. ๋ ˆ์ด์–ด ์บ์‹ฑ ์ตœ์ ํ™”

Dockerfile์˜ ๋ช…๋ น์–ด ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•ด. ์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ๋จผ์ € ๋ฐฐ์น˜ํ•˜๋ฉด ๋นŒ๋“œ ์บ์‹œ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด:

# โŒ ๋น„ํšจ์œจ์ ์ธ ๋ฐฉ์‹
COPY . /app
RUN mvn clean package

# โœ… ํšจ์œจ์ ์ธ ๋ฐฉ์‹
COPY pom.xml /app/
RUN mvn dependency:go-offline
COPY src /app/src
RUN mvn clean package

2. ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ ํ™œ์šฉ

์•ž์—์„œ ๋ดค๋“ฏ์ด ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ์ข… ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๋Œ€ํญ ์ค„์ผ ์ˆ˜ ์žˆ์–ด. ์ด๊ฑด ์ •๋ง ํ•„์ˆ˜์•ผ!

3. JVM ๋ฉ”๋ชจ๋ฆฌ ์„ค์ •

์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์—์„œ๋Š” JVM ๋ฉ”๋ชจ๋ฆฌ ์„ค์ •์ด ์ค‘์š”ํ•ด:

# Dockerfile์—์„œ JVM ์˜ต์…˜ ์„ค์ •
ENTRYPOINT ["java", \
    "-XX:+UseContainerSupport", \
    "-XX:MaxRAMPercentage=75.0", \
    "-XX:InitialRAMPercentage=50.0", \
    "-Xms512m", \
    "-Xmx2g", \
    "-XX:+UseG1GC", \
    "-XX:MaxGCPauseMillis=200", \
    "-jar", "app.jar"]

4. ๋ถˆํ•„์š”ํ•œ ํŒŒ์ผ ์ œ๊ฑฐ

.dockerignore ํŒŒ์ผ์„ ์ ๊ทน ํ™œ์šฉํ•ด์„œ ๋นŒ๋“œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ตœ์†Œํ™”ํ•˜์ž:

# .dockerignore
target/
.git/
.gitignore
.idea/
*.iml
*.md
.env
.DS_Store
node_modules/
logs/
*.log
โš ๏ธ ์ฃผ์˜: ๋ฏผ๊ฐํ•œ ์ •๋ณด ๊ด€๋ฆฌ ์ ˆ๋Œ€๋กœ Dockerfile์ด๋‚˜ ์ด๋ฏธ์ง€์— ๋น„๋ฐ€๋ฒˆํ˜ธ, API ํ‚ค ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ํ•˜๋“œ์ฝ”๋”ฉํ•˜๋ฉด ์•ˆ ๋ผ! ๋Œ€์‹  ๋‹ค์Œ ๋ฐฉ๋ฒ•๋“ค์„ ์‚ฌ์šฉํ•ด:

๐Ÿ”‘ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ
๐Ÿ”‘ Docker Secrets ์‚ฌ์šฉ
๐Ÿ”‘ ์™ธ๋ถ€ ์„ค์ • ์„œ๋ฒ„ (Spring Cloud Config)
๐Ÿ”‘ Kubernetes Secrets (K8s ํ™˜๊ฒฝ)

๐Ÿ“Š ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋กœ๊น…

์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์—์„œ๋Š” ๋กœ๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ๋”์šฑ ์ค‘์š”ํ•ด. Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋กœ๊ทธ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž:

# application.yml
logging:
  level:
    root: INFO
    com.example: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
  file:
    name: /app/logs/application.log
    max-size: 10MB
    max-history: 30

# Docker Compose์—์„œ ๋กœ๊ทธ ๋“œ๋ผ์ด๋ฒ„ ์„ค์ •
services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

๐ŸŒ Docker Hub์™€ ์ด๋ฏธ์ง€ ๋ฐฐํฌ

์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ์ด์ œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์„œ๋ฒ„์— ๋ฐฐํฌํ•  ์ฐจ๋ก€์•ผ. Docker Hub๋Š” GitHub์ฒ˜๋Ÿผ Docker ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์•ผ. ๐Ÿš€

๐Ÿ“ค Docker Hub์— ์ด๋ฏธ์ง€ ํ‘ธ์‹œํ•˜๊ธฐ

# 1. Docker Hub ๋กœ๊ทธ์ธ
docker login

# 2. ์ด๋ฏธ์ง€์— ํƒœ๊ทธ ๋ถ™์ด๊ธฐ
docker tag my-spring-app:1.0 username/my-spring-app:1.0
docker tag my-spring-app:1.0 username/my-spring-app:latest

# 3. ์ด๋ฏธ์ง€ ํ‘ธ์‹œ
docker push username/my-spring-app:1.0
docker push username/my-spring-app:latest

# 4. ๋‹ค๋ฅธ ์„œ๋ฒ„์—์„œ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ
docker pull username/my-spring-app:latest
docker run -d -p 8080:8080 username/my-spring-app:latest
๐Ÿ’ก Private Registry ์‚ฌ์šฉํ•˜๊ธฐ ํšŒ์‚ฌ ํ”„๋กœ์ ํŠธ๋ผ๋ฉด Docker Hub์˜ Private Repository๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ์ž์ฒด Private Registry๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒŒ ์ข‹์•„. AWS ECR, Google Container Registry, Azure Container Registry ๊ฐ™์€ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋„ ์žˆ์–ด!

๐Ÿ”„ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ํ†ตํ•ฉ

Docker๋ฅผ CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ํ†ตํ•ฉํ•˜๋ฉด ์ž๋™ํ™”๋œ ๋นŒ๋“œ์™€ ๋ฐฐํฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด. GitHub Actions๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ณผ๊ฒŒ:

# .github/workflows/docker-build.yml
name: Docker Build and Push

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
    
    - name: Build with Maven
      run: mvn clean package -DskipTests
    
    - name: Log in to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    
    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: |
          username/my-spring-app:latest
          username/my-spring-app:${{ github.sha }}
    
    - name: Deploy to production
      run: |
        ssh user@server "docker pull username/my-spring-app:latest"
        ssh user@server "docker-compose up -d"

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด main ๋ธŒ๋žœ์น˜์— ์ฝ”๋“œ๋ฅผ ํ‘ธ์‹œํ•  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ Docker ์ด๋ฏธ์ง€๊ฐ€ ๋นŒ๋“œ๋˜๊ณ  ๋ฐฐํฌ๋ผ! ๐ŸŽ‰


๐Ÿ› ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…๊ณผ ๋””๋ฒ„๊น…

Docker๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฌธ์ œ์— ๋ถ€๋”ชํž ์ˆ˜ ์žˆ์–ด. ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿ”ง

โŒ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค

๋ฌธ์ œ 1: ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋ฐ”๋กœ ์ข…๋ฃŒ๋จ

์ฆ์ƒ: docker ps๋กœ ํ™•์ธํ•˜๋ฉด ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋ณด์ด์ง€ ์•Š์Œ

ํ•ด๊ฒฐ:
# ์ข…๋ฃŒ๋œ ์ปจํ…Œ์ด๋„ˆ ํ™•์ธ
docker ps -a

# ๋กœ๊ทธ ํ™•์ธ
docker logs container-name

# ์ผ๋ฐ˜์ ์ธ ์›์ธ
# - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹คํŒจ
# - ํฌํŠธ ์ถฉ๋Œ
# - ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋ˆ„๋ฝ
# - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ
๋ฌธ์ œ 2: ํฌํŠธ ๋ฐ”์ธ๋”ฉ ์˜ค๋ฅ˜

์ฆ์ƒ: "port is already allocated" ์—๋Ÿฌ

ํ•ด๊ฒฐ:
# ํฌํŠธ ์‚ฌ์šฉ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ํ™•์ธ (Linux/Mac)
lsof -i :8080

# ํฌํŠธ ์‚ฌ์šฉ ์ค‘์ธ ํ”„๋กœ์„ธ์Šค ํ™•์ธ (Windows)
netstat -ano | findstr :8080

# ๋‹ค๋ฅธ ํฌํŠธ๋กœ ๋งคํ•‘
docker run -p 8081:8080 my-spring-app:1.0
๋ฌธ์ œ 3: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ

์ฆ์ƒ: "Connection refused" ๋˜๋Š” "Unknown host"

ํ•ด๊ฒฐ:
# Docker Compose ์‚ฌ์šฉ ์‹œ ์„œ๋น„์Šค ์ด๋ฆ„์œผ๋กœ ์—ฐ๊ฒฐ
spring.datasource.url=jdbc:mysql://db:3306/mydb

# ๋„คํŠธ์›Œํฌ ํ™•์ธ
docker network ls
docker network inspect app-network

# ์ปจํ…Œ์ด๋„ˆ ๊ฐ„ ํ†ต์‹  ํ…Œ์ŠคํŠธ
docker exec -it app ping db
๋ฌธ์ œ 4: ์ด๋ฏธ์ง€ ๋นŒ๋“œ๊ฐ€ ๋А๋ฆผ

ํ•ด๊ฒฐ:
# BuildKit ํ™œ์„ฑํ™” (๋” ๋น ๋ฅธ ๋นŒ๋“œ)
export DOCKER_BUILDKIT=1
docker build -t my-spring-app:1.0 .

# ๋นŒ๋“œ ์บ์‹œ ํ™œ์šฉ
# - .dockerignore ํŒŒ์ผ ์ž‘์„ฑ
# - ๋ ˆ์ด์–ด ์ˆœ์„œ ์ตœ์ ํ™”
# - ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ ์‚ฌ์šฉ

๐Ÿ” ๋””๋ฒ„๊น… ํŒ

์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ ๋ฌธ์ œ๋ฅผ ๋””๋ฒ„๊น…ํ•˜๋Š” ์œ ์šฉํ•œ ๋ช…๋ น์–ด๋“ค์ด์•ผ:

# ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ์ ‘์†
docker exec -it container-name /bin/bash

# ์ปจํ…Œ์ด๋„ˆ ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ
docker stats

# ์ปจํ…Œ์ด๋„ˆ ์ƒ์„ธ ์ •๋ณด ํ™•์ธ
docker inspect container-name

# ์ปจํ…Œ์ด๋„ˆ ํ”„๋กœ์„ธ์Šค ํ™•์ธ
docker top container-name

# ์ปจํ…Œ์ด๋„ˆ ํŒŒ์ผ ์‹œ์Šคํ…œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ ํ™•์ธ
docker diff container-name

# ์‹ค์‹œ๊ฐ„ ๋กœ๊ทธ ์ŠคํŠธ๋ฆฌ๋ฐ
docker logs -f --tail 100 container-name

# ํŠน์ • ์‹œ๊ฐ„๋Œ€ ๋กœ๊ทธ ํ™•์ธ
docker logs --since 30m container-name
๐ŸŽฏ ๋””๋ฒ„๊น… ๋ชจ๋“œ๋กœ ์‹คํ–‰ํ•˜๊ธฐ

๊ฐœ๋ฐœ ์ค‘์—๋Š” ๋””๋ฒ„๊น… ํฌํŠธ๋ฅผ ์—ด์–ด์„œ IDE์—์„œ ์›๊ฒฉ ๋””๋ฒ„๊น…์„ ํ•  ์ˆ˜ ์žˆ์–ด:

# Dockerfile์— ๋””๋ฒ„๊ทธ ์˜ต์…˜ ์ถ”๊ฐ€
ENTRYPOINT ["java", \
    "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", \
    "-jar", "app.jar"]

# ๋””๋ฒ„๊ทธ ํฌํŠธ์™€ ํ•จ๊ป˜ ์‹คํ–‰
docker run -p 8080:8080 -p 5005:5005 my-spring-app:1.0
IntelliJ๋‚˜ Eclipse์—์„œ Remote Debug ์„ค์ •์œผ๋กœ localhost:5005์— ์—ฐ๊ฒฐํ•˜๋ฉด ๋ผ!

๐Ÿš€ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์˜ˆ์ œ

์ด์ œ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ „ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! REST API, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์บ์‹œ๋ฅผ ๋ชจ๋‘ ํฌํ•จํ•œ ์™„์ „ํ•œ ์˜ˆ์ œ์•ผ. ๐Ÿ’ช

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

my-spring-app/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ main/
โ”‚   โ”‚   โ”œโ”€โ”€ java/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ com/example/demo/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Application.java
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ controller/
โ”‚   โ”‚   โ”‚       โ”‚   โ””โ”€โ”€ UserController.java
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ service/
โ”‚   โ”‚   โ”‚       โ”‚   โ””โ”€โ”€ UserService.java
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ repository/
โ”‚   โ”‚   โ”‚       โ”‚   โ””โ”€โ”€ UserRepository.java
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ model/
โ”‚   โ”‚   โ”‚           โ””โ”€โ”€ User.java
โ”‚   โ”‚   โ””โ”€โ”€ resources/
โ”‚   โ”‚       โ”œโ”€โ”€ application.yml
โ”‚   โ”‚       โ””โ”€โ”€ application-prod.yml
โ”œโ”€โ”€ Dockerfile
โ”œโ”€โ”€ docker-compose.yml
โ”œโ”€โ”€ .dockerignore
โ”œโ”€โ”€ pom.xml
โ””โ”€โ”€ README.md

๐Ÿ’ป ํ•ต์‹ฌ ์ฝ”๋“œ

User ์—”ํ‹ฐํ‹ฐ:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String email;
    
    @CreatedDate
    private LocalDateTime createdAt;
    
    // getters, setters, constructors
}

UserController:

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    @GetMapping("/{id}")
    @Cacheable(value = "users", key = "#id")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
    
    @DeleteMapping("/{id}")
    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(@PathVariable Long id) {
        userService.delete(id);
    }
}

application.yml:

spring:
  application:
    name: my-spring-app
  
  datasource:
    url: jdbc:mysql://${DB_HOST:localhost}:3306/${DB_NAME:mydb}
    username: ${DB_USER:root}
    password: ${DB_PASSWORD:secret}
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  
  redis:
    host: ${REDIS_HOST:localhost}
    port: 6379
  
  cache:
    type: redis
    redis:
      time-to-live: 600000

server:
  port: 8080

logging:
  level:
    root: INFO
    com.example: DEBUG

๐Ÿณ ์™„์ „ํ•œ docker-compose.yml

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: spring-app
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_HOST=db
      - DB_NAME=mydb
      - DB_USER=root
      - DB_PASSWORD=secret123
      - REDIS_HOST=redis
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    image: mysql:8.0
    container_name: mysql-db
    environment:
      - MYSQL_ROOT_PASSWORD=secret123
      - MYSQL_DATABASE=mydb
    ports:
      - "3306:3306"
    volumes:
      - db-data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-psecret123"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  redis:
    image: redis:7-alpine
    container_name: redis-cache
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - app-network
    restart: unless-stopped
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    networks:
      - app-network
    restart: unless-stopped

networks:
  app-network:
    driver: bridge

volumes:
  db-data:
  redis-data:

์ด ์„ค์ •์œผ๋กœ docker-compose up -d ๋ช…๋ น์–ด ํ•˜๋‚˜๋ฉด ์ „์ฒด ์Šคํƒ์ด ์‹คํ–‰๋ผ! ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐Ÿ˜Š


โ˜๏ธ ํด๋ผ์šฐ๋“œ ๋ฐฐํฌ ์ „๋žต

๋กœ์ปฌ์—์„œ ์™„๋ฒฝํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ด์ œ ํด๋ผ์šฐ๋“œ์— ๋ฐฐํฌํ•ด๋ณด์ž! AWS, Azure, GCP ๋ชจ๋‘ Docker๋ฅผ ์ง€์›ํ•ด. ๐ŸŒฅ๏ธ

๐ŸŽฏ AWS ECS (Elastic Container Service)

AWS ECS๋Š” Docker ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‰ฝ๊ฒŒ ์‹คํ–‰ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋น„์Šค์•ผ:

# 1. ECR์— ์ด๋ฏธ์ง€ ํ‘ธ์‹œ
aws ecr get-login-password --region ap-northeast-2 | \
  docker login --username AWS --password-stdin \
  123456789.dkr.ecr.ap-northeast-2.amazonaws.com

docker tag my-spring-app:1.0 \
  123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-spring-app:1.0

docker push 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-spring-app:1.0

# 2. ECS Task Definition ์ƒ์„ฑ (JSON)
{
  "family": "my-spring-app",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "containerDefinitions": [
    {
      "name": "app",
      "image": "123456789.dkr.ecr.ap-northeast-2.amazonaws.com/my-spring-app:1.0",
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "SPRING_PROFILES_ACTIVE",
          "value": "prod"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/my-spring-app",
          "awslogs-region": "ap-northeast-2",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

โ˜ธ๏ธ Kubernetes ๋ฐฐํฌ

Kubernetes๋Š” ์ปจํ…Œ์ด๋„ˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์˜ ํ‘œ์ค€์ด์•ผ. ๋ณต์žกํ•˜์ง€๋งŒ ๊ฐ•๋ ฅํ•ด:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app
  labels:
    app: spring-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: spring-app
  template:
    metadata:
      labels:
        app: spring-app
    spec:
      containers:
      - name: app
        image: username/my-spring-app:1.0
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: db.host
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 20
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: spring-app-service
spec:
  type: LoadBalancer
  selector:
    app: spring-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
๐ŸŽ“ ์žฌ๋Šฅ๋„ท์—์„œ ๋ฐฐ์šฐ๊ธฐ Docker์™€ Kubernetes๊ฐ€ ์ฒ˜์Œ์ด๋ผ๋ฉด ๋ณต์žกํ•˜๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์–ด. ์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์—์„œ๋Š” ์‹ค๋ฌด ๊ฒฝํ—˜์ด ํ’๋ถ€ํ•œ ์ „๋ฌธ๊ฐ€๋“ค์ด 1:1 ๋ฉ˜ํ† ๋ง์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์–ด. ์‹ค์ „ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•จ๊ป˜ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋ฐฐ์šฐ๋ฉด ํ›จ์”ฌ ๋น ๋ฅด๊ฒŒ ์‹ค๋ ฅ์„ ํ‚ค์šธ ์ˆ˜ ์žˆ์ง€! ๐Ÿš€

๐Ÿ“ˆ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๊ด€์ฐฐ์„ฑ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๊ฒŒ ์ •๋ง ์ค‘์š”ํ•ด. Spring Boot Actuator์™€ Prometheus, Grafana๋ฅผ ์กฐํ•ฉํ•˜๋ฉด ๊ฐ•๋ ฅํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ“Š

๐Ÿ” Spring Boot Actuator ์„ค์ •

// pom.xml์— ์˜์กด์„ฑ ์ถ”๊ฐ€
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
  metrics:
    export:
      prometheus:
        enabled: true

๐Ÿ“Š Prometheus + Grafana ์„ค์ •

# docker-compose.yml์— ์ถ”๊ฐ€
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
    networks:
      - app-network
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-data:/var/lib/grafana
    depends_on:
      - prometheus
    networks:
      - app-network
    restart: unless-stopped
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['app:8080']

์ด์ œ Grafana(http://localhost:3000)์— ์ ‘์†ํ•ด์„œ Prometheus๋ฅผ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋กœ ์ถ”๊ฐ€ํ•˜๊ณ , ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๋งŒ๋“ค๋ฉด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฉ”ํŠธ๋ฆญ์„ ๋ณผ ์ˆ˜ ์žˆ์–ด! ๐Ÿ“ˆ


๐ŸŽ“ ํ•™์Šต ๋กœ๋“œ๋งต๊ณผ ๋‹ค์Œ ๋‹จ๊ณ„

์—ฌ๊ธฐ๊นŒ์ง€ ๋”ฐ๋ผ์™”๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐ŸŽ‰ ์ด์ œ Spring Boot์™€ Docker์˜ ๊ธฐ๋ณธ์€ ๋งˆ์Šคํ„ฐํ–ˆ์–ด. ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ ์œ„ํ•œ ๋กœ๋“œ๋งต์„ ์ •๋ฆฌํ•ด๋ดค์–ด:

๐ŸŒฑ ์ดˆ๊ธ‰ ๋‹จ๊ณ„ (์ง€๊ธˆ ์—ฌ๊ธฐ!)

โœ… Docker ๊ธฐ๋ณธ ๊ฐœ๋… ์ดํ•ด
โœ… Dockerfile ์ž‘์„ฑ
โœ… Docker Compose ํ™œ์šฉ
โœ… Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…Œ์ด๋„ˆํ™”
โœ… ๊ธฐ๋ณธ์ ์ธ ๋„คํŠธ์›Œํ‚น๊ณผ ๋ณผ๋ฅจ ๊ด€๋ฆฌ
๐ŸŒฟ ์ค‘๊ธ‰ ๋‹จ๊ณ„

๐ŸŽฏ ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ ์ตœ์ ํ™”
๐ŸŽฏ Docker ๋„คํŠธ์›Œํฌ ์‹ฌํ™”
๐ŸŽฏ ๋ณด์•ˆ ๊ฐ•ํ™” (Secrets, ์Šค์บ”)
๐ŸŽฏ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•
๐ŸŽฏ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋กœ๊น… ์‹œ์Šคํ…œ ๊ตฌ์ถ•
๐ŸŽฏ Docker Swarm ํ•™์Šต
๐ŸŒณ ๊ณ ๊ธ‰ ๋‹จ๊ณ„

๐Ÿš€ Kubernetes ๋งˆ์Šคํ„ฐ
๐Ÿš€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„
๐Ÿš€ ์„œ๋น„์Šค ๋ฉ”์‹œ (Istio, Linkerd)
๐Ÿš€ GitOps (ArgoCD, Flux)
๐Ÿš€ ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ํŒจํ„ด
๐Ÿš€ ์„ฑ๋Šฅ ํŠœ๋‹๊ณผ ์ตœ์ ํ™”

๐Ÿ“š ์ถ”์ฒœ ํ•™์Šต ์ž๋ฃŒ

๊ณต์‹ ๋ฌธ์„œ:
โ€ข Docker ๊ณต์‹ ๋ฌธ์„œ: https://docs.docker.com
โ€ข Spring Boot ๊ณต์‹ ๋ฌธ์„œ: https://spring.io/projects/spring-boot
โ€ข Kubernetes ๊ณต์‹ ๋ฌธ์„œ: https://kubernetes.io/docs

์‹ค์Šต ํ”Œ๋žซํผ:
โ€ข Play with Docker: https://labs.play-with-docker.com
โ€ข Katacoda: ๋ฌด๋ฃŒ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํ•™์Šต
โ€ข Docker Hub: ๋‹ค์–‘ํ•œ ์ด๋ฏธ์ง€ ํƒ์ƒ‰

์ปค๋ฎค๋‹ˆํ‹ฐ:
โ€ข Docker Community Forums
โ€ข Stack Overflow
โ€ข Reddit r/docker
โ€ข ํ•œ๊ตญ Docker ์‚ฌ์šฉ์ž ๋ชจ์ž„

๐Ÿ’ผ ์‹ค๋ฌด ํ”„๋กœ์ ํŠธ๋กœ ์„ฑ์žฅํ•˜๊ธฐ ์ด๋ก ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•ด! ์‹ค์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋ฐฐ์šฐ๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•์ด์•ผ. ์žฌ๋Šฅ๋„ท์—์„œ๋Š” ์‹ค๋ฌด ํ”„๋กœ์ ํŠธ ๋ฉ˜ํ† ๋ง๋ถ€ํ„ฐ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๊นŒ์ง€ ๋‹ค์–‘ํ•œ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์–ด. ํ˜ผ์ž ๊ณ ๋ฏผํ•˜์ง€ ๋ง๊ณ  ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ์„ ํƒ์ด์•ผ! ๐Ÿ’ช

๐ŸŽฌ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์ž, ์—ฌ๊ธฐ๊นŒ์ง€ Spring Boot์™€ Docker๋ฅผ ํ™œ์šฉํ•œ ์ปจํ…Œ์ด๋„ˆํ™”์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด! ์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ๋”ฐ๋ผํ•˜๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ Docker ์ „๋ฌธ๊ฐ€๊ฐ€ ๋˜์–ด ์žˆ์„ ๊ฑฐ์•ผ. ๐Ÿ˜Š

Docker๋Š” ๋‹จ์ˆœํžˆ ๋ฐฐํฌ ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ, ํ˜„๋Œ€์ ์ธ ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์˜ ํ•„์ˆ˜ ์š”์†Œ์•ผ. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์˜ ์ผ๊ด€์„ฑ, ๋น ๋ฅธ ๋ฐฐํฌ, ํšจ์œจ์ ์ธ ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ ๋“ฑ Docker๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ด์ ์€ ์ •๋ง ๋งŽ์•„.

ํŠนํžˆ Spring Boot์™€์˜ ์กฐํ•ฉ์€ Java ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์ตœ๊ณ ์˜ ์„ ํƒ์ด์•ผ. Spring Boot์˜ ๊ฐ„ํŽธํ•จ๊ณผ Docker์˜ ์ด์‹์„ฑ์ด ๋งŒ๋‚˜๋ฉด ์ •๋ง ๊ฐ•๋ ฅํ•œ ์‹œ๋„ˆ์ง€๋ฅผ ๋ฐœํœ˜ํ•˜๊ฑฐ๋“ ! ๐Ÿš€

๊ธฐ์–ตํ•ด์•ผ ํ•  ํ•ต์‹ฌ ํฌ์ธํŠธ๋“ค:

๐ŸŽฏ ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋กœ ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์ ํ™”
๐ŸŽฏ Docker Compose๋กœ ๋ฉ€ํ‹ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ
๐ŸŽฏ ๋ณด์•ˆ์„ ํ•ญ์ƒ ์ตœ์šฐ์„ ์œผ๋กœ ๊ณ ๋ ค
๐ŸŽฏ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋กœ๊น… ์‹œ์Šคํ…œ ๊ตฌ์ถ•
๐ŸŽฏ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ์ž๋™ํ™”

์ด์ œ ๋‹น์‹ ๋„ Docker ์ปจํ…Œ์ด๋„ˆ์˜ ์„ธ๊ณ„๋กœ ๋– ๋‚  ์ค€๋น„๊ฐ€ ๋์–ด! ์‹ค์Šตํ•˜๋ฉด์„œ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๊ฑฐ๋‚˜, ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•˜๊ฑฐ๋‚˜, ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์•„.

๋งˆ์ง€๋ง‰์œผ๋กœ, ํ•™์Šต์€ ๋์ด ์—†์–ด. ๊ณ„์†ํ•ด์„œ ์ƒˆ๋กœ์šด ๊ธฐ์ˆ ์„ ๋ฐฐ์šฐ๊ณ , ์‹ค์ „ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด๋ฉด์„œ ์„ฑ์žฅํ•ด๋‚˜๊ฐ€์ž! ๐Ÿ’ช

Happy Dockerizing! ๐Ÿณโœจ

Docker Journey Complete! ๐Ÿณ Docker โ˜• Spring Boot ๐Ÿš€ Deploy From Development to Production Keep Learning, Keep Growing! ๐Ÿ’ช

๐ŸŽ‰ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค!
Spring Boot์™€ Docker ์ปจํ…Œ์ด๋„ˆํ™” ์—ฌ์ •์„ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด์ œ ๋‹น์‹ ์€ ํ˜„๋Œ€์ ์ธ ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

๊ณ„์†ํ•ด์„œ ์‹ค์Šตํ•˜๊ณ , ์‹คํ—˜ํ•˜๊ณ , ์„ฑ์žฅํ•˜์„ธ์š”! ๐Ÿš€

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.