🐳 Docker + AWS 배포 완전 가이드 (초보자도 OK!)
🎯 오늘의 목표
전체 시스템을 Docker로 컨테이너화하고 AWS EC2에 배포하기!
✨ 완성 후 모습
- ✅ Spring Boot + Python + MySQL 한 번에 실행
- ✅
docker-compose up 명령어 하나로 끝
- ✅ AWS EC2에서 실제 서비스 운영
- ✅ 환경 변수로 민감 정보 관리
- ✅ 데이터 영구 저장 (볼륨)
결과: 진짜 서비스 배포 완료! 🚀
📚 Docker가 뭔가요?
간단하게 말하면
"내 컴퓨터에서는 되는데 서버에서는 안 돼요!" 문제 해결사
비유로 설명
전통 방식 (너무 복잡함):
1. 서버에 Java 17 설치
2. 서버에 Python 3.11 설치
3. 서버에 MySQL 설치
4. 환경 변수 100개 설정
5. 라이브러리 하나씩 설치
6. 버전 충돌 해결...
7. 포기 😭
Docker 방식 (간단함):
1. docker-compose up
2. 끝! 🎉
핵심 용어 정리
| 용어 |
설명 |
쉬운 비유 |
| Image |
실행 가능한 패키지 |
설치 파일 (.exe) |
| Container |
실행 중인 Image |
실행 중인 프로그램 |
| Dockerfile |
Image 만드는 레시피 |
요리 레시피 |
| docker-compose |
여러 Container 동시 실행 |
세트 메뉴 주문 |
| Volume |
데이터 영구 저장 공간 |
외장 하드 |
우리가 만들 구조
┌─────────────────────────────────────────┐
│ docker-compose.yml │
│ (전체 오케스트레이션) │
└─────────┬───────────┬───────────────────┘
│ │
┌─────▼─────┐ ┌──▼──────────┐ ┌──────────┐
│ MySQL │ │ Spring Boot │ │ Python │
│ Container │ │ Container │ │Container │
│ (3306) │ │ (8080) │ │ (8000) │
└───────────┘ └─────────────┘ └──────────┘
│ │ │
┌────▼──────────────▼───────────────▼────┐
│ brainstorm-network (Docker) │
└─────────────────────────────────────────┘
3개 컨테이너가 하나의 네트워크에서 서로 통신!
🛠️ 1. Dockerfile 작성 (Spring Boot)
왜 Dockerfile이 필요한가요?
Docker Image를 만드는 설명서!
"어떻게 내 앱을 실행할 건지"를 Docker에게 알려주는 파일
Spring Boot Dockerfile
경로: 프로젝트 루트/Dockerfile
# ==========================================
# 1단계: 빌드 (Gradle로 JAR 파일 생성)
# ==========================================
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
# Gradle 파일 먼저 복사 (캐싱 최적화)
COPY build.gradle settings.gradle ./
COPY gradle ./gradle
# 소스 코드 복사
COPY src ./src
# JAR 빌드 (테스트 스킵해서 시간 단축)
RUN gradle build -x test --no-daemon
# ==========================================
# 2단계: 실행 (가벼운 JRE 이미지)
# ==========================================
FROM openjdk:17-jdk-slim
WORKDIR /app
# 1단계에서 빌드한 JAR만 복사
COPY --from=builder /app/build/libs/*.jar app.jar
# 포트 노출
EXPOSE 8080
# 실행 명령어
ENTRYPOINT ["java", "-jar", "app.jar"]
각 줄 상세 설명
FROM gradle:8.5-jdk17 AS builder
FROM: 베이스 이미지 선택
gradle:8.5-jdk17: Gradle 8.5 + Java 17 포함
AS builder: 이 단계를 "builder"라고 이름 붙임
WORKDIR /app
- 컨테이너 안에서
/app 폴더를 작업 공간으로 설정
- 이후 모든 명령어는 이 폴더에서 실행됨
COPY build.gradle settings.gradle ./
COPY gradle ./gradle
- Gradle 설정 파일을 먼저 복사
- 왜? 소스 코드 변경돼도 이 부분은 캐시 재사용 가능!
- 빌드 시간 엄청 단축됨 💨
RUN gradle build -x test --no-daemon
- JAR 파일 빌드
-x test: 테스트 스킵 (시간 절약)
--no-daemon: Gradle 데몬 비활성화 (메모리 절약)
FROM openjdk:17-jdk-slim
- 새로운 단계 시작!
slim: 최소한의 JRE만 포함 (이미지 크기 감소)
- 빌드 도구(Gradle)는 버림 → 이미지 가벼워짐!
COPY --from=builder /app/build/libs/*.jar app.jar
--from=builder: 1단계에서 만든 JAR 파일만 가져옴
- 빌드 도구, 소스 코드 등은 안 가져옴
- 멀티 스테이지 빌드의 핵심!
ENTRYPOINT ["java", "-jar", "app.jar"]
- 컨테이너 시작 시 실행할 명령어
CMD와 차이: 덮어쓰기 불가 (더 안전)
💡 멀티 스테이지 빌드의 장점
일반 빌드:
- Gradle (100MB)
- 소스 코드 (50MB)
- 빌드된 JAR (30MB)
───────────────────
총 이미지 크기: 180MB
멀티 스테이지 빌드:
- JRE (70MB)
- 빌드된 JAR (30MB)
───────────────────
총 이미지 크기: 100MB
45% 감소! 🎉
🐍 2. Dockerfile 작성 (Python FastAPI)
경로: python-service/Dockerfile
# Python 3.11 슬림 이미지
FROM python:3.11-slim
WORKDIR /app
# 시스템 패키지 업데이트 및 빌드 도구 설치
RUN apt-get update && apt-get install -y \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
# requirements.txt 먼저 복사 (캐싱 최적화)
COPY requirements.txt .
# Python 패키지 설치
RUN pip install --no-cache-dir -r requirements.txt
# 소스 코드 복사
COPY . .
# 포트 노출
EXPOSE 8000
# 실행 명령어
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
각 줄 상세 설명
FROM python:3.11-slim
- Python 3.11 최소 버전 이미지
slim: 불필요한 패키지 제거된 버전
RUN apt-get update && apt-get install -y build-essential curl
build-essential: C 컴파일러 등 빌드 도구
- 일부 Python 패키지(numpy 등)가 C로 작성되어 필요
curl: 헬스체크용
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
requirements.txt만 먼저 복사
- 소스 코드 변경돼도 pip install 캐시 재사용!
RUN pip install --no-cache-dir -r requirements.txt
--no-cache-dir: pip 캐시 안 남김 (이미지 크기 감소)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
--host 0.0.0.0: 매우 중요!
- 기본값
127.0.0.1은 컨테이너 내부만 접근 가능
0.0.0.0으로 해야 외부에서 접근 가능!
⚠️ 삽질 1: 127.0.0.1 vs 0.0.0.0
문제:
curl http://localhost:8000
# curl: (7) Failed to connect
원인:
# ❌ 이렇게 하면 외부 접근 불가
uvicorn.run("main:app", host="127.0.0.1")
# ✅ 이렇게 해야 함
uvicorn.run("main:app", host="0.0.0.0")
Docker 컨테이너는 격리된 환경!
127.0.0.1은 컨테이너 내부만 의미함!
🎼 3. docker-compose.yml 작성 (핵심!)
경로: 프로젝트 루트/docker-compose.yml
version: '3.8'
services:
# ==========================================
# MySQL 데이터베이스
# ==========================================
mysql:
image: mysql:8.0
container_name: brainstorm-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: brainstorm
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- brainstorm-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# ==========================================
# Python FastAPI (브레인스토밍 엔진)
# ==========================================
python-service:
build:
context: ./python-service
dockerfile: Dockerfile
container_name: brainstorm-python
restart: always
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- LLM_MODEL=${LLM_MODEL:-gpt-4o}
- EMBEDDING_MODEL=${EMBEDDING_MODEL:-text-embedding-3-large}
ports:
- "8000:8000"
networks:
- brainstorm-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# ==========================================
# Spring Boot (메인 백엔드)
# ==========================================
spring-boot:
build:
context: .
dockerfile: Dockerfile
container_name: brainstorm-spring
restart: always
depends_on:
mysql:
condition: service_healthy
python-service:
condition: service_healthy
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/brainstorm?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
- SPRING_DATASOURCE_USERNAME=${MYSQL_USER}
- SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD}
- JWT_SECRET=${JWT_SECRET}
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
- KAKAO_CLIENT_ID=${KAKAO_CLIENT_ID}
- KAKAO_CLIENT_SECRET=${KAKAO_CLIENT_SECRET}
- NAVER_CLIENT_ID=${NAVER_CLIENT_ID}
- NAVER_CLIENT_SECRET=${NAVER_CLIENT_SECRET}
- PYTHON_API_URL=http://python-service:8000
ports:
- "8080:8080"
networks:
- brainstorm-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
# ==========================================
# 볼륨 (데이터 영구 저장)
# ==========================================
volumes:
mysql-data:
driver: local
# ==========================================
# 네트워크 (컨테이너 간 통신)
# ==========================================
networks:
brainstorm-network:
driver: bridge
핵심 포인트 설명
1. restart: always
restart: always
- 컨테이너 종료되면 자동 재시작
- 서버 재부팅해도 자동으로 다시 실행됨!
- 운영 환경 필수!
2. depends_on (중요! 🔥)
depends_on:
mysql:
condition: service_healthy
- MySQL이 완전히 준비될 때까지 대기
condition: service_healthy: healthcheck 성공해야 시작
- 순서: MySQL → Python → Spring Boot
왜 필요한가?
없으면:
MySQL 시작 중...
Spring Boot 시작! → MySQL 연결 시도 → 실패! 💥
있으면:
MySQL 시작 중...
MySQL healthcheck 성공!
Spring Boot 시작! → MySQL 연결 성공 ✅
3. healthcheck (생명줄!)
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s # 10초마다 체크
timeout: 5s # 5초 안에 응답 없으면 실패
retries: 5 # 5번 실패하면 unhealthy
- 컨테이너가 진짜 준비됐는지 확인
- 프로세스 실행 != 서비스 준비 완료
예시:
MySQL 프로세스 시작: 1초
MySQL 완전 준비: 10초
healthcheck 없으면:
→ 1초 후 Spring Boot 시작
→ MySQL 연결 실패 💥
healthcheck 있으면:
→ 10초 후 MySQL healthy
→ Spring Boot 시작
→ 연결 성공 ✅
4. environment (환경 변수)
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/...
mysql은 컨테이너 이름
- Docker 네트워크에서 자동으로 IP 해석
localhost 쓰면 안 됨! (컨테이너 자기 자신 의미)
네트워크 매직:
Spring Boot 컨테이너에서:
jdbc:mysql://mysql:3306/brainstorm
└─ 이름
Docker가 자동으로:
jdbc:mysql://172.18.0.2:3306/brainstorm
└─ MySQL 컨테이너 IP
5. volumes (데이터 영구 저장)
volumes:
- mysql-data:/var/lib/mysql
- MySQL 데이터를 호스트에 저장
- 컨테이너 삭제해도 데이터 유지!
비유:
volumes 없으면:
컨테이너 = 노트북 RAM
전원 끄면 다 날아감 💥
volumes 있으면:
컨테이너 = 노트북 RAM
volumes = 외장 하드
전원 끄면 RAM은 날아가지만 하드는 남음 ✅
6. networks (컨테이너 통신)
networks:
- brainstorm-network
- 모든 컨테이너가 같은 네트워크에 연결
- 이름으로 서로 찾을 수 있음
- 외부 네트워크와 격리됨 (보안!)
네트워크 구조:
┌─────────────────────────────────────────┐
│ brainstorm-network (내부) │
│ ┌────────┐ ┌───────────┐ ┌────────┐ │
│ │ MySQL │ │ Spring │ │ Python │ │
│ │ 3306 │◄─┤ 8080 │◄─┤ 8000 │ │
│ └────────┘ └───────────┘ └────────┘ │
│ │ │ │ │
└───────┼────────────┼─────────────┼──────┘
│ │ │
└────────────▼─────────────┘
호스트 포트 매핑
(3306, 8080, 8000)
🔐 4. 환경 변수 파일 (.env)
경로: 프로젝트 루트/.env
# ==========================================
# MySQL 설정
# ==========================================
MYSQL_ROOT_PASSWORD=your_super_secret_root_password
MYSQL_USER=brainstorm
MYSQL_PASSWORD=your_mysql_password_here
# ==========================================
# JWT 토큰
# ==========================================
JWT_SECRET=your_jwt_secret_at_least_32_characters_long_for_hs256
# ==========================================
# OAuth 2.0 (소셜 로그인)
# ==========================================
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
KAKAO_CLIENT_ID=your_kakao_client_id
KAKAO_CLIENT_SECRET=your_kakao_client_secret
NAVER_CLIENT_ID=your_naver_client_id
NAVER_CLIENT_SECRET=your_naver_client_secret
# ==========================================
# OpenAI API
# ==========================================
OPENAI_API_KEY=sk-your_openai_api_key_here
LLM_MODEL=gpt-4o
EMBEDDING_MODEL=text-embedding-3-large
⚠️ 매우 중요!
# .gitignore에 반드시 추가!
echo ".env" >> .gitignore
# 확인
cat .gitignore | grep .env
절대 Git에 올리면 안 됨! 🚨
💡 환경 변수 사용법
docker-compose.yml에서:
environment:
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
# └─ .env에서 자동으로 읽어옴
기본값 설정:
environment:
- LLM_MODEL=${LLM_MODEL:-gpt-4o}
# └─ 없으면 gpt-4o 사용
🧪 5. 로컬 테스트 (단계별)
Step 1: Docker 설치 확인
# Docker 버전 확인
docker --version
# Docker version 24.0.7
# Docker Compose 버전 확인
docker compose version
# Docker Compose version v2.21.0
버전이 안 나오면?
Step 2: 프로젝트 준비
# 1. .env 파일 생성
cp .env.example .env # 템플릿 있으면
nano .env # 없으면 직접 생성
# 2. .env 파일 확인
cat .env
# 3. .gitignore 확인
cat .gitignore | grep .env
Step 3: 빌드 (처음 한 번만)
# 프로젝트 루트에서
docker compose build
# 또는 특정 서비스만
docker compose build spring-boot
docker compose build python-service
예상 시간:
- Spring Boot: 2-3분
- Python: 1-2분
- 총: 3-5분
빌드 로그 예시:
[+] Building 187.3s (22/22) FINISHED
=> [spring-boot builder 1/6] FROM docker.io/library/gradle:8.5-jdk17
=> [spring-boot builder 2/6] WORKDIR /app
=> [spring-boot builder 3/6] COPY build.gradle settings.gradle ./
=> [spring-boot builder 4/6] COPY gradle ./gradle
=> [spring-boot builder 5/6] COPY src ./src
=> [spring-boot builder 6/6] RUN gradle build -x test --no-daemon
=> [spring-boot stage-1 1/2] FROM docker.io/library/openjdk:17-jdk-slim
=> [spring-boot stage-1 2/2] COPY --from=builder /app/build/libs/*.jar app.jar
=> => exporting to image
=> => naming to docker.io/library/brainstorming-platform-spring-boot
Step 4: 실행
# 포그라운드 실행 (로그 보임)
docker compose up
# 백그라운드 실행 (추천!)
docker compose up -d
실행 순서:
1. MySQL 시작...
2. MySQL healthcheck...
3. MySQL healthy ✅
4. Python 시작...
5. Python healthcheck...
6. Python healthy ✅
7. Spring Boot 시작...
8. Spring Boot healthy ✅
9. 전체 완료! 🎉
로그 예시:
[+] Running 3/3
✔ Container brainstorm-mysql Healthy 12.3s
✔ Container brainstorm-python Healthy 15.6s
✔ Container brainstorm-spring Started 18.2s
Step 5: 상태 확인
# 컨테이너 상태 확인
docker compose ps
# 결과:
NAME STATUS PORTS
brainstorm-mysql Up (healthy) 0.0.0.0:3306->3306/tcp
brainstorm-python Up (healthy) 0.0.0.0:8000->8000/tcp
brainstorm-spring Up (healthy) 0.0.0.0:8080->8080/tcp
"Up (healthy)" 이면 완벽! ✅
Step 6: 로그 확인
# 전체 로그
docker compose logs
# 특정 서비스만
docker compose logs spring-boot
docker compose logs python-service
docker compose logs mysql
# 실시간 로그 (Ctrl+C로 종료)
docker compose logs -f
# 최근 100줄만
docker compose logs --tail=100
Step 7: 테스트
# 1. Python API 헬스체크
curl http://localhost:8000/health
# 응답:
# {
# "status": "healthy",
# "service": "Brainstorming API",
# "openai_key_set": true
# }
# 2. Spring Boot 헬스체크
curl http://localhost:8080/actuator/health
# 응답:
# {"status":"UP"}
# 3. 브라우저 테스트
open http://localhost:8080
# 또는 크롬에서 직접 접속
Step 8: 종료
# 중지 (컨테이너 유지)
docker compose stop
# 재시작
docker compose start
# 중지 + 삭제 (데이터는 유지)
docker compose down
# 완전 초기화 (볼륨까지 삭제)
docker compose down -v
😵 6. 자주 발생하는 오류
오류 1: 포트 이미 사용 중
증상:
Error response from daemon: Ports are not available:
exposing port TCP 0.0.0.0:8080 -> 0.0.0.0:0:
listen tcp 0.0.0.0:8080: bind: address already in use
원인: 로컬에서 서비스 실행 중
해결 방법 1: 프로세스 종료
# 실행 중인 프로세스 찾기
lsof -i :8080 # Spring Boot
lsof -i :8000 # Python
lsof -i :3306 # MySQL
# 예시 출력:
# java 12345 user 123u IPv6 0x... TCP *:8080 (LISTEN)
# 종료
kill -9 12345
해결 방법 2: 포트 변경
# docker-compose.yml
services:
spring-boot:
ports:
- "8081:8080" # 호스트:컨테이너
# └─ 8081로 변경
오류 2: MySQL 연결 실패
증상:
spring-boot | com.mysql.cj.jdbc.exceptions.CommunicationsException:
Communications link failure
원인 1: MySQL이 준비 안 됨
# ✅ 해결: depends_on 추가 (이미 추가됨)
depends_on:
mysql:
condition: service_healthy
원인 2: 환경 변수 오타
# .env 파일 확인
cat .env | grep MYSQL
# docker-compose.yml과 비교
cat docker-compose.yml | grep MYSQL
원인 3: 네트워크 문제
# 네트워크 확인
docker network ls
# 컨테이너 네트워크 확인
docker inspect brainstorm-mysql | grep NetworkMode
오류 3: 빌드 실패
증상:
ERROR: failed to solve: process "/bin/sh -c gradle build"
did not complete successfully: exit code: 1
해결 방법 1: 캐시 삭제 후 재빌드
# 캐시 없이 빌드
docker compose build --no-cache
# 또는 전체 정리 후
docker system prune -a
docker compose build
해결 방법 2: 로그 확인
# 빌드 로그 자세히 보기
docker compose build --progress=plain
오류 4: 메모리 부족
증상:
java.lang.OutOfMemoryError: Java heap space
해결: docker-compose.yml에 메모리 제한 추가
services:
spring-boot:
# ... 기존 설정
deploy:
resources:
limits:
memory: 1G # 최대 1GB
reservations:
memory: 512M # 최소 512MB
environment:
- JAVA_OPTS=-Xmx512m -Xms256m # JVM 힙 크기 설정
오류 5: 데이터 볼륨 문제
증상: 데이터가 초기화됨
원인: docker compose down -v 실행
# ❌ 볼륨까지 삭제
docker compose down -v
# ✅ 볼륨 유지
docker compose down
볼륨 확인:
# 볼륨 목록
docker volume ls
# 특정 볼륨 상세 정보
docker volume inspect brainstorming-platform_mysql-data
☁️ 7. AWS EC2 배포 (단계별)
준비물
- AWS 계정
- 신용카드 (무료 티어 사용해도 필요)
- 인내심 😅
Step 1: EC2 인스턴스 생성
1. AWS 콘솔 로그인
https://console.aws.amazon.com/
2. EC2 대시보드
서비스 → EC2 → "인스턴스 시작" 클릭
3. 인스턴스 설정
| 항목 |
설정값 |
설명 |
| 이름 |
brainstorm-server |
알아보기 쉬운 이름 |
| AMI |
Ubuntu Server 22.04 LTS |
안정적인 최신 버전 |
| 인스턴스 유형 |
t3.medium |
2 vCPU, 4GB RAM |
| 키 페어 |
새로 생성 또는 기존 사용 |
중요! 잘 보관 |
| 스토리지 |
30GB gp3 |
기본 8GB는 부족함 |
4. 보안 그룹 설정 (매우 중요! 🔥)
"편집" 클릭 → 다음 규칙 추가:
| 유형 |
프로토콜 |
포트 범위 |
소스 |
설명 |
| SSH |
TCP |
22 |
내 IP |
서버 접속용 |
| HTTP |
TCP |
80 |
0.0.0.0/0 |
웹 (나중에) |
| HTTPS |
TCP |
443 |
0.0.0.0/0 |
웹 (나중에) |
| Custom TCP |
TCP |
8080 |
0.0.0.0/0 |
Spring Boot |
| Custom TCP |
TCP |
8000 |
0.0.0.0/0 |
Python API |
5. 인스턴스 시작!
"인스턴스 시작" 버튼 클릭
6. 키 페어 다운로드
brainstorm-key.pem 다운로드
- 안전한 곳에 보관!
- 잃어버리면 서버 접속 불가! 🚨
Step 2: EC2 접속
1. 키 파일 권한 변경 (Mac/Linux)
chmod 400 brainstorm-key.pem
2. SSH 접속
# EC2 퍼블릭 IP 확인 (AWS 콘솔에서)
# 예: 13.125.123.45
# SSH 접속
ssh -i brainstorm-key.pem ubuntu@13.125.123.45
3. 접속 성공!
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 6.2.0-1009-aws x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
ubuntu@ip-172-31-12-34:~$
Step 3: Docker 설치
# 1. 시스템 업데이트
sudo apt update && sudo apt upgrade -y
# 2. Docker 설치 (공식 스크립트)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# 3. 현재 사용자를 docker 그룹에 추가
sudo usermod -aG docker ubuntu
# 4. 재로그인 (권한 적용)
exit
ssh -i brainstorm-key.pem ubuntu@13.125.123.45
# 5. Docker 버전 확인
docker --version
# Docker version 24.0.7, build afdd53b
# 6. Docker Compose 설치
sudo apt install docker-compose-plugin -y
# 7. Docker Compose 버전 확인
docker compose version
# Docker Compose version v2.21.0
Step 4: 코드 배포
방법 1: Git Clone (권장)
# Git 설치
sudo apt install git -y
# 코드 클론
git clone https://github.com/your-username/brainstorming-platform.git
cd brainstorming-platform
방법 2: SCP 업로드
# 로컬에서 실행 (Mac/Linux)
scp -i brainstorm-key.pem -r ./brainstorming-platform ubuntu@13.125.123.45:~/
# Windows (PowerShell)
scp -i brainstorm-key.pem -r .\brainstorming-platform ubuntu@13.125.123.45:~/
Step 5: 환경 변수 설정
# .env 파일 생성
nano .env
# 내용 붙여넣기 (로컬 .env 복사)
# Ctrl + O (저장)
# Enter
# Ctrl + X (종료)
# 확인
cat .env
Step 6: 빌드 및 실행
# 빌드 (시간 걸림!)
docker compose build
# 실행
docker compose up -d
# 상태 확인
docker compose ps
# 로그 확인
docker compose logs -f
Step 7: 방화벽 설정 (Ubuntu)
# UFW 설치 확인
sudo apt install ufw -y
# 기본 정책
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 포트 허용
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 8080/tcp # Spring Boot
sudo ufw allow 8000/tcp # Python
# 방화벽 활성화
sudo ufw enable
# 상태 확인
sudo ufw status
# 결과:
# Status: active
# To Action From
# -- ------ ----
# 22/tcp ALLOW Anywhere
# 80/tcp ALLOW Anywhere
# 443/tcp ALLOW Anywhere
# 8080/tcp ALLOW Anywhere
# 8000/tcp ALLOW Anywhere
Step 8: 테스트
# EC2 IP 확인
curl ifconfig.me
# 13.125.123.45
# 브라우저에서 접속
http://13.125.123.45:8080
# 또는 curl로
curl http://13.125.123.45:8080/actuator/health
접속되면 성공! 🎉
🔧 8. 유용한 명령어 모음
Docker 관리
# 컨테이너 목록
docker ps
docker ps -a # 중지된 것도 포함
# 이미지 목록
docker images
# 로그 보기
docker logs brainstorm-spring
docker logs -f brainstorm-spring # 실시간
docker logs --tail=100 brainstorm-spring # 최근 100줄
# 컨테이너 내부 접속
docker exec -it brainstorm-spring bash
docker exec -it brainstorm-mysql mysql -u root -p
# 리소스 사용량
docker stats
# 디스크 사용량
docker system df
# 디스크 정리 (조심!)
docker system prune # 사용 안 하는 것 정리
docker system prune -a # 모든 이미지까지 삭제
docker volume prune # 사용 안 하는 볼륨 삭제
Docker Compose 관리
# 빌드
docker compose build # 전체
docker compose build spring-boot # 특정 서비스만
# 실행
docker compose up # 포그라운드
docker compose up -d # 백그라운드
docker compose up --build # 빌드 + 실행
# 중지/시작
docker compose stop # 중지
docker compose start # 시작
docker compose restart # 재시작
# 삭제
docker compose down # 컨테이너만 삭제 (볼륨 유지)
docker compose down -v # 볼륨까지 삭제 (데이터 삭제!)
# 로그
docker compose logs
docker compose logs -f
docker compose logs --tail=100
docker compose logs spring-boot
# 상태
docker compose ps
docker compose top
MySQL 관리
# MySQL 컨테이너 접속
docker exec -it brainstorm-mysql bash
# MySQL 로그인
mysql -u root -p
# 또는
mysql -u brainstorm -p
# SQL 명령어
SHOW DATABASES;
USE brainstorm;
SHOW TABLES;
SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM ideas;
SELECT COUNT(*) FROM inquiries;
# 백업
docker exec brainstorm-mysql mysqldump -u root -p brainstorm > backup.sql
# 복원
docker exec -i brainstorm-mysql mysql -u root -p brainstorm < backup.sql
재배포 (코드 수정 후)
# 1. 코드 업데이트
git pull
# 2. 컨테이너 중지
docker compose down
# 3. 이미지 다시 빌드
docker compose build
# 4. 컨테이너 재시작
docker compose up -d
# 한 줄로 (편리!)
git pull && docker compose down && docker compose build && docker compose up -d
디버깅
# 컨테이너 상세 정보
docker inspect brainstorm-spring
# 네트워크 확인
docker network ls
docker network inspect brainstorming-platform_brainstorm-network
# 볼륨 확인
docker volume ls
docker volume inspect brainstorming-platform_mysql-data
# 헬스체크 상태
docker inspect --format='{{json .State.Health}}' brainstorm-spring | jq
# 환경 변수 확인
docker exec brainstorm-spring env
# 포트 확인
docker port brainstorm-spring
🐛 9. 트러블슈팅
문제 1: 컨테이너가 계속 재시작됨
확인:
docker compose ps
# 결과:
# brainstorm-spring Restarting (1) ...
원인 파악:
# 로그 확인
docker compose logs spring-boot
# 예시 오류:
# Error: Unable to connect to MySQL server
해결:
- MySQL 먼저 확인
- 환경 변수 확인
- healthcheck 시간 늘리기
문제 2: 디스크 공간 부족
확인:
df -h
# 결과:
# /dev/xvda1 30G 28G 2.0G 94% /
해결:
# Docker 정리
docker system prune -a
docker volume prune
# 빌드 캐시 삭제
docker builder prune
# 로그 파일 정리
sudo journalctl --vacuum-time=3d
문제 3: 메모리 부족
확인:
free -h
# 결과:
# total used free
# Mem: 3.8Gi 3.5Gi 300Mi
해결:
# 스왑 추가 (4GB)
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 영구 적용
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 확인
free -h
문제 4: OAuth 리다이렉트 실패
원인: 로컬 URL로 설정되어 있음
해결:
Google/Kakao/Naver 개발자 콘솔에서 리다이렉트 URL 수정
변경 전:
http://localhost:8080/login/oauth2/code/google
변경 후:
http://13.125.123.45:8080/login/oauth2/code/google
또는
https://yourdomain.com/login/oauth2/code/google
💡 10. 배운 점
Docker의 장점
환경 일관성
- 로컬 = 개발 = 운영
- "내 컴에서는 되는데?" 문제 해결
빠른 배포
docker compose up 한 줄로 끝
- 설정 파일만 관리하면 됨
쉬운 롤백
- 이미지 버전 관리
- 문제 생기면 이전 버전으로 즉시 복구
리소스 효율
- VM보다 가볍고 빠름
- 동일 서버에 여러 환경 운영 가능
삽질하며 배운 것
healthcheck의 중요성
- 프로세스 시작 != 서비스 준비
- MySQL 준비 완료까지 10초 걸림
네트워크 이해
localhost vs 0.0.0.0 vs 컨테이너 이름
- Docker 네트워크는 격리된 환경
볼륨의 필요성
- 컨테이너는 휘발성
- 데이터는 반드시 볼륨에 저장
환경 변수 관리
- 민감 정보는 .env로
- Git에 절대 올리지 말기
🚀 11. 다음 단계
지금 완성된 것
- ✅ Docker로 전체 시스템 컨테이너화
- ✅ docker-compose로 한 번에 실행
- ✅ AWS EC2에 배포 완료
앞으로 할 것
1. 도메인 + HTTPS
# 무료 도메인 + Let's Encrypt
- Freenom에서 도메인 받기
- Nginx 리버스 프록시 추가
- Certbot으로 SSL 인증서 발급
2. CI/CD 파이프라인
# GitHub Actions
- 코드 푸시 → 자동 빌드
- 테스트 실행
- Docker 이미지 생성
- EC2 자동 배포
3. 모니터링
# Grafana + Prometheus
- CPU/메모리 사용량 모니터링
- 로그 수집 및 분석
- 알림 설정 (서버 다운 시)
4. 백업 자동화
# 매일 자동 백업
- MySQL 데이터 백업
- S3에 업로드
- 주기적인 백업 테스트
📊 12. 최종 시스템 구조
┌─────────────────────────────────────────────────┐
│ AWS EC2 (Ubuntu 22.04) │
│ ┌───────────────────────────────────────────┐ │
│ │ Docker Engine │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ brainstorm-network │ │ │
│ │ │ │ │ │
│ │ │ ┌────────┐ ┌─────────────┐ │ │ │
│ │ │ │ MySQL │ │ Spring Boot │ │ │ │
│ │ │ │ :3306 │◄─┤ :8080 │ │ │ │
│ │ │ └────┬───┘ └──────┬──────┘ │ │ │
│ │ │ │ │ ▲ │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ ┌────▼─────────────▼──┘ │ │ │
│ │ │ │ Python FastAPI │ │ │ │
│ │ │ │ :8000 │ │ │ │
│ │ │ └────────────────────┘ │ │ │
│ │ └──────────────────────────────────┘ │ │
│ │ │ │
│ │ Volumes: │ │
│ │ └─ mysql-data (30GB) │ │
│ └───────────────────────────────────────────┘ │
│ │
│ 포트 매핑: │
│ - 3306 → MySQL │
│ - 8080 → Spring Boot │
│ - 8000 → Python FastAPI │
└─────────────────────────────────────────────────┘
▲
│
인터넷 접속
http://13.125.123.45:8080
💬 13. 마무리
Docker... 처음엔 어려웠는데 막상 해보니 신기하다! 😮
특히 docker compose up 한 줄로 전체 시스템이 뜨는 게 정말 편함.
힘들었던 점:
- healthcheck 개념 이해
- 네트워크 설정 (localhost vs 0.0.0.0)
- 환경 변수 관리
- AWS 보안 그룹 설정
뿌듯한 점:
- 전체 시스템 컨테이너화 완료!
- AWS에 실제 배포 성공!
- 누구든
git clone + docker compose up으로 실행 가능
- 재배포도 한 줄로 가능!
이제 진짜 서비스 운영할 수 있다!
다음 목표: CI/CD 파이프라인 구축 → 코드 푸시만 하면 자동 배포!
작성일: 2024-12-04
소요 시간: 약 3시간
GitHub: [저장소 링크]
#Docker #DockerCompose #AWS #EC2 #배포 #컨테이너 #DevOps
'[그래서 일단 프로젝트] > [개인프로젝트-IdeaMaker]' 카테고리의 다른 글