💻 PROJECT/Team Projects

[ 🚚 ONESTOP ] Docker Compose + GitHub Actions로 팀 개발 환경 & CI 구축하기

eunjiom 2026. 5. 14. 20:59

 

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 작성법을 코드 리뷰를 통해 배움
  • 리뷰 봇이 항상 옳은 게 아니라는 것도 배움