diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..faf0b1d --- /dev/null +++ b/.env.local.example @@ -0,0 +1,21 @@ +# git-ranker 로컬 도커 실행용 환경변수 템플릿 (SPRING_PROFILES_ACTIVE=local) +# 사용법: cp .env.local.example .env.local 후 값 채우기 (.env.local 은 gitignore 대상) +# 실행: docker compose --env-file .env.local -f docker-compose.local.yml up -d --build + +# --- Database (local) --- +DB_NAME=git_ranker +DB_USERNAME=root +DB_PASSWORD=root + +# --- GitHub OAuth App (local): 본인 OAuth App 값 --- +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +GITHUB_REDIRECT_URI=http://localhost:8080/login/oauth2/code/github + +# --- JWT: JWT_SECRET 은 32바이트 이상 랜덤 시크릿 --- +JWT_SECRET= +JWT_ACCESS_TOKEN_EXPIRATION=900000 +JWT_REFRESH_TOKEN_EXPIRATION=86400000 + +# --- GitHub GraphQL API tokens (comma-separated, 토큰 풀 로테이션) --- +GITHUB_API_TOKENS= diff --git a/.gitignore b/.gitignore index 4226467..16dc9f2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ build/** !**/src/main/**/build/ !**/src/test/**/build/ .env +.env.* +!.env*.example ### STS ### .apt_generated diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..f1c1f14 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,62 @@ +# 로컬 개발용 (SPRING_PROFILES_ACTIVE=local): API + MySQL 두 컨테이너만. +# 운영 스택(docker-compose.yml)과 분리 — 모니터링(Prometheus/Loki/Grafana) 없음. +# 실행: docker compose --env-file .env.local -f docker-compose.local.yml up -d --build +# 검증: curl http://localhost:9090/actuator/health +services: + git-ranker-api: + build: . + container_name: git-ranker-api-local + ports: + - "8080:8080" + - "127.0.0.1:9090:9090" + environment: + SPRING_PROFILES_ACTIVE: local + # application-local.yml이 ${DB_URL}을 읽음 → 컨테이너에선 localhost가 아니라 DB 서비스명으로 구성 + DB_URL: jdbc:mysql://git-ranker-db:3306/${DB_NAME}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} + GITHUB_REDIRECT_URI: ${GITHUB_REDIRECT_URI} + GITHUB_API_TOKENS: ${GITHUB_API_TOKENS} + JWT_SECRET: ${JWT_SECRET} + JWT_ACCESS_TOKEN_EXPIRATION: ${JWT_ACCESS_TOKEN_EXPIRATION} + JWT_REFRESH_TOKEN_EXPIRATION: ${JWT_REFRESH_TOKEN_EXPIRATION} + TZ: Asia/Seoul + depends_on: + git-ranker-db: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/actuator/health"] + interval: 10s + timeout: 5s + retries: 10 + networks: + - git-ranker-local-net + + git-ranker-db: + image: mysql:8.0 + container_name: git-ranker-db-local + restart: unless-stopped + environment: + MYSQL_DATABASE: ${DB_NAME} + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} # 앱이 root로 접속 (DB_USERNAME=root). MySQL 이미지는 MYSQL_USER=root를 거부하므로 root 계정 사용. + TZ: Asia/Seoul + ports: + - "127.0.0.1:3306:3306" + volumes: + - mysql_data_local:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 10 + networks: + - git-ranker-local-net + +networks: + git-ranker-local-net: + driver: bridge + +volumes: + mysql_data_local: diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index db4b583..a90f5f8 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -14,16 +14,16 @@ | 프로파일 | 용도 | 특징 | |---|---|---| -| `local` | 로컬 개발 | localhost MySQL, `LOCAL_*` OAuth env, CORS localhost:3000, 쿠키 non-secure | +| `local` | 로컬 개발 | MySQL, OAuth·DB env(`.env.local`), CORS localhost:3000, 쿠키 non-secure | | `prod` | 운영 | `DB_URL` 등 운영 env, git-ranker.com CORS, secure 쿠키, actuator 노출 축소 | ## 필요 환경 변수 (local) -`spring-dotenv`가 루트 `.env`를 읽는다. +`local`·`prod`가 같은 변수명을 쓰고, 값은 환경별 파일로 구분한다. 로컬 도커는 `--env-file .env.local`로 주입한다(템플릿 `.env.local.example`). -``` -LOCAL_DB_USERNAME, LOCAL_DB_PASSWORD -LOCAL_GITHUB_CLIENT_ID, LOCAL_GITHUB_CLIENT_SECRET, LOCAL_GITHUB_REDIRECT_URI +```text +DB_NAME, DB_USERNAME, DB_PASSWORD +GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_REDIRECT_URI JWT_SECRET, JWT_ACCESS_TOKEN_EXPIRATION, JWT_REFRESH_TOKEN_EXPIRATION GITHUB_API_TOKENS # GitHub GraphQL 토큰 (콤마 구분, 토큰 풀 로테이션) ``` @@ -31,12 +31,19 @@ GITHUB_API_TOKENS # GitHub GraphQL 토큰 (콤마 구분, 토큰 풀 로테이 ## 실행 ```bash -SPRING_PROFILES_ACTIVE=local ./gradlew bootRun # 앱 실행 (8080) +SPRING_PROFILES_ACTIVE=local ./gradlew bootRun # 호스트 직접 실행: 아래 도커 권장. 이 경로는 spring-dotenv가 읽는 .env(로컬 변수 전체)가 필요 ./gradlew test # 단위 테스트 ./gradlew build # 패키징 (CI는 build -x test) ``` -docker-compose 전체 스택(api + db + 모니터링)은 `docker compose up -d`. 서비스: `git-ranker-api`(8080, mgmt 9090), `git-ranker-db`(MySQL 8.0), `prometheus`, `loki`, `promtail`, `grafana`(3001). +로컬 도커(api + db만, `local` 프로파일): + +```bash +docker compose --env-file .env.local -f docker-compose.local.yml up -d --build # 기동 +docker compose --env-file .env.local -f docker-compose.local.yml down # 정지(-v 추가 시 DB 볼륨 삭제) +``` + +운영 전체 스택(api + db + 모니터링)은 `docker compose up -d`. 서비스: `git-ranker-api`(8080, mgmt 9090), `git-ranker-db`(MySQL 8.0), `prometheus`, `loki`, `promtail`, `grafana`(3001). ## 주요 엔드포인트 diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index cbfb6ee..f60a68d 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -4,22 +4,26 @@ spring: on-profile: local datasource: - url: jdbc:mysql://localhost:3306/git_ranker?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul - username: ${LOCAL_DB_USERNAME} - password: ${LOCAL_DB_PASSWORD} + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + security: oauth2: client: registration: github: - client-id: ${LOCAL_GITHUB_CLIENT_ID} - client-secret: ${LOCAL_GITHUB_CLIENT_SECRET} + client-id: ${GITHUB_CLIENT_ID} + client-secret: ${GITHUB_CLIENT_SECRET} scope: - read:user - user:email - redirect-uri: ${LOCAL_GITHUB_REDIRECT_URI} + redirect-uri: ${GITHUB_REDIRECT_URI} jwt: secret: ${JWT_SECRET} @@ -39,4 +43,4 @@ app: authorized-redirect-uri: http://localhost:3000/auth/callback cookie: domain: localhost - secure: false \ No newline at end of file + secure: false