From e3326b733a65bb9a0907de3f3e2df6586e288bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=A4=80=EC=98=81?= Date: Wed, 19 Aug 2026 20:28:41 +0900 Subject: [PATCH] =?UTF-8?q?T-04=20=EB=A1=9C=EC=BB=AC=20=EC=8B=A4=ED=96=89?= =?UTF-8?q?=20=ED=99=98=EA=B2=BD=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 부팅 → 마이그레이션 → /health 200을 로컬에서 재현하고, 그 과정에서 겪은 문제를 README에 남긴다. - docker-compose.yml — 로컬 Postgres 16. 호스트 포트는 COMPOSE_POSTGRES_PORT로 오버라이드 가능(기본 5432) - README.md — 실행 절차, 테스트 실행, 흔히 겪는 문제 application-local.yml은 추가하지 않았다. .env + springboot4-dotenv(developmentOnly)가 이미 이 역할을 하고 있고(.env.example에 필요한 오버라이드가 전부 문서화되어 있다), Spring profile을 추가로 얹으면 로컬 설정 경로가 두 개가 된다. 로컬에서 실제로 부딪힌 문제: 이 Mac에 홈브루 postgresql@18 서비스가 이미 5432를 점유하고 있어 Flyway가 엉뚱한 Postgres(역할 "postgres" 없음)에 연결해 실패했다. COMPOSE_POSTGRES_PORT 오버라이드로 재현 확인 — README에 원인과 해결을 남겼다. 검증: docker compose up -d → .env 생성(JWT_SECRET 발급) → ./gradlew bootRun → Flyway 6개 마이그레이션 적용 → curl /health → 200 OK. ./gradlew test 전체 통과. Refs #16 --- README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 23 ++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 README.md create mode 100644 docker-compose.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c2af24 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# BCSD Internal API V2 + +BCSD 동아리 인터널(내부 관리) 서비스 백엔드. Spring Boot 4 / Java 21 / PostgreSQL 16. + +## 로컬 실행 + +### 요구 사항 +- JDK 21 +- Docker (로컬 Postgres 컨테이너용) + +### 절차 + +1. `.env` 생성 + ``` + cp .env.example .env + ``` + `.env`를 열어 `JWT_SECRET`을 32바이트 이상 임의 문자열로 채운다. + ``` + openssl rand -base64 32 + ``` + `.env`는 `.gitignore`에 포함되어 있다. + +2. 로컬 Postgres 기동 + ``` + docker compose up -d + ``` + 이미 로컬에 5432 포트를 쓰는 Postgres가 있다면(Postgres.app, `brew services` 등) 충돌한다. + `docker compose ps`로 컨테이너가 healthy인지 확인하고, 충돌 시: + ``` + COMPOSE_POSTGRES_PORT=5433 docker compose up -d + ``` + 와 함께 `.env`에 `DB_PORT=5433`을 추가한다. + +3. 애플리케이션 실행 + ``` + ./gradlew bootRun + ``` + `developmentOnly` 의존성인 `springboot4-dotenv`가 `.env`를 자동으로 읽는다. + 별도 `--spring.profiles.active` 지정은 필요 없다 — `application.yml`의 프로퍼티 기본값이 + 이미 로컬 개발을 기준으로 되어 있다(`DB_HOST:localhost`, `DB_PORT:5432` 등). + +4. 확인 + ``` + curl http://localhost:8080/health + ``` + `OK`(200)가 반환되면 정상이다. 부팅 로그에서 Flyway 마이그레이션이 전부 적용됐는지도 확인한다. + +### 테스트 실행 + +``` +./gradlew test +``` + +Testcontainers가 테스트 실행 시 Postgres 컨테이너를 자동으로 띄우므로 Docker가 실행 중이어야 한다. +로컬 Postgres(2번 단계)와는 별개다 — 테스트는 매번 격리된 컨테이너를 쓴다. + +### 흔히 겪는 문제 + +- **`Cannot find a Java installation on your machine ... {languageVersion=21}`** + 이 프로젝트는 JDK 21 툴체인을 요구한다. JDK 21을 설치한 뒤 `~/.gradle/gradle.properties` + (저장소가 아니라 사용자 홈, 커밋하지 않는다)에 다음을 추가한다. + ``` + org.gradle.java.installations.paths= + ``` + +- **`FATAL: role "postgres" does not exist`로 Flyway 마이그레이션이 실패한다** + 로컬에 이미 떠 있는 다른 Postgres(홈브루 서비스 등)가 5432를 선점하고 있다는 신호다. + 위 2번 단계의 `COMPOSE_POSTGRES_PORT` 대안을 쓴다. + +- **`JWT_SECRET`이 비어 있으면 부팅이 즉시 실패한다.** + `.env`에 32바이트 이상 값을 채웠는지 확인한다(`app.jwt.secret: ${JWT_SECRET}`에 기본값이 없다). + +## 배포 + +`main` push → GitHub Actions → ghcr.io 이미지 빌드 → 서버 SSH 접속 후 기존 컨테이너 종료·재시작(순차 재시작, 수초 다운타임). 서버가 KONECT 운영 서버를 공유하고 API 컨테이너 메모리 한도가 256MiB로 빠듯하므로, 배포 후 `docker stats`와 `free -h`로 확인한다. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3fb8ace --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +services: + postgres: + image: postgres:16-alpine + container_name: bcsd-internal-api-v2-postgres + restart: unless-stopped + ports: + # 로컬에 이미 5432를 쓰는 Postgres가 있다면(Postgres.app, brew services 등) + # COMPOSE_POSTGRES_PORT=5433 docker compose up -d 로 띄우고 .env의 DB_PORT를 맞춘다. + - "${COMPOSE_POSTGRES_PORT:-5432}:5432" + environment: + POSTGRES_DB: bcsd_internal_api_v2 + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - bcsd-internal-api-v2-pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d bcsd_internal_api_v2"] + interval: 5s + timeout: 5s + retries: 10 + +volumes: + bcsd-internal-api-v2-pgdata: