7.2 KiB
Local Dev Setup — 로컬 개발 환경 설정
로컬에서 앱을 구동할 때 필요한 환경/경로 설정을 둔다.
⚠️ 코드·JSP 변경 후 반드시 앱 재빌드·재시작 (가장 흔한 함정)
소스(Java 컨트롤러/매퍼/서비스)나 JSP(/WEB-INF/views/*.jsp) 를 고쳐도, 돌고 있는 앱은 자동으로 반영되지 않는다. 변경을 커밋·테스트 통과까지 해도 실행 중인 인스턴스가 구 버전이면 화면 동작은 그대로다.
증상 예: 좋아요/댓글 등 신규 서버 기능을 추가했는데 "고친 게 반영이 안 된다" — 십중팔구 앱 미재시작. 특히 클라이언트 동작(예: 로그인 안 했는데도 버튼이 눌리고 카운트가 로컬에서만 변함)이 보이면 구 JSP 가 서빙 중이라는 신호다.
구동 방식별 재배포:
| 구동 방식 | 재빌드·재시작 |
|---|---|
IDE / 호스트 JVM (./mvnw spring-boot:run, 또는 IntelliJ Run) |
앱 Stop → 프로젝트 rebuild → 다시 Run. 컴파일된 컨트롤러 변경은 hot-reload 안 됨(devtools 미사용). JSP 변경도 재기동으로 확실히 반영. |
| docker compose (로컬 dev — 권장) | 아래 "로컬 도커 dev" 참조. JSP 는 즉시, Java 는 docker compose restart app. 이미지 재빌드 불필요. |
| docker compose (배포 이미지 빌드) | docker compose -f docker-compose.yml up -d --build app — Dockerfile 로 WAR 굽기. 배포/운영용. |
| 수동 WAR | ./mvnw -P dev clean package spring-boot:repackage -DskipTests 로 실행형 WAR 재패키지(프로필·repackage goal 주의 — 본 문서 함정 참조) 후 재기동. |
확인: 재시작 후 의도한 신규 엔드포인트가 응답하는지 1회 스모크(예: 좋아요는 로그인 상태에서 클릭 → 새로고침·목록에서도 카운트 유지). 미로그인 클릭은 신 코드에선 401(로그인 유도)이 정상 — 더 이상 클라이언트에서 토글되지 않는다.
코드 변경을 핸드오프할 때는 이 재빌드·재시작 단계를
needs_user_verification/배포 후 체크리스트에 명시한다. 커밋·테스트 통과 ≠ 실행 인스턴스 반영.
로컬 도커 dev (override) vs 배포 이미지 — 분리
로컬은 이미지 재빌드 없이 빠르게 돌리고, 배포 이미지는 별도 경로로 굽는다.
-
로컬 dev:
docker-compose.override.yml(git 추적, dev 기본)이app을 재정의한다 —maven:3.9-eclipse-temurin-21이미지에 소스를 bind-mount(.:/build)하고 호스트~/.m2캐시를 물려mvn -o -P dev spring-boot:run으로 띄운다.docker compose는 base + override 를 자동 병합하므로 평소엔 그냥:docker compose up -d app # 로컬 dev (override 자동 적용, WAR 안 구움) docker compose logs -f app반영(이미지 재빌드 0):
- JSP/정적: 저장 즉시(Jasper 가 마운트된 webapp 재컴파일). 재시작 불필요.
- Java 코드:
docker compose restart app(spring-boot:run 재기동 ~1~2s). 또는docker compose exec app mvn -o -P dev -DskipTests compile후 restart. - 전제: 호스트
~/.m2에 의존성 캐시 존재(offline-o). 새 의존성 추가 시: override 의-o를 일시 제거해 1회 온라인 받거나, 호스트에서./mvnw -P dev dependency:go-offline후 다시 offline.
-
배포 이미지: override 를 제외하고 base 만 명시한다(자동병합 회피).
docker compose -f docker-compose.yml build app # Dockerfile WAR 굽기 docker compose -f docker-compose.yml up -d --build appDockerfile 은 멀티스테이지(WAR repackage →
eclipse-temurin:21-jre+java -jar)로 운영과 동일한 산출물을 만든다(프록시 CA·-P dev·repackage goal 은 본 문서 함정 참조).
devtools 자동재시작은 도입하지 않았다 — 컨테이너
spring-boot:run에는 소스 자동 재컴파일러가 없어(IDE 부재) 이득이 작고,3.5.x-SNAPSHOTdevtools 의 offline 해소가 불안정했다.restart/exec compile루프가 더 견고하다.
업로드 저장 루트 (static 트리 밖)
업로드물(프로필 이미지·게임 WebGL asset)은 웹서버 정적 서빙 트리(src/main/resources/static/) 밖에 저장한다. 직접 서빙을 차단하고 컨트롤러 권한 게이트를 강제하기 위함이다(보안 하드닝, commit 9041bb7).
경로 규약
| 설정 키 | 값 | 비고 |
|---|---|---|
app.upload.game-storage-path |
${user.home}/.bibimbap/uploads |
application.properties 에 명시. ${user.home} 는 Spring 런타임 placeholder |
- 프로필 이미지:
~/.bibimbap/uploads/profile/{userId}/*.{png,jpg}—UploadResourceConfig의/profile/**ResourceHandler 가 이 위치를 서빙. - 게임 WebGL asset:
~/.bibimbap/uploads/game/{gameUuid}/**—GameAssetController @GetMapping("/game/{gameUuid}/**")컨트롤러가 경계 검증 후 서빙(정적 핸들러 아님).
디렉토리 생성
런타임에 Files.createDirectories 로 자동 생성된다(GameUploadController·UserController). 첫 업로드 시 ~/.bibimbap/uploads/{game,profile} 가 만들어지므로 수동 생성 불필요.
@Value 기본값 주의
5개 클래스(UploadResourceConfig·GameUploadController·GameAssetController·UserController·GameAssetCleanupService)의 @Value("${app.upload.game-storage-path:src/main/resources/static}") 기본값(fallback)은 src/main/resources/static 으로 유지한다. 이유:
application.properties의 명시 설정이 항상 오버라이드하므로 실제 동작 경로는~/.bibimbap/uploads.- 기본값을 홈 경로로 바꾸면 IDE/CI 가 명시 설정 없이 홈 디렉토리에 파일을 생성하는 부작용이 생긴다.
- 업로드 테스트(
GameUploadControllerSecurityTest)는@TempDir+ReflectionTestUtils.setField로 경로를 격리하므로 기본값과 무관.
자산 이전 이력
저장 루트가 static 트리 밖으로 이전되면서, 이전부터 static/profile/ 에 커밋돼 있던 자산은 자동 이동되지 않는다. 신규 루트로 수동 이전이 필요하다.
| 일자 | 이전 대상 | 조치 |
|---|---|---|
| 2026-06-29 | src/main/resources/static/profile/8/* (user 8 프로필 2파일) |
~/.bibimbap/uploads/profile/8/ 로 이동(체크섬 대조 검증) + tracked 원본 git rm. 이후 user 8 프로필은 신규 루트에서 서빙됨. |
static/game/은 이전 시점 비어 있어(커밋된 게임 자산 0) 이전 대상 없음.
향후 저장 루트를 다시 옮기거나 자산을 마이그레이션할 때 이 표에 한 줄씩 기록한다.
SSRF DNS 캐시 (운영 참고)
SsrfSafeFetcher 가 @PostConstruct 로 networkaddress.cache.ttl=30 을 best-effort 설정한다(DNS rebinding TOCTOU 완화). 런타임 반영은 InetAddressCachePolicy lazy init 경합에 의존하므로 결정적 보장이 필요하면 JVM 레벨($JAVA_HOME/conf/security/java.security 의 networkaddress.cache.ttl=30)이 정본이다. 앱 기동 후 java.security.Security.getProperty("networkaddress.cache.ttl") 로 반영 여부를 확인할 수 있다(배포 후 체크리스트 참조: ../maintenance/post-deploy-verification-checklist.md).