259 lines
14 KiB
Markdown
259 lines
14 KiB
Markdown
---
|
|
kind: usage
|
|
title: 로컬 개발환경 온보딩
|
|
description: bibimbap 을 로컬에서 기동하는 두 경로(Docker / 직접 실행) 와 DB 스키마 초기화·트러블슈팅을 실행 검증 근거와 함께 정리한 가이드.
|
|
owner: art
|
|
stability: living
|
|
last_reviewed: 2026-06-17
|
|
---
|
|
|
|
# 로컬 개발환경 온보딩
|
|
|
|
이 문서는 bibimbap 을 처음 받는 개발자가 로컬에서 앱을 띄우기까지의 전 과정을 정리한다. 모든 명령·출력은 실제 실행으로 검증된 것만 싣는다.
|
|
|
|
권장 경로는 **A: Docker** 다. 호스트에 직접 띄우려면 **B: 직접 실행** 을 따른다. 두 경로 모두 전 과정 실행 검증을 통과했다.
|
|
|
|
> docs-first: 이 문서를 읽기 전 `../index.md` → `./index.md` 순으로 카테고리 위치를 확인했다고 가정한다. 기술부채 맥락은 `../analysis/2026-06-16-project-analysis.md` 참조.
|
|
|
|
## 1. 사전 요구사항
|
|
|
|
| 항목 | 버전/사실 | 비고 |
|
|
| --- | --- | --- |
|
|
| Java | 21 (검증: Temurin/OpenJDK 21.0.11) | `pom.xml:25` `java.version=21` |
|
|
| Maven | mvnw wrapper 3.9.14 | 별도 `mvn` 설치 불요. `./mvnw` 사용 |
|
|
| Docker | 29.3.1 | 경로 A 권장 |
|
|
| Docker Compose | v5.1.1 | `docker compose` 서브커맨드 |
|
|
| PostgreSQL | 16 (컨테이너) | `docker-compose.yml` db 서비스 |
|
|
|
|
**온라인 빌드 전제**: 이 프로젝트는 `spring-boot 3.5.14-SNAPSHOT` 에 고정돼 있다(`pom.xml:29`, dependencyManagement import 는 `pom.xml:200-219`). SNAPSHOT 의존성은 `repo.spring.io/snapshot` 에서 온라인으로 받아야 하므로 **오프라인 빌드는 불가**하다. 회사 TLS 인터셉션 프록시 환경에서는 §6 트러블슈팅을 먼저 확인한다.
|
|
|
|
## 2. 경로 A: Docker (compose 기반) — 권장
|
|
|
|
전 과정 실행 검증됨.
|
|
|
|
### 단계
|
|
|
|
```bash
|
|
# 1. 환경파일 준비 (이미 있으면 생략). 비밀값은 .env 에 두며 gitignore 된다.
|
|
cp .env.example .env
|
|
|
|
# 2. (회사 TLS 프록시 환경만) certs/ 에 프록시 CA 배치 — §6 참조
|
|
|
|
# 3. 빌드 + 기동
|
|
docker compose up --build # 또는 -d 로 백그라운드
|
|
|
|
# 4. 홈 접속
|
|
open http://localhost:8080/ # HTTP 200
|
|
```
|
|
|
|
### 구성요소
|
|
|
|
- `docker-compose.yml`: **db**(postgres:16, 최초 기동 시 `db/schema.sql` 자동 주입) + **app**(멀티스테이지 JDK21 빌드, 임베디드 Tomcat 실행).
|
|
- **비밀값 주입**: app 은 `SPRING_DATASOURCE_URL` / `SPRING_DATASOURCE_USERNAME` / `SPRING_DATASOURCE_PASSWORD` 환경변수(`.env`)로 db 에 접속한다. 이미지·git 에 비밀값을 포함하지 않는다.
|
|
- **SNAPSHOT 네트워크 의존**: Dockerfile 빌드 스테이지가 온라인으로 의존성을 받는다. TLS 인터셉션 프록시 환경에선 CA 주입이 필요하다(§6).
|
|
- **업로드 경로**: `APP_UPLOAD_GAME_STORAGE_PATH=/app/uploads/game` + named volume `uploads` 로 영속한다.
|
|
- **호스트 DB 포트**: `.env` 의 `DB_PORT`(기본 5432, 충돌 시 변경). 앱→db 통신은 컨테이너 내부 `db:5432` 라 호스트 포트 변경의 영향을 받지 않는다.
|
|
|
|
### 검증 증거
|
|
|
|
```text
|
|
$ docker compose build app
|
|
[INFO] BUILD SUCCESS
|
|
# image bibimbap-app 생성
|
|
|
|
$ docker compose ps
|
|
# db Up (healthy)
|
|
# app Up 0.0.0.0:8080->8080
|
|
|
|
$ curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/
|
|
200 # 홈 35143 bytes 렌더
|
|
|
|
$ curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/recruit
|
|
200
|
|
```
|
|
|
|
- DB: `dev` 스키마에 6테이블 생성 확인. `games` / `users` / `recruit_posts` 조회 0행 무에러.
|
|
- **라운드트립 증거**: `dev.games` 에 행을 삽입 → 홈에 해당 게임명이 1회 노출됨을 확인 → DB → MyBatis → JSP 전체 경로 동작 확인.
|
|
|
|
## 3. 경로 B: 직접 실행 (호스트)
|
|
|
|
전 과정 실행 검증됨 (macOS Apple Silicon 기준).
|
|
|
|
### 단계
|
|
|
|
```bash
|
|
# 1. JDK21 설치 (검증한 방법). keg-only 라 sudo 불요.
|
|
brew install openjdk@21
|
|
export JAVA_HOME=/opt/homebrew/opt/openjdk@21
|
|
|
|
# 2. wrapper 실행권한 (최초 1회 필요했음)
|
|
chmod +x mvnw
|
|
|
|
# 3. (회사 TLS 프록시 환경만) 프록시 CA 를 호스트 JDK cacerts 에 import — §6 참조
|
|
keytool -importcert -noprompt -trustcacerts -cacerts -storepass changeit \
|
|
-alias proxy -file certs/corporate-proxy-ca.crt
|
|
# 중요: curl 이 성공해도 JDK 는 자체 truststore 를 쓰므로 별도 import 가 필요하다.
|
|
|
|
# 4. DB 준비 — Docker DB 단독 사용 (권장) 또는 로컬 PostgreSQL
|
|
docker compose up -d db # localhost:5433 (.env DB_PORT 기준)
|
|
# 로컬 PG 를 쓰면: PostgreSQL 16 설치 후 db/schema.sql 적용
|
|
|
|
# 5. dev datasource 설정 파일 작성 (gitignore)
|
|
cp src/main/resources/dev/db.properties.example src/main/resources/dev/db.properties
|
|
# 편집: url=jdbc:postgresql://localhost:5433/bibimbap?currentSchema=dev , user/pass 실값
|
|
|
|
# 6. 빌드 / 테스트 / 실행 (JAVA_HOME 지정 상태)
|
|
./mvnw -P dev test
|
|
./mvnw -P dev clean package spring-boot:repackage -DskipTests
|
|
./mvnw -P dev spring-boot:run
|
|
|
|
# 7. 홈 접속
|
|
open http://localhost:8080/ # HTTP 200
|
|
```
|
|
|
|
### 검증 증거
|
|
|
|
```text
|
|
$ ./mvnw -P dev test
|
|
Tests run: 6, Failures: 0, Errors: 0 # UserControllerCsrfTest 5 + BibimbapApplicationTests 1
|
|
[INFO] BUILD SUCCESS
|
|
|
|
$ ./mvnw -P dev clean package spring-boot:repackage -DskipTests
|
|
# WAR: target/bibimbap-0.0.1-SNAPSHOT.war (30MB, 실행형)
|
|
|
|
$ ./mvnw -P dev spring-boot:run
|
|
# Tomcat started on port 8080
|
|
# Started BibimbapApplication
|
|
# 홈 / -> 200 (provided tomcat 으로 정상 run)
|
|
```
|
|
|
|
### 중요 주의: `-P dev` 는 필수
|
|
|
|
이 pom 은 `spring-boot-starter-parent` 를 쓰지 않고 dependencyManagement import 만 한다(`pom.xml:200-219`). 그 결과:
|
|
|
|
- (a) profile 을 지정하지 않으면 `application.properties` 의 `@app.profile@` 필터링이 동작하지 않아 `spring.profiles.active` 가 깨진다.
|
|
- (b) `spring-boot:repackage` goal 이 `package` 단계에 자동 바인딩되지 않는다. 명시 호출하지 않으면 비실행 WAR 가 나오고 `no main manifest attribute` 로 실패한다.
|
|
|
|
## 4. DB 스키마 초기화 절차
|
|
|
|
핵심 사실: **flyway / liquibase 가 없다.** 마이그레이션 자동화가 없으며, 작업 시작 시점에 전체 스키마 SQL 도 부재였다. 6테이블 중 `recruit_posts` 만 권위 DDL(`docs/recruit-posts-ddl.sql`)이 존재했다.
|
|
|
|
본 작업에서 `db/schema.sql` 을 **복원**했다:
|
|
|
|
- `recruit_posts` — 권위 DDL 그대로.
|
|
- `user_auth_identities` — security-hardening 의 active-unique index 포함.
|
|
- `users` / `games` / `game_comments` / `game_likes` — **비권위 복원본**. 매퍼 `@Insert`/`@Select` 컬럼과 data POJO Java 타입에서 역추출했다. 컬럼명·유무는 매퍼와 일치 확인했으나, 타입·길이·제약은 추론값이다.
|
|
|
|
적용 방법:
|
|
|
|
- **Docker**: db 컨테이너 최초 기동 시 `db/schema.sql` 이 `docker-entrypoint-initdb.d` 로 자동 1회 실행된다(`dev` 스키마를 채우고, `live` 는 빈 스키마만 생성). 스키마 변경을 재적용하려면 `docker compose down -v` 후 재기동한다.
|
|
- **호스트 로컬 PG**: `psql -f db/schema.sql` 로 수동 적용한다.
|
|
|
|
> 경고: `db/schema.sql` 의 5개 비권위 테이블(users / games / game_comments / game_likes 및 user_auth_identities 의 추론 부분)은 운영 DB `pg_dump` 와 대조하기 전까지 타입을 신뢰하지 말 것. §7 미해결 항목 참조.
|
|
|
|
### 4.1 신규/변경 DDL 을 실행 중 로컬 DB 에 즉시 적용
|
|
|
|
**핵심 사실**: `db/schema.sql` 은 컨테이너 **최초 기동 시 1회만** `docker-entrypoint-initdb.d` 로 자동 주입된다. 그 이후 `docs/*-ddl.sql` 로 추가된 스키마 변경은 **실행 중인 DB 에 자동 반영되지 않는다.** flyway/liquibase 가 없으므로(§4) 변경분을 수동으로 적용해야 한다.
|
|
|
|
`docker compose down -v` 재기동은 스키마를 다시 주입하지만 **로컬 데이터가 전부 소실**된다. 데이터를 보존하면서 변경분만 비파괴 적용하려면 다음 헬퍼를 쓴다.
|
|
|
|
```bash
|
|
# 권위 DDL(docs/*-ddl.sql) 전체를 실행 컨테이너의 dev 스키마에 멱등 적용.
|
|
# 모든 docs/*-ddl.sql 은 IF NOT EXISTS / DO $$ / CREATE OR REPLACE 로 작성되어
|
|
# 몇 번 재실행해도 기존 객체는 skip 되고 누락분만 생성된다.
|
|
db/apply-local-ddl.sh # 전체 적용(기본)
|
|
db/apply-local-ddl.sh docs/game-reviews-ddl.sql # 특정 파일만
|
|
|
|
# 접속 정보는 .env(POSTGRES_USER / POSTGRES_DB / APP_SCHEMA)에서 읽고,
|
|
# DB_CONTAINER / PG_USER / PG_DB / PG_SCHEMA 환경변수로 override 할 수 있다.
|
|
```
|
|
|
|
> 내부적으로 `docker exec -e PGOPTIONS="-c search_path=dev" bibimbap-db psql -U <user> -d <db> -v ON_ERROR_STOP=1 -f -` 로 적용한다. `docs/*-ddl.sql` 은 unqualified 테이블명을 쓰므로 `search_path` 로 스키마를 강제하는 것이 필수다.
|
|
|
|
#### 새 DDL 을 추가할 때의 규약 (작성자 의무)
|
|
|
|
`docs/<기능>-ddl.sql` 을 새로 추가하거나 변경하면 **세 곳을 함께 맞춘다.** 하나라도 빠지면 환경 간 스키마가 어긋난다.
|
|
|
|
1. **`docs/<기능>-ddl.sql`** — 권위 DDL. 모든 문장을 멱등(IF NOT EXISTS / DO `$$` 가드 / CREATE OR REPLACE)으로 작성한다.
|
|
2. **실행 중 로컬 DB** — `db/apply-local-ddl.sh` 를 실행해 즉시 적용한다. → 곧바로 로컬 테스트 가능.
|
|
3. **`db/schema.sql`** — 동일 변경을 반영(`SET search_path TO dev;` 블록 안)해, 신규 환경의 컨테이너 최초 기동 init 에도 포함되게 한다.
|
|
|
|
> 검증: 적용 후 `docker exec bibimbap-db psql -U bibimbap -d bibimbap -c "\d dev.<테이블>"` 로 컬럼·제약·인덱스를 확인한다.
|
|
|
|
### 4.2 검증 진입 전: 가동 중 앱이 "현재 빌드"인지 확인
|
|
|
|
**핵심 함정**: "8080 에 앱이 떠 있음"이 "현재 작업 빌드가 서빙 중"을 보장하지 않는다. 직접 실행(`spring-boot:run` / provided Tomcat)에서 **JSP 는 docBase 가 기동 시점에 동결**되어 파일을 고쳐도 라이브 리로드되지 않는다. 과거 세션에 띄워둔 stale 프로세스가 8080 을 점유한 채 검증을 시작하면 **구 JSP 가 응답**하고 현재 빌드는 검증되지 않는다(거짓 PASS/FAIL). flyway/liquibase 부재(§4)와 같은 결의 함정 — "떠 있음 ≠ 최신".
|
|
|
|
L3/브라우저 스모크·런타임 검증 진입 전 점검:
|
|
|
|
```bash
|
|
# 8080 점유 프로세스의 기동 시각 확인 (오늘 변경분보다 이전이면 stale)
|
|
ps -o lstart,command -p "$(lsof -ti tcp:8080)" 2>/dev/null
|
|
|
|
# JSP 변경분이 실제 서빙되는지 확인 (예: 신규 마커 토큰 grep)
|
|
curl -s http://localhost:8080/<경로> | grep -c '<현재빌드에만-있는-마커>'
|
|
```
|
|
|
|
- JSP/Java 변경분은 **재기동해야** 반영된다(JSP touch 후 재요청해도 docBase 동결로 미반영 확인됨, 20260622).
|
|
- DDL 변경분은 앱 재기동과 무관하게 `db/apply-local-ddl.sh` 로 실행 DB 에 별도 적용한다(§4.1). MyBatis annotation 매퍼는 쿼리타임 반영이라 DDL 적용 후 앱 재기동 불요.
|
|
|
|
## 5. 검증 체크리스트
|
|
|
|
실제 통과한 항목은 `[x]` 다.
|
|
|
|
- [x] A: 빈 상태 → 의존성 수신 → 컴파일 (BUILD SUCCESS)
|
|
- [x] A: 앱 기동 후 홈 `/` HTTP 200 + 게임목록 렌더
|
|
- [x] A: DB 연결 정상 (6테이블, 빈 조회 무에러, 삽입 → 홈노출 라운드트립)
|
|
- [x] B: `./mvnw -P dev test` green (6 tests, 0 fail)
|
|
- [x] B: `./mvnw -P dev package` WAR 산출
|
|
- [x] B: `./mvnw -P dev spring-boot:run` 홈 200 (provided tomcat)
|
|
- [x] 신규 환경파일 비밀값 비커밋 (`.env` / `db.properties` / `certs/*.crt` gitignore 확인)
|
|
- [ ] (수동) 실제 사용자 환경에서 회원가입/로그인/게임등록 등 기능 스모크는 미수행 — 사용자 환경 검증 권장.
|
|
|
|
## 6. 트러블슈팅
|
|
|
|
### TLS 인터셉션 프록시로 SNAPSHOT 빌드 실패
|
|
|
|
- 증상: `PKIX path building failed: unable to find valid certification path to requested target` + `Non-resolvable import POM: spring-boot-dependencies:3.5.14-SNAPSHOT`.
|
|
- 원인: `repo.spring.io` 인증서를 회사 프록시(예: Cloudflare Gateway / Zero Trust)가 재서명하는데, JDK truststore 에 그 CA 가 없다. `curl` 은 시스템 키체인을 쓰므로 성공해도 **JDK 는 자체 cacerts** 를 쓰기 때문에 별도 import 가 필요하다.
|
|
- 해결:
|
|
- Docker — `certs/*.crt` 를 Dockerfile 이 자동 주입한다.
|
|
- 호스트 — §3 단계 3 의 `keytool` import 를 수행한다.
|
|
- CA 추출 예시 (macOS):
|
|
|
|
```bash
|
|
security find-certificate -a -c "Gateway CA - Cloudflare Managed" -p \
|
|
~/Library/Keychains/login.keychain-db /Library/Keychains/System.keychain \
|
|
> certs/corporate-proxy-ca.crt
|
|
```
|
|
|
|
자세한 내용은 `certs/README.md` 참조.
|
|
|
|
### db.properties 누락
|
|
|
|
`spring.config.import` 가 `optional:` 이라 컨텍스트는 뜨지만 datasource 가 구성되지 않아 DB 사용 페이지에서 실패한다. `.example` 을 복사하고 실값을 입력한다(§3 단계 5).
|
|
|
|
### 포트 충돌
|
|
|
|
- 호스트 5432 가 다른 postgres 에 점유되면 `.env` 의 `DB_PORT` 를 변경한다(예 5433). 실제로 본 환경에서는 기존 `kord-postgres` 가 5432 를 점유해 5433 으로 회피했다.
|
|
- 8080 이 점유되면 `APP_PORT` 를 변경한다.
|
|
|
|
### Java 미설치
|
|
|
|
`Unable to locate a Java Runtime` → §3 단계 1 의 `brew install openjdk@21`.
|
|
|
|
### "no main manifest attribute"
|
|
|
|
§3 주의 참조 — `spring-boot:repackage` 를 명시 호출해야 실행형 WAR 가 나온다.
|
|
|
|
## 7. 알려진 미해결
|
|
|
|
- **DDL 전체본 부재**: `db/schema.sql` 5테이블은 비권위 복원본(타입 추론)이다. 운영 DB `pg_dump` 대조가 필요하다. (open_question)
|
|
- **spring-boot 3.5.14-SNAPSHOT 고정 결정 대기**: SNAPSHOT 은 비재현·온라인 의존이다. 안정 릴리스 고정 여부가 미결이다 — `../analysis/2026-06-16-project-analysis.md` open_question #4.
|
|
- **기능 스모크 미수행**: 비권위 `db/schema.sql` 타입으로 인해 가입/업로드 등 기능 스모크는 수행하지 않았다.
|
|
|
|
## 참고: 본 가이드가 참조하는 생성 파일
|
|
|
|
- 루트: `docker-compose.yml`, `Dockerfile`, `.env.example`, `.dockerignore`, `db/schema.sql`, `certs/README.md`.
|
|
- gitignore 대상(비밀): `.env`, `src/main/resources/dev/db.properties`, `certs/*.crt`.
|