🐳 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/*
  • apt 캐시 삭제
  • 이미지 크기 줄이기 위함
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

해결:

  1. MySQL 먼저 확인
  2. 환경 변수 확인
  3. 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의 장점

  1. 환경 일관성

    • 로컬 = 개발 = 운영
    • "내 컴에서는 되는데?" 문제 해결
  2. 빠른 배포

    • docker compose up 한 줄로 끝
    • 설정 파일만 관리하면 됨
  3. 쉬운 롤백

    • 이미지 버전 관리
    • 문제 생기면 이전 버전으로 즉시 복구
  4. 리소스 효율

    • VM보다 가볍고 빠름
    • 동일 서버에 여러 환경 운영 가능

삽질하며 배운 것

  1. healthcheck의 중요성

    • 프로세스 시작 != 서비스 준비
    • MySQL 준비 완료까지 10초 걸림
  2. 네트워크 이해

    • localhost vs 0.0.0.0 vs 컨테이너 이름
    • Docker 네트워크는 격리된 환경
  3. 볼륨의 필요성

    • 컨테이너는 휘발성
    • 데이터는 반드시 볼륨에 저장
  4. 환경 변수 관리

    • 민감 정보는 .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 한 줄로 전체 시스템이 뜨는 게 정말 편함.

힘들었던 점:

  1. healthcheck 개념 이해
  2. 네트워크 설정 (localhost vs 0.0.0.0)
  3. 환경 변수 관리
  4. AWS 보안 그룹 설정

뿌듯한 점:

  1. 전체 시스템 컨테이너화 완료!
  2. AWS에 실제 배포 성공!
  3. 누구든 git clone + docker compose up으로 실행 가능
  4. 재배포도 한 줄로 가능!

이제 진짜 서비스 운영할 수 있다!

다음 목표: CI/CD 파이프라인 구축 → 코드 푸시만 하면 자동 배포!


작성일: 2024-12-04
소요 시간: 약 3시간
GitHub: [저장소 링크]

#Docker #DockerCompose #AWS #EC2 #배포 #컨테이너 #DevOps


'[그래서 일단 프로젝트] > [개인프로젝트-IdeaMaker]' 카테고리의 다른 글

기록 8. 전체 시스템 통합 완성 (0)
기록 7. OAuth 2.0 / JWT 구현 (0)
기록 6. 컨트롤러 계층 완성 (0)

+ Recent posts