1. 로컬 개발 환경 세팅 (Docker Compose)
Docker Compose를 선택한 이유
로컬에서 MySQL과 Redis를 구성하는 방법이 세 가지 있었음
- 직접 설치 - 가장 단순하지만 팀원마다 버전이 달라질 수 있고 OS별로 설치 방법이 달라 환경 불일치 문제가 생김
- Docker 단독 실행 - 컨테이너마다 명령어를 따로 입력해야 하고 설정이 코드로 관리되지 않아 팀원 간 공유가 어려움
- Docker Compose - 하나의 yml 파일로 정의하고 docker compose up -d 한 줄로 실행 가능, 팀원 5명이 동일한 환경을 보장할 수 있어서 선택함
이미지 버전을 latest 대신 고정한 이유
- latest는 팀원마다 설치 시점에 따라 버전이 달라질 수 있어 환경 불일치 문제가 생김
- redis:7.0, mysql:8.0으로 고정해 팀원 모두 동일한 버전 사용 보장
application-local.yml / application-example.yml 분리한 이유
- application-local.yml - DB 접속 정보, JWT Secret Key 등 민감 정보 포함, .gitignore로 깃에 올라가지 않게 막음
- application-example.yml - 민감 정보를 플레이스홀더로 대체해 팀원들이 참고할 수 있게 공유
2. 트러블슈팅 — Docker MySQL 포트 충돌
문제 상황
Error response from daemon: ports are not available: exposing port TCP 0.0.0.0:3306
원인
- 로컬에 MySQL이 이미 설치되어 있고 Windows 서비스로 실행 중이었음
- mysqld.exe가 이미 3306 포트를 점유하고 있어 Docker와 충돌 발생
해결 과정
# 1. 포트 점유 프로세스 확인
netstat -ano | findstr :3306
# 2. PID로 프로세스 이름 확인
tasklist | findstr 7100
# mysqld.exe 7100
# 3. 강제 종료
taskkill /PID 7100 /F
# 4. Docker Compose 재실행
docker compose up -d
배운 점
- Windows 서비스에서 중지해도 프로세스가 살아있는 경우가 있음
- netstat으로 포트를 점유한 PID를 직접 찾아서 taskkill로 종료해야 함
3. GitHub Actions CI 파이프라인 구성
GitHub Actions를 선택한 이유
- Jenkins - 별도 서버 설치가 필요하고 초기 설정이 복잡함
- CircleCI - 클라우드 기반이라 별도 서버가 필요 없지만 GitHub 연동에 별도 설정이 필요하고 무료 플랜 빌드 시간 제한이 있음
- GitHub Actions - GitHub 안에 내장되어 있어 .github/workflows/ 폴더에 yml 파일만 추가하면 바로 동작, 팀 전원이 GitHub을 사용하고 있어서 선택함
Gradle 캐시를 설정한 이유
- CI 환경은 매번 새로운 서버에서 실행되어 캐시 없이는 매번 수백 개의 라이브러리를 새로 다운받아야 함
- 의존성 파일의 해시값을 캐시 키로 사용해서 의존성이 변경됐을 때만 새로 다운받도록 설정
Dockerfile 멀티 스테이지 빌드를 선택한 이유
- 단일 스테이지 - 빌드 도구(Gradle)와 실행 파일이 같은 이미지에 포함되어 최종 이미지 크기가 커짐
- 멀티 스테이지 빌드 - 빌드 스테이지와 실행 스테이지를 분리해 jar 파일만 실행 이미지에 담아 크기를 최소화함
# 빌드 스테이지
FROM gradle:8.14-jdk17 AS build
WORKDIR /app
COPY . .
RUN gradle bootJar --no-daemon
# 실행 스테이지
FROM openjdk:17-jdk-slim
WORKDIR /app
RUN useradd --create-home --shell /usr/sbin/nologin appuser
COPY --from=build /app/build/libs/app.jar app.jar
EXPOSE 8080
USER appuser
ENTRYPOINT ["java", "-jar", "app.jar"]
4. 코드 리뷰 반영
Dockerfile non-root user 추가
- root 권한으로 실행하면 컨테이너가 해킹됐을 때 서버 전체가 위험해질 수 있음
- appuser 계정을 만들어 그 계정으로 실행하도록 변경
*.jar 와일드카드 → app.jar 고정
- plain.jar 같은 불필요한 파일이 함께 생성되어 의도하지 않은 파일이 복사될 위험이 있었음
- build.gradle에 jar 파일명을 app.jar로 고정
bootJar {
archiveFileName = 'app.jar'
}
application-example.yml 플레이스홀더 문법 수정
- {DB_PASSWORD} 형식은 Spring이 환경변수로 인식하지 못하는 잘못된 문법이었음
- ${DB_PASSWORD} 형식으로 수정
QueryDSL 생성 경로 변경
- src/main/generated - 소스 트리 안에 생성 파일이 위치해 실수로 깃에 커밋될 위험이 있었음
- build/generated/querydsl - gradle clean 시 자동으로 삭제되어 안전
봇이 잘못 생성한 테스트 파일 삭제
- GitHub Actions 리뷰 봇이 의존성이 삭제됐다고 잘못 판단해 의존성이 없다는 걸 검증하는 테스트 파일을 자동으로 생성함
- 실제로는 의존성을 추가한 PR이라 봇이 반대로 동작한 것이었음
- 해당 파일 삭제 후 기본 테스트 파일 원래 내용으로 복구
오늘의 회고
- 처음으로 Docker Compose로 팀 개발 환경을 구성해봄
- 포트 충돌 문제를 직접 겪으면서 netstat과 taskkill 명령어를 배움
- 보안을 고려한 Dockerfile 작성법을 코드 리뷰를 통해 배움
- 리뷰 봇이 항상 옳은 게 아니라는 것도 배움
'💻 PROJECT > Team Projects' 카테고리의 다른 글
| [ 🚚 ONESTOP ] CI/CD부터 관리자 API까지 - 기술 선택의 이유 (0) | 2026.05.18 |
|---|---|
| [ 🚚 ONESTOP ] 정책 설계 — 옵션 중복 방지와 상태 관리 (0) | 2026.05.15 |
| [ 🚚 ONESTOP ] Spring Boot 프로젝트 기술 선택 이유 정리 (0) | 2026.05.13 |
| [ 🚚 ONESTOP ] 동시성 제어 전략 (0) | 2026.05.12 |
| [ 😺 GitHub ] Git 활용하여 팀 소개 페이지 만들기 (1) | 2026.01.07 |