오픈소스 코드베이스를 "읽는" 대신 "질문에 답하며" 익히도록 돕는 Kotlin + Spring Boot 서버.
개발자가 오픈소스의 코드 구조와 핵심 개발 맥락을, AI가 생성한 질문을 기반으로 학습할 수 있도록 돕는 것을 목표로 합니다. 저장소를 훑어보는 것만으로는 잘 남지 않는 설계 의도와 흐름을, 질문에 답해보는 과정을 통해 체득하게 하는 것이 이 서버의 역할입니다.
| 구분 | 사용 기술 |
|---|---|
| 언어 | Kotlin 2.3.21 |
| 런타임 | JDK 25 (Gradle toolchain) |
| 프레임워크 | Spring Boot 4.1.0 (Spring MVC) |
| 데이터베이스 | MongoDB 8 (Spring Data MongoDB) |
베이스 패키지는 com.nexters.gitit 입니다.
com.nexters.gitit
├── domain ← 순수 비즈니스 로직 (외부 의존 없음)
│ ├── model
│ └── repository (인터페이스만)
│
├── application ← domain에만 의존, 유스케이스 처리
│ └── service
│
├── infrastructure ← 기술적 관심사, domain 인터페이스의 구현체 (DIP)
│ └── persistence
│
└── ui ← application만 호출
└── controller
의존 방향
ui → application → domain ← infrastructure
infrastructure는 기술적 관심사를 담당하며 domain이 선언한 인터페이스의 구현체를 두므로, 의존성 역전 (DIP)에 따라 화살표가 domain 쪽을 향합니다.
- JDK 25
- Docker (로컬 MongoDB 및 테스트용 Testcontainers 실행)
cp .env.example .env.env.example에서 값이 비어 있는 항목은 채워야 합니다. 채우지 않으면 기동에 실패합니다.
Spring Boot는
.env파일을 자동으로 읽지 않습니다. 셸에export하거나 IDE 실행 구성의 환경 변수에 넣어야 합니다.
docker compose -f docker-compose.local.yml up -dmongo:8 컨테이너 (git-it-mongodb)가 27017 포트로 뜨고, 데이터는 mongodb-data 볼륨에 유지됩니다. healthcheck가 포함되어 있으므로 준비 상태는 아래로 확인합니다.
docker compose -f docker-compose.local.yml ps./gradlew bootRun- 서버: http://localhost:8080
- Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI 문서: http://localhost:8080/v3/api-docs
docker compose -f docker-compose.local.yml down # 컨테이너만 정리
docker compose -f docker-compose.local.yml down -v # 저장된 데이터까지 삭제./gradlew test./gradlew ktlintCheck detekt # 검사
./gradlew ktlintFormat # 자동 포맷- 포맷 규칙은
.editorconfig를 따릅니다. - detekt 규칙은
config/detekt/detekt.yml에서 관리합니다. - PR을 올리기 전 확인 항목은 PR 템플릿의 체크리스트를 참고하세요.
main에 push(=PR 머지)되면 .github/workflows/cd.yml이 실행되어 이미지를
ghcr.io/nexters/git-it-server에 push하고, 서버에 SSH로 접속해 재배포합니다.
태그는 커밋 SHA와 latest 두 개가 붙고, 서버는 latest를 pull합니다.
Dockerfile 없이 Jib Gradle 플러그인이 이미지를 만듭니다.
설정은 build.gradle.kts의 jib { } 블록에 있습니다.
./gradlew jibDockerBuild --image=git-it-server:local # 로컬 Docker 데몬에만 빌드베이스 이미지는 eclipse-temurin:25-jre이고 non-root(uid 1000)로 실행됩니다.
운영 서버는 https://git-it.kr 로 서비스합니다. docker-compose.prod.yml이 앱·MongoDB와 함께
Nginx와 Certbot을 띄웁니다.
- Nginx가 80·443을 받아
app:8080으로 넘깁니다. 80으로 들어온 요청은 ACME 검증 경로 (/.well-known/acme-challenge/)만 통과시키고 나머지는 HTTPS로 리다이렉트합니다. - 설정은
nginx/nginx.conf이며 배포마다 서버로 전송됩니다. - 인증서는
~/git-it/certbot/conf에 보관됩니다. 최초 발급은 배포 스크립트가 standalone 방식으로 한 번 수행하고, 이후 갱신은 Certbot 컨테이너가 12시간마다 webroot 방식으로 처리합니다. Nginx는 6시간마다 reload해 갱신된 인증서를 집어 옵니다.
서버에서 80·443 인바운드가 열려 있어야 합니다. 80이 막히면 ACME 검증이 실패해 발급 자체가 되지 않습니다.
Actions → Backend CD → Run workflow로 브랜치를 골라 실행할 수 있습니다.
⚠️ 수동 실행은mainpush와 완전히 동일하게 동작합니다. 고른 브랜치의 코드가 그대로 운영에 배포되고latest태그도 그 이미지로 옮겨갑니다. 되돌리려면main을 다시 배포해야 합니다.
| Secret | 용도 |
|---|---|
API_SERVER_HOST |
서버 고정 공인 IP |
API_SERVER_USERNAME |
SSH 접속 유저 |
API_SERVER_KEY |
배포 전용 SSH 개인키 |
API_SERVER_PORT |
SSH 포트 |
배포 스크립트가 아래 값들로 서버의 .env를 조립합니다. 이름과 의미는 .env.example과 같습니다.
| Secret | 비고 |
|---|---|
MONGODB_HOST |
mongodb (docker-compose 서비스명) |
MONGODB_PORT |
27017 |
MONGODB_DATABASE |
|
MONGODB_USERNAME |
|
MONGODB_PASSWORD |
|
JWT_SECRET |
|
OAUTH_GOOGLE_CLIENT_ID |
|
OAUTH_APPLE_CLIENT_ID |
|
GITHUB_WORK_DIR |
앱 컨테이너 안의 경로 |
GCP_CREDENTIALS_BASE64 |
서비스 계정 JSON을 base64로 인코딩한 값 |
MONGODB_HOST를localhost로 두면 안 됩니다. 운영에서는app과mongodb가 같은 Docker 네트워크의 별개 컨테이너이므로localhost는 app 컨테이너 자신을 가리켜 연결에 실패합니다.
GHCR 인증은 워크플로가 GITHUB_TOKEN으로 docker login한 결과(~/.docker/config.json)를
Jib이 읽어 가므로 별도 시크릿이 필요 없습니다.
서버 사전 준비사항은
docs/superpowers/specs/2026-08-03-ci-cd-design.md를
참고하세요. 단, 이 문서는 Dockerfile로 빌드하던 시점에 작성되어 이미지 빌드 부분은 현재와 다릅니다.