하이어코딩 RSS 태그 관리 글쓰기 방명록 mahiru
2025-07-21 19:20:17
728x90
반응형

🐳 들어가며

"내 컴퓨터에서는 잘 되는데요?"

개발자라면 누구나 한 번쯤 들어본 말입니다. Docker는 이런 문제를 완벽하게 해결해줍니다. 애플리케이션과 모든 의존성을 하나의 컨테이너로 패키징하여 어디서든 동일하게 실행할 수 있죠.

이 글에서는 Spring Boot 애플리케이션을 Docker로 컨테이너화하는 방법부터 운영 환경에서의 최적화까지, 실무에서 바로 활용할 수 있는 모든 내용을 다룹니다.

📑 목차

  1. Docker 기초 이해하기
  2. Spring Boot Dockerfile 작성
  3. 멀티 스테이지 빌드
  4. Docker Compose로 환경 구성
  5. 개발 환경 최적화
  6. 운영 환경 배포
  7. 보안 및 최적화
  8. 트러블슈팅

Docker 기초 이해하기

🎯 Docker란?

Docker는 애플리케이션을 컨테이너라는 단위로 패키징하고 실행하는 플랫폼입니다.

 
mermaid
graph LR
    A[소스 코드] --> B[Docker Image]
    B --> C[Container 1]
    B --> D[Container 2]
    B --> E[Container 3]

핵심 개념

개념설명비유

Image 읽기 전용 템플릿 설계도
Container 이미지의 실행 인스턴스 실제 건물
Dockerfile 이미지 빌드 명령서 레시피
Registry 이미지 저장소 창고
Volume 영구 데이터 저장소 외장 하드

Docker 설치

Windows/Mac

Docker Desktop 다운로드 및 설치

Linux (Ubuntu)

 
bash
# Docker 설치 스크립트
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# 사용자 권한 설정
sudo usermod -aG docker $USER
newgrp docker

# 설치 확인
docker --version
docker run hello-world

Spring Boot Dockerfile 작성

1. 기본 Dockerfile

 
dockerfile
# 기본 Dockerfile (단일 스테이지)
FROM eclipse-temurin:17-jre

# 작업 디렉토리 설정
WORKDIR /app

# JAR 파일 복사
COPY target/*.jar app.jar

# 포트 노출
EXPOSE 8080

# 실행 명령
ENTRYPOINT ["java", "-jar", "app.jar"]

2. 개선된 Dockerfile

 
dockerfile
# 개선된 Dockerfile
FROM eclipse-temurin:17-jre-alpine

# 필수 패키지 설치
RUN apk add --no-cache tzdata curl

# 시간대 설정
ENV TZ=Asia/Seoul
RUN cp /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 애플리케이션 사용자 생성
RUN addgroup -g 1000 spring && \
    adduser -D -u 1000 -G spring spring

# 작업 디렉토리 생성
WORKDIR /app

# 파일 복사 및 권한 설정
COPY --chown=spring:spring target/*.jar app.jar

# 사용자 전환
USER spring:spring

# 헬스체크 추가
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

# 포트 노출
EXPOSE 8080

# JVM 옵션과 함께 실행
ENTRYPOINT ["java", \
    "-XX:+UseContainerSupport", \
    "-XX:MaxRAMPercentage=75.0", \
    "-Djava.security.egd=file:/dev/./urandom", \
    "-jar", \
    "app.jar"]

멀티 스테이지 빌드

1. Maven 멀티 스테이지 빌드

 
dockerfile
# 1단계: 빌드 스테이지
FROM maven:3.9-eclipse-temurin-17 AS builder

# 작업 디렉토리
WORKDIR /build

# 의존성 캐싱을 위한 pom.xml 먼저 복사
COPY pom.xml .
RUN mvn dependency:go-offline -B

# 소스 코드 복사 및 빌드
COPY src ./src
RUN mvn clean package -DskipTests

# 2단계: 런타임 스테이지
FROM eclipse-temurin:17-jre-alpine

# 필수 패키지 설치
RUN apk add --no-cache tzdata

# 시간대 설정
ENV TZ=Asia/Seoul
RUN cp /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 보안을 위한 non-root 사용자
RUN addgroup -g 1000 spring && \
    adduser -D -u 1000 -G spring spring

WORKDIR /app

# 빌드 스테이지에서 JAR 파일 복사
COPY --from=builder --chown=spring:spring /build/target/*.jar app.jar

# 사용자 전환
USER spring:spring

# 메타데이터
LABEL maintainer="your-email@example.com" \
      version="1.0" \
      description="Spring Boot Application"

# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --retries=3 --start-period=40s \
    CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

EXPOSE 8080

# JVM 메모리 최적화
ENV JAVA_OPTS="-XX:+UseContainerSupport \
    -XX:MaxRAMPercentage=75.0 \
    -XX:InitialRAMPercentage=50.0 \
    -XX:+UseG1GC \
    -XX:+UseStringDeduplication"

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

2. Gradle 멀티 스테이지 빌드

 
dockerfile
# Gradle 버전
FROM gradle:8.5-jdk17 AS builder

WORKDIR /build

# Gradle 캐시 활용
COPY build.gradle settings.gradle ./
COPY gradle ./gradle
RUN gradle dependencies --no-daemon

# 소스 코드 복사 및 빌드
COPY src ./src
RUN gradle clean bootJar --no-daemon

# 런타임 스테이지 (위와 동일)
FROM eclipse-temurin:17-jre-alpine
# ... (이하 동일)

3. Spring Boot 레이어드 JAR 활용

 
dockerfile
# 레이어드 JAR를 활용한 최적화
FROM eclipse-temurin:17-jre-alpine AS builder

WORKDIR /app

# JAR 파일 복사
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar

# 레이어 추출
RUN java -Djarmode=layertools -jar app.jar extract

# 런타임 스테이지
FROM eclipse-temurin:17-jre-alpine

RUN apk add --no-cache tzdata && \
    addgroup -g 1000 spring && \
    adduser -D -u 1000 -G spring spring

WORKDIR /app

# 레이어별로 복사 (캐시 최적화)
COPY --from=builder --chown=spring:spring /app/dependencies/ ./
COPY --from=builder --chown=spring:spring /app/spring-boot-loader/ ./
COPY --from=builder --chown=spring:spring /app/snapshot-dependencies/ ./
COPY --from=builder --chown=spring:spring /app/application/ ./

USER spring:spring

EXPOSE 8080

ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

Docker Compose로 환경 구성

1. 개발 환경 구성

 
yaml
# docker-compose.yml
version: '3.8'

services:
  # Spring Boot 애플리케이션
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: spring-app
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - TZ=Asia/Seoul
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - app-network
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # MariaDB 데이터베이스
  db:
    image: mariadb:11.2
    container_name: spring-db
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: rootpass123!
      MYSQL_DATABASE: springdb
      MYSQL_USER: springuser
      MYSQL_PASSWORD: springpass123!
      TZ: Asia/Seoul
    volumes:
      - db_data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --max-connections=200

  # Redis 캐시
  redis:
    image: redis:7-alpine
    container_name: spring-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - app-network
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass redispass123!

  # Nginx 리버스 프록시
  nginx:
    image: nginx:alpine
    container_name: spring-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - ./static:/usr/share/nginx/html/static:ro
    depends_on:
      - app
    networks:
      - app-network
    restart: unless-stopped

  # Prometheus 모니터링
  prometheus:
    image: prom/prometheus:latest
    container_name: spring-prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
      - '--web.console.templates=/usr/share/prometheus/consoles'
    networks:
      - app-network
    restart: unless-stopped

  # Grafana 대시보드
  grafana:
    image: grafana/grafana:latest
    container_name: spring-grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123!
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    depends_on:
      - prometheus
    networks:
      - app-network
    restart: unless-stopped

# 볼륨 정의
volumes:
  db_data:
    driver: local
  redis_data:
    driver: local
  prometheus_data:
    driver: local
  grafana_data:
    driver: local

# 네트워크 정의
networks:
  app-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

2. 환경별 설정

 
yaml
# docker-compose.override.yml (개발 환경)
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    environment:
      - SPRING_PROFILES_ACTIVE=dev
      - DEBUG=true
    volumes:
      - ./src:/app/src:ro
      - ./target:/app/target
    ports:
      - "5005:5005"  # 디버그 포트
    command: ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]
 
yaml
# docker-compose.prod.yml (운영 환경)
version: '3.8'

services:
  app:
    image: your-registry/spring-app:${VERSION:-latest}
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - JAVA_OPTS=-Xms512m -Xmx2048m
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

3. Nginx 설정

 
nginx
# nginx/conf.d/default.conf
upstream spring-app {
    least_conn;
    server app:8080 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    server_name example.com;
    
    # HTTP를 HTTPS로 리다이렉트
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;
    
    # SSL 설정
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    
    # 보안 헤더
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    # 정적 파일 처리
    location /static/ {
        alias /usr/share/nginx/html/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
    
    # API 프록시
    location /api/ {
        proxy_pass http://spring-app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # 타임아웃 설정
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # 버퍼 설정
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }
    
    # 웹소켓 지원
    location /ws/ {
        proxy_pass http://spring-app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

개발 환경 최적화

1. 핫 리로드 설정

 
dockerfile
# Dockerfile.dev
FROM eclipse-temurin:17-jdk

RUN apt-get update && apt-get install -y \
    inotify-tools \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Maven Wrapper 복사
COPY mvnw .
COPY .mvn .mvn

# 의존성 캐싱
COPY pom.xml .
RUN ./mvnw dependency:go-offline

# 소스 코드는 볼륨으로 마운트
VOLUME ["/app/src", "/app/target"]

# 개발 모드 실행
CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.profiles=docker"]

2. 효율적인 개발 워크플로우

 
makefile
# Makefile
.PHONY: help build run stop clean logs

help:
	@echo "사용 가능한 명령어:"
	@echo "  make build   - Docker 이미지 빌드"
	@echo "  make run     - 컨테이너 실행"
	@echo "  make stop    - 컨테이너 중지"
	@echo "  make clean   - 컨테이너 및 이미지 삭제"
	@echo "  make logs    - 로그 확인"

build:
	docker-compose build --no-cache

run:
	docker-compose up -d
	@echo "애플리케이션이 http://localhost:8080 에서 실행 중입니다"

stop:
	docker-compose down

clean:
	docker-compose down -v
	docker rmi $$(docker images -q spring-app) 2>/dev/null || true

logs:
	docker-compose logs -f app

# 개발 환경 실행
dev:
	docker-compose -f docker-compose.yml -f docker-compose.override.yml up

# 운영 환경 실행
prod:
	docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 데이터베이스 백업
backup:
	docker exec spring-db mysqldump -u root -prootpass123! springdb > backup_$$(date +%Y%m%d_%H%M%S).sql

# 헬스체크
health:
	@docker-compose ps
	@echo "\n=== 헬스체크 ==="
	@curl -s http://localhost:8080/actuator/health | jq .

3. 개발용 스크립트

 
bash
#!/bin/bash
# dev.sh - 개발 환경 관리 스크립트

set -e

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

function print_usage() {
    echo "사용법: ./dev.sh [명령어]"
    echo "명령어:"
    echo "  start    - 개발 환경 시작"
    echo "  stop     - 개발 환경 중지"
    echo "  restart  - 개발 환경 재시작"
    echo "  logs     - 로그 확인"
    echo "  shell    - 컨테이너 쉘 접속"
    echo "  db       - 데이터베이스 접속"
    echo "  clean    - 전체 정리"
}

function start_dev() {
    echo -e "${GREEN}개발 환경을 시작합니다...${NC}"
    docker-compose up -d
    echo -e "${GREEN}완료! http://localhost:8080${NC}"
}

function stop_dev() {
    echo -e "${YELLOW}개발 환경을 중지합니다...${NC}"
    docker-compose down
}

function show_logs() {
    docker-compose logs -f app
}

function enter_shell() {
    docker-compose exec app /bin/sh
}

function enter_db() {
    docker-compose exec db mysql -u springuser -pspringpass123! springdb
}

function clean_all() {
    echo -e "${RED}모든 컨테이너와 볼륨을 삭제합니다. 계속하시겠습니까? (y/N)${NC}"
    read -r response
    if [[ "$response" =~ ^[Yy]$ ]]; then
        docker-compose down -v --rmi all
        echo -e "${GREEN}정리 완료!${NC}"
    fi
}

case "$1" in
    start)
        start_dev
        ;;
    stop)
        stop_dev
        ;;
    restart)
        stop_dev
        start_dev
        ;;
    logs)
        show_logs
        ;;
    shell)
        enter_shell
        ;;
    db)
        enter_db
        ;;
    clean)
        clean_all
        ;;
    *)
        print_usage
        exit 1
        ;;
esac

운영 환경 배포

1. CI/CD 파이프라인

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

on:
  push:
    branches: [ main, develop ]
    tags: [ 'v*' ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      
    steps:
    - name: Checkout
      uses: actions/checkout@v3
      
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        
    - name: Cache Maven dependencies
      uses: actions/cache@v3
      with:
        path: ~/.m2
        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
        restore-keys: ${{ runner.os }}-m2
        
    - name: Build with Maven
      run: mvn clean package -DskipTests
      
    - name: Run tests
      run: mvn test
      
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2
      
    - name: Log in to Container Registry
      uses: docker/login-action@v2
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
        
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=semver,pattern={{version}}
          type=semver,pattern={{major}}.{{minor}}
          type=sha,prefix={{branch}}-
          
    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
        build-args: |
          BUILD_DATE=${{ github.event.repository.updated_at }}
          VCS_REF=${{ github.sha }}

2. Kubernetes 배포

 
yaml
# k8s/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: ghcr.io/yourusername/spring-app:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "k8s"
        - name: DB_HOST
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: host
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 5
        volumeMounts:
        - name: app-config
          mountPath: /app/config
          readOnly: true
      volumes:
      - name: app-config
        configMap:
          name: app-config
---
apiVersion: v1
kind: Service
metadata:
  name: spring-app-service
spec:
  selector:
    app: spring-app
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer

3. Docker Swarm 배포

 
yaml
# docker-stack.yml
version: '3.8'

services:
  app:
    image: ghcr.io/yourusername/spring-app:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
      placement:
        constraints:
          - node.role == worker
    networks:
      - app-net
    secrets:
      - db_password
      - app_secret
    configs:
      - source: app_config
        target: /app/application.yml
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3

networks:
  app-net:
    driver: overlay
    
secrets:
  db_password:
    external: true
  app_secret:
    external: true
    
configs:
  app_config:
    external: true

배포 명령:

 
bash
# Docker Swarm 초기화
docker swarm init

# 시크릿 생성
echo "mydbpass" | docker secret create db_password -
echo "myappsecret" | docker secret create app_secret -

# 설정 생성
docker config create app_config ./application-prod.yml

# 스택 배포
docker stack deploy -c docker-stack.yml myapp

# 서비스 확인
docker service ls
docker service ps myapp_app

보안 및 최적화

1. 이미지 보안 스캔

 
bash
# Trivy를 사용한 취약점 스캔
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image your-image:tag

# Dockerfile 린트
docker run --rm -i hadolint/hadolint < Dockerfile

2. 이미지 크기 최적화

 
dockerfile
# 최적화된 Dockerfile
FROM eclipse-temurin:17-jre-alpine AS runtime

# 불필요한 파일 제거
RUN apk add --no-cache tzdata && \
    cp /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
    echo "Asia/Seoul" > /etc/timezone && \
    apk del tzdata

# distroless 이미지 사용 (더 작은 크기)
FROM gcr.io/distroless/java17-debian11

COPY --from=runtime /etc/localtime /etc/localtime
COPY --from=runtime /etc/timezone /etc/timezone

WORKDIR /app
COPY --chown=nonroot:nonroot target/*.jar app.jar

USER nonroot

EXPOSE 8080

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

3. 보안 설정

 
yaml
# docker-compose.security.yml
version: '3.8'

services:
  app:
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    read_only: true
    tmpfs:
      - /tmp
    user: "1000:1000"

4. 리소스 제한

 
yaml
# docker-compose.resources.yml
version: '3.8'

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
    ulimits:
      nofile:
        soft: 65536
        hard: 65536

트러블슈팅

1. 빌드 관련 문제

문제: 빌드 캐시로 인한 문제

 
bash
# 캐시 없이 빌드
docker build --no-cache -t myapp .

# 모든 빌드 캐시 삭제
docker builder prune -a

문제: 의존성 다운로드 실패

 
dockerfile
# 타임아웃 증가
RUN mvn dependency:go-offline -B \
    -Dmaven.wagon.http.timeout=120000 \
    -Dmaven.wagon.http.retryHandler.count=3

2. 실행 관련 문제

문제: 메모리 부족

 
bash
# 컨테이너 메모리 사용량 확인
docker stats

# JVM 힙 덤프 생성
docker exec <container-id> jcmd 1 GC.heap_dump /tmp/heapdump.hprof
docker cp <container-id>:/tmp/heapdump.hprof .

문제: 포트 충돌

 
bash
# 사용 중인 포트 확인
sudo lsof -i :8080
netstat -tulpn | grep :8080

# 다른 포트로 실행
docker run -p 8081:8080 myapp

3. 네트워크 문제

문제: 컨테이너 간 통신 불가

 
bash
# 네트워크 확인
docker network ls
docker network inspect bridge

# 컨테이너 네트워크 확인
docker inspect <container> | grep -i network

# ping 테스트
docker exec app ping db

4. 디버깅 팁

 
bash
# 실행 중인 컨테이너에 접속
docker exec -it <container> /bin/sh

# 로그 실시간 확인
docker logs -f <container>

# 특정 시간 이후 로그만 보기
docker logs --since 10m <container>

# 컨테이너 상세 정보
docker inspect <container>

# 프로세스 확인
docker top <container>

# 파일 시스템 변경 사항 확인
docker diff <container>

마무리

Docker는 현대 소프트웨어 개발의 필수 도구입니다. 이 가이드를 통해 Spring Boot 애플리케이션을 효과적으로 컨테이너화하고 배포할 수 있기를 바랍니다.

핵심 체크리스트

  • ✅ 멀티 스테이지 빌드로 이미지 크기 최적화
  • ✅ 보안을 위한 non-root 사용자 실행
  • ✅ 헬스체크 구성
  • ✅ 환경별 설정 분리
  • ✅ CI/CD 파이프라인 구축
  • ✅ 모니터링 및 로깅 설정

다음 단계

  • Kubernetes 오케스트레이션
  • 서비스 메시 (Istio/Linkerd)
  • GitOps (ArgoCD/Flux)
  • 카나리 배포 전략

태그: #Docker #SpringBoot #DevOps #Containerization #Kubernetes

728x90
반응형
이 페이지는 리디주식회사에서 제공한 리디바탕 글꼴이 사용되어 있습니다.