
MySQL, Redis, Spring Boot를 모두 Docker Compose로 통합 관리하고 리소스 제한 적용.
Docker Compose는 여러 개의 Docker 컨테이너를 하나의 YAML 파일로 정의하고 관리하는 도구.
docker-compose.yml (설계도)
↓
한 번의 명령어로
↓
MySQL + Redis + Spring Boot
모두 시작/중지 가능
| 목적 | 설명 |
|---|---|
| 멀티 컨테이너 관리 | 여러 컨테이너를 하나의 명령으로 제어 |
| 설정 문서화 | YAML 파일로 인프라 구조 명시 |
| 재현 가능성 | 누구나 동일한 환경 구축 가능 |
| 의존성 관리 | 컨테이너 시작 순서 제어 (depends_on) |
| 리소스 제한 | CPU/Memory 제한을 YAML로 관리 |
# 1. MySQL 컨테이너 시작
docker run -d --name mysql-1 \
-e MYSQL_ROOT_PASSWORD=lldj123414 \
-e MYSQL_DATABASE=DevCourse \
-p 3306:3306 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
# 2. Redis 컨테이너 시작
docker run -d --name redis-auction \
-p 6379:6379 \
-v redis-data:/data \
redis:7-alpine
# 3. Spring Boot 컨테이너 시작
docker run -d --name auction-app \
-p 8080:8080 \
-e SPRING_DATASOURCE_URL="jdbc:mysql://host.docker.internal:3306/DevCourse?..." \
-e SPRING_DATASOURCE_USERNAME=root \
-e SPRING_DATASOURCE_PASSWORD=lldj123414 \
-e SPRING_DATA_REDIS_HOST=host.docker.internal \
-e SPRING_DATA_REDIS_PORT=6379 \
--cpus=2.0 \
--memory=980m \
-v uploads-data:/app/uploads \
auction-app:latest
# 4. 중지하려면?
docker stop auction-app mysql-1 redis-auction
docker rm auction-app mysql-1 redis-auction
문제점:
# docker-compose.yml
services:
mysql:
image: mysql:8.0
# ... 설정
redis:
image: redis:7-alpine
# ... 설정
app:
build: .
# ... 설정
# 시작
docker-compose up -d
# 중지
docker-compose down
장점:
┌─────────────────────────────────────────────────┐
│ Host Machine (맥북/Windows) │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ Docker Compose 관리 영역 │ │
│ │ (auction-network) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ auction-mysql-compose │ │ │
│ │ │ - 호스트: localhost:3307 │ │ │
│ │ │ - 내부: mysql:3306 │ │ │
│ │ │ - CPU: 2.0, Memory: 980MB ✅ │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ │ ↕ (내부 네트워크) │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ auction-app │ │ │
│ │ │ - 호스트: localhost:8080 │ │ │
│ │ │ - 연결: mysql:3306, redis:6379 │ │ │
│ │ │ - CPU: 2.0, Memory: 980MB ✅ │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ │ ↕ (내부 네트워크) │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ auction-redis-compose │ │ │
│ │ │ - 호스트: localhost:6380 │ │ │
│ │ │ - 내부: redis:6379 │ │ │
│ │ │ - CPU: 2.0, Memory: 510MB ✅ │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Docker 이미지는 공유 가능하며, 컨테이너는 이미지를 실행한 인스턴스.
┌────────────────────────────────────────────┐
│ Docker Hub (온라인 저장소) │
│ ├─ mysql:8.0 (이미지) │
│ └─ redis:7-alpine (이미지) │
└────────────────────────────────────────────┘
↓ docker pull (다운로드)
┌────────────────────────────────────────────┐
│ 로컬 이미지 저장소 │
│ ├─ mysql:8.0 (이미지) ✅ │
│ └─ redis:7-alpine (이미지) ✅ │
└────────────────────────────────────────────┘
↓ docker run (실행)
┌────────────────────────────────────────────┐
│ 실행 중인 컨테이너 │
│ ├─ mysql-1 (mysql:8.0의 인스턴스) │
│ └─ redis-auction (redis:7-alpine 인스턴스)│
└────────────────────────────────────────────┘
# 1. 이미지 다운로드 (자동 또는 수동)
docker pull mysql:8.0
# 2. 컨테이너 실행
docker run -d --name mysql-1 -p 3306:3306 mysql:8.0
결과:
mysql:8.0 이미지 저장됨 ✅mysql-1 컨테이너 실행 중# docker-compose.yml
services:
mysql:
image: mysql:8.0 # ← 로컬에 이미 있는 이미지 사용!
# ... 설정
docker-compose up -d
Docker Compose가 하는 일:
1. docker-compose.yml 읽기
2. "mysql:8.0 이미지가 필요하구나"
3. 로컬 이미지 저장소 확인
→ 있으면: 그대로 사용 ✅
→ 없으면: Docker Hub에서 다운로드
4. 새로운 컨테이너 생성 (auction-mysql-compose)
5. 설정대로 실행 (포트, 환경 변수, 리소스 제한 등)
이미지는 재사용 가능!
같은 이미지 (mysql:8.0)를
여러 번 실행하여
여러 개의 컨테이너를 만들 수 있음
예시:
mysql:8.0 이미지
├─ mysql-1 컨테이너 (기존)
├─ auction-mysql-compose 컨테이너 (새로 생성)
└─ test-mysql 컨테이너 (필요하면 또 생성 가능)
비유:
이미지 = 게임 설치 파일 (ISO, 앱스토어 앱)
컨테이너 = 실행 중인 게임 프로세스
같은 게임을 여러 번 실행할 수 있듯이,
같은 이미지로 여러 컨테이너를 만들 수 있음!
# 로컬에 있는 이미지 확인
docker images
# 출력 예시:
# REPOSITORY TAG IMAGE ID CREATED SIZE
# mysql 8.0 abc123... 2 weeks ago 514MB ← 이미 다운로드됨!
# redis 7-alpine def456... 3 weeks ago 30MB ← 이미 다운로드됨!
Compose 실행 시:
docker-compose up -d
# 출력:
# [+] Running 3/3
# ✔ Container auction-mysql-compose Started (이미지 재사용)
# ✔ Container auction-redis-compose Started (이미지 재사용)
# ✔ Container auction-app Started (새로 빌드)
# 프로젝트 루트로 이동
cd ~/Desktop/DevCource_2
# docker-compose.yml 생성
touch docker-compose.yml
# 편집기로 열기
nano docker-compose.yml
# 또는
code docker-compose.yml # VS Code 사용 시
# 프로젝트 루트로 이동
cd C:\Users\YourName\Desktop\DevCource_2
# docker-compose.yml 생성
New-Item -Path . -Name "docker-compose.yml" -ItemType "file"
# 편집기로 열기
notepad docker-compose.yml
# 또는
code docker-compose.yml
아래 내용을 복사하여 붙여넣기:
services:
# ====================================
# MySQL 데이터베이스 (Docker Compose용)
# ====================================
mysql:
image: mysql:8.0
container_name: auction-mysql-compose
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD:-lldj123414}
MYSQL_DATABASE: ${MYSQL_DATABASE:-DevCourse}
TZ: Asia/Seoul
ports:
- "3307:3306" # 기존 mysql-1(3306)과 충돌 방지
volumes:
- mysql-compose-data:/var/lib/mysql
networks:
- auction-network
deploy:
resources:
limits:
cpus: '2.0' # AWS RDS db.t3.micro 스펙
memory: 980M
reservations:
cpus: '1.0'
memory: 512M
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_PASSWORD:-lldj123414}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max-connections=200
# ====================================
# Redis 캐시 (Docker Compose용)
# ====================================
redis:
image: redis:7-alpine
container_name: auction-redis-compose
restart: unless-stopped
ports:
- "6380:6379" # 기존 redis-auction(6379)과 충돌 방지
volumes:
- redis-compose-data:/data
networks:
- auction-network
deploy:
resources:
limits:
cpus: '2.0' # AWS ElastiCache cache.t3.micro 스펙
memory: 510M
reservations:
cpus: '0.5'
memory: 256M
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
command: redis-server --maxmemory 450mb --maxmemory-policy allkeys-lru
# ====================================
# Spring Boot 애플리케이션
# ====================================
app:
build:
context: .
dockerfile: Dockerfile
container_name: auction-app
restart: unless-stopped
environment:
# Spring Profile
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-dev}
# MySQL 연결 정보 (같은 네트워크의 컨테이너)
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/${MYSQL_DATABASE:-DevCourse}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD:-lldj123414}
# Redis 연결 정보 (같은 네트워크의 컨테이너)
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: 6379
# JPA 설정
SPRING_JPA_HIBERNATE_DDL_AUTO: ${JPA_DDL_AUTO:-update}
SPRING_JPA_SHOW_SQL: ${JPA_SHOW_SQL:-true}
# 파일 업로드
FILE_UPLOAD_DIR: /app/uploads
# 타임존
TZ: Asia/Seoul
ports:
- "8080:8080"
volumes:
- uploads-data:/app/uploads
networks:
- auction-network
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
deploy:
resources:
limits:
cpus: '2.0' # AWS EC2 t3.micro 스펙
memory: 980M
reservations:
cpus: '1.0'
memory: 512M
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
# ====================================
# Docker 네트워크
# ====================================
networks:
auction-network:
driver: bridge
# ====================================
# Docker 볼륨 (데이터 영구 저장)
# ====================================
volumes:
mysql-compose-data:
driver: local
redis-compose-data:
driver: local
uploads-data:
driver: local
저장:
Ctrl + X → Y → EnterCtrl + S하드코딩 (나쁜 예):
environment:
MYSQL_PASSWORD: ~~~ # 비밀번호 노출
환경 변수 사용 (좋은 예):
environment:
MYSQL_PASSWORD: ${MYSQL_PASSWORD} # .env에서 읽음
장점:
cd ~/Desktop/DevCource_2
touch .env
nano .env
cd C:\Users\YourName\Desktop\DevCource_2
New-Item -Path . -Name ".env" -ItemType "file"
notepad .env
# ====================================
# Docker Compose 환경 변수
# ====================================
# Spring Profile (dev, test, prod)
SPRING_PROFILES_ACTIVE=dev
# MySQL 설정
MYSQL_DATABASE=DevCourse
MYSQL_PASSWORD=lldj123414
# JPA 설정
JPA_DDL_AUTO=update
JPA_SHOW_SQL=true
저장
.gitignore에 추가 (필수):
# .gitignore 확인
cat .gitignore | grep .env
# 없으면 추가
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore
# Git 상태 확인 (.env가 untracked여야 함)
git status
image: mysql:8.0
역할:
왜 8.0?
container_name: auction-mysql-compose
역할:
왜 -compose 접미사?
mysql-1 컨테이너와 구분ports:
- "3307:3306"
역할:
왜 3307?
mysql-1 컨테이너가 3306 포트 사용 중중요 개념:
호스트(맥북)에서 접속:
mysql -h localhost -P 3307 -u root -p
Spring Boot(컨테이너 내부)에서 접속:
jdbc:mysql://mysql:3306/DevCourse
↑ 내부 네트워크에서는 3306 사용!
volumes:
- mysql-compose-data:/var/lib/mysql
역할:
Volume 이름:
mysql-compose-data: Compose 전용 Volume (기존 mysql-data와 분리)deploy:
resources:
limits:
cpus: '2.0'
memory: 980M
reservations:
cpus: '1.0'
memory: 512M
역할:
limits vs reservations:
| 항목 | limits | reservations |
|---|---|---|
| 의미 | 최대 사용량 | 최소 보장량 |
| 초과 시 | 제한됨 (스로틀링, OOMKilled) | - |
| 적용 | 항상 | 리소스 부족 시 우선 할당 |
왜 980M?
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_PASSWORD:-lldj123414}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
역할:
옵션:
interval: 10초마다 체크timeout: 5초 이내 응답 없으면 실패retries: 5번 연속 실패 시 unhealthystart_period: 시작 후 30초는 실패해도 OK왜 필요?
depends_on)ports:
- "6380:6379"
왜 6380?
redis-auction 컨테이너가 6379 포트 사용 중command: redis-server --maxmemory 450mb --maxmemory-policy allkeys-lru
역할:
옵션:
--maxmemory 450mb: 최대 메모리 사용량 제한--maxmemory-policy allkeys-lru: 메모리 초과 시 가장 오래된 키 삭제왜 450MB?
build:
context: .
dockerfile: Dockerfile
역할:
다른 서비스와의 차이:
image)build)environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/...
SPRING_DATA_REDIS_HOST: redis
중요:
mysql:3306 ← 컨테이너 서비스 이름 + 내부 포트redis ← 컨테이너 서비스 이름Docker Compose의 DNS 자동 해석:
services:
mysql: ← 이 이름이 DNS로 등록됨
redis: ← 이 이름이 DNS로 등록됨
app: ← 이 컨테이너에서 mysql, redis 이름으로 접근 가능
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
역할:
healthy 상태가 되어야 Spring Boot 시작시작 순서:
1. MySQL 시작 → healthcheck 통과 (30초 정도)
2. Redis 시작 → healthcheck 통과 (5초 정도)
3. Spring Boot 시작 ✅
networks:
auction-network:
driver: bridge
역할:
┌──────────────────────────────────────────┐
│ auction-network (Docker 네트워크) │
│ │
│ ┌─────────────────────────────────┐ │
│ │ auction-app │ │
│ │ IP: 172.18.0.4 │ │
│ │ │ │
│ │ mysql:3306 연결 요청 │ │
│ └───────────┬─────────────────────┘ │
│ │ │
│ │ Docker DNS 해석 │
│ │ "mysql" → 172.18.0.2 │
│ ↓ │
│ ┌─────────────────────────────────┐ │
│ │ auction-mysql-compose │ │
│ │ IP: 172.18.0.2 │ │
│ │ 포트: 3306 리스닝 │ │
│ └─────────────────────────────────┘ │
└──────────────────────────────────────────┘
"호스트는 3307인데, 내부에서는 3306을 사용할 수 있는 이유는?"
각 컨테이너는 독립된 네트워크 네임스페이스
┌─────────────────────────────────────────┐
│ Host (맥북) │
│ 포트 3307, 6380 열림 │
│ │
│ ┌───────────────────────────────────┐ │
│ │ auction-mysql-compose │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ 독립된 네트워크 공간 │ │ │
│ │ │ MySQL: 포트 3306 리스닝 ✅ │ │ │
│ │ │ (이 컨테이너 안에서만 3306) │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ ↕ 포트 매핑 │ │
│ │ 호스트 포트: 3307 │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────┐ │
│ │ auction-redis-compose │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ 독립된 네트워크 공간 │ │ │
│ │ │ Redis: 포트 6379 리스닝 ✅ │ │ │
│ │ │ (이 컨테이너 안에서만 6379) │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ ↕ 포트 매핑 │ │
│ │ 호스트 포트: 6380 │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
Docker는 Linux 네임스페이스를 사용하여 격리.
각 컨테이너는 독립된 네임스페이스를 가짐:
1. NET 네임스페이스: 네트워크 인터페이스 격리 ← 포트 격리!
2. PID 네임스페이스: 프로세스 ID 격리
3. MNT 네임스페이스: 파일 시스템 격리
4. IPC 네임스페이스: 프로세스 간 통신 격리
5. UTS 네임스페이스: 호스트 이름 격리
NET 네임스페이스 덕분에:
맥북 터미널:
mysql -h localhost -P 3307 -u root -p
↓ Docker Bridge 포트 매핑
auction-mysql-compose 컨테이너 내부:
MySQL 프로세스 (포트 3306 리스닝) ✅
auction-app 컨테이너:
jdbc:mysql://mysql:3306/DevCourse
↓ Docker DNS 해석
auction-mysql-compose 컨테이너:
IP: 172.18.0.2, 포트: 3306 ✅
같은 MySQL 8.0 이미지에서 만든 두 컨테이너가 동시에 3306 포트를 사용 중
mysql-1 (기존):
- 내부 포트: 3306
- 호스트 포트: 3306
- IP: 172.17.0.2
auction-mysql-compose (새로):
- 내부 포트: 3306
- 호스트 포트: 3307
- IP: 172.18.0.2
→ 충돌 없음! 각자 독립된 네트워크 공간! ✅
비유:
같은 건물에 두 개의 독립된 사무실:
1층 (mysql-1):
- 내선번호: 3306
2층 (auction-mysql-compose):
- 내선번호: 3306 (1층과 동일해도 OK!)
외부 전화:
- 1층 연결: 02-1234-3306
- 2층 연결: 02-1234-3307
→ 내선은 같아도 외부 번호가 다르면 충돌 없음!
# 프로젝트 루트로 이동
cd ~/Desktop/DevCource_2
# 전체 스택 시작 (빌드 + 시작)
docker-compose up --build -d
# 로그 확인 (실시간)
docker-compose logs -f
# 프로젝트 루트로 이동
cd C:\Users\YourName\Desktop\DevCource_2
# 전체 스택 시작
docker-compose up --build -d
# 로그 확인
docker-compose logs -f
# 컨테이너 상태
docker-compose ps
# 출력 예시:
# NAME STATUS PORTS
# auction-app Up 2 min (healthy) 0.0.0.0:8080->8080/tcp
# auction-mysql-compose Up 3 min (healthy) 0.0.0.0:3307->3306/tcp
# auction-redis-compose Up 3 min (healthy) 0.0.0.0:6380->6379/tcp
# Spring Boot API 테스트
curl http://localhost:8080/actuator/health
# 출력: {"status":"UP"}
# 실시간 리소스 모니터링
docker stats
# 출력 예시:
# CONTAINER CPU % MEM USAGE / LIMIT
# auction-app 5.23% 450MiB / 980MiB
# auction-mysql-compose 2.15% 620MiB / 980MiB
# auction-redis-compose 0.84% 12MiB / 510MiB
확인 포인트:
# 전체 재시작
docker-compose restart
# 특정 컨테이너만
docker-compose restart app
# 중지 (데이터 유지)
docker-compose stop
# 삭제 (Volume 유지)
docker-compose down
# 완전 삭제 (데이터도 삭제)
docker-compose down -v
# Spring Boot만 재빌드
docker-compose build app
docker-compose up -d app
# 전체 재빌드
docker-compose up --build -d