813 lines
23 KiB
Markdown
813 lines
23 KiB
Markdown
# GoldenChart 서버 배포 가이드 (exdev.co.kr + Jenkins)
|
||
|
||
> **대상 프로젝트:** `goldenChart` (React Frontend + Spring Boot Backend + MySQL)
|
||
> **배포 서버:** `exdev.co.kr`
|
||
> **문서 버전:** 1.0 · 최종 갱신: 2026-05-23
|
||
|
||
Mac Studio HFT 배포 매뉴얼의 **Git Hook(Track A)** · **Jenkins(Track B)** 패턴을 GoldenChart에 맞게 단순화한 문서입니다.
|
||
이 프로젝트는 **Docker Compose 3서비스**(mysql / backend / frontend) 중심이며, 별도 PM2·AI Agent 스택은 포함하지 않습니다.
|
||
|
||
---
|
||
|
||
## 목차
|
||
|
||
1. [시스템 개요](#1-시스템-개요)
|
||
2. [환경 정보 및 경로](#2-환경-정보-및-경로)
|
||
3. [사전 준비 (최초 1회)](#3-사전-준비-최초-1회)
|
||
4. [케이스 A: 로컬에서 Git push → 자동 배포](#4-케이스-a-로컬에서-git-push--자동-배포)
|
||
5. [케이스 B: deploy-home.sh 수동 배포 (개발용)](#5-케이스-b-deploy-homesh-수동-배포-개발용)
|
||
6. [케이스 C: Jenkins 스케줄 자동 배포](#6-케이스-c-jenkins-스케줄-자동-배포)
|
||
7. [케이스 D: Jenkins 수동·파라미터 배포](#7-케이스-d-jenkins-수동파라미터-배포)
|
||
8. [케이스 E: 서버에서 직접 배포](#8-케이스-e-서버에서-직접-배포)
|
||
9. [케이스 F: 서비스별 선택 배포](#9-케이스-f-서비스별-선택-배포)
|
||
10. [배포 후 검증](#10-배포-후-검증)
|
||
11. [일상 운영 명령어](#11-일상-운영-명령어)
|
||
12. [트러블슈팅](#12-트러블슈팅)
|
||
13. [부록: deploy.sh · post-receive · Jenkinsfile](#13-부록-deploysh--post-receive--jenkinsfile)
|
||
|
||
---
|
||
|
||
## 1. 시스템 개요
|
||
|
||
GoldenChart 배포는 **공통 스크립트 `deploy.sh`** 를 중심으로 두 가지 트랙을 운영합니다.
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────────────┐
|
||
│ deploy.sh (서버 공통 진입점) │
|
||
│ 예: /Users/aidev/git/deploy.sh (서버에 1회 설치) │
|
||
└───────────────┬────────────────────────────┬───────────────────────┘
|
||
│ │
|
||
Track A (Express) Track B (Scheduled)
|
||
git push main → 즉시 배포 Jenkins → 예약/승인 배포
|
||
│ │
|
||
docker compose build/up docker compose build/up
|
||
(frontend / backend 선택 가능) (backend·DB 마이그레 권장)
|
||
```
|
||
|
||
| 트랙 | 트리거 | 적합한 변경 | 다운타임 | 실행 도구 |
|
||
|------|--------|------------|---------|----------|
|
||
| **Track A** | `git push exdev main` | UI, 소규모 API, 설정 | 짧음 (컨테이너 재기동) | Git `post-receive` |
|
||
| **Track B** | Jenkins 크론/수동 | Flyway DB 마이그레, 대규모 백엔드 | 짧음 | Jenkins Freestyle/Pipeline |
|
||
| **개발용** | 로컬 `./deploy-home.sh` | 긴급·초기 구축 | `down` 후 재기동 | SSH + tar 동기화 |
|
||
|
||
### deploy.sh 4단계 (권장)
|
||
|
||
| Phase | 작업 | 설명 |
|
||
|-------|------|------|
|
||
| Phase 1 | Git checkout | bare repo → work-tree 동기화 |
|
||
| Phase 2 | 빌드 | Docker 이미지 빌드 (`docker compose build`) |
|
||
| Phase 3 | 교체 | `docker compose up -d` (서비스별 `--no-deps` 가능) |
|
||
| Phase 4 | 검증 | Frontend HTTP + Backend API 응답 확인 |
|
||
|
||
> **참고:** 저장소에 포함된 [`deploy-home.sh`](./deploy-home.sh) 는 Git 없이 **tar 압축 + SCP** 로 동기화하는 별도 경로입니다. Jenkins·Git Hook 구축 전까지 또는 긴급 배포에 사용합니다.
|
||
|
||
---
|
||
|
||
## 2. 환경 정보 및 경로
|
||
|
||
### 서버 (exdev.co.kr)
|
||
|
||
| 항목 | 값 (기본) |
|
||
|------|-----------|
|
||
| 호스트 | `exdev.co.kr` |
|
||
| SSH 사용자 | `aidev` |
|
||
| SSH 포트 | `22` |
|
||
| 프로젝트 work-tree | `/Users/aidev/dev/goldenChart` |
|
||
| Git bare 저장소 (권장) | `/Users/aidev/git/goldenChart.git` |
|
||
| 배포 스크립트 (권장) | `/Users/aidev/git/deploy.sh` |
|
||
| 배포 로그 (권장) | `/Users/aidev/logs/goldenchart/deploy.log` |
|
||
|
||
> 서버 OS·디렉터리는 환경에 맞게 조정하세요. [`deploy-home.sh`](./deploy-home.sh) 기본값은 work-tree를 `/Users/aidev/dev/goldenChart` 로 사용합니다.
|
||
|
||
### 로컬 개발 PC
|
||
|
||
| 항목 | 예시 경로 |
|
||
|------|-----------|
|
||
| 소스 | `~/dev/goldenChart` |
|
||
| Git remote 이름 | `exdev` (권장) |
|
||
|
||
### 서비스 포트 (docker-compose.yml)
|
||
|
||
| 서비스 | 컨테이너 | 포트 (기본) | URL |
|
||
|--------|---------|------------|-----|
|
||
| Frontend (Nginx) | `gc-frontend` | 80 | http://exdev.co.kr/ |
|
||
| Backend (Spring Boot) | `gc-backend` | 8080 | http://exdev.co.kr:8080 (직접) 또는 `/api` (Nginx 경유) |
|
||
| MySQL | `gc-mysql` | 3306 | localhost:3306 (서버 내부) |
|
||
|
||
브라우저에서는 Nginx가 `/api` → `backend:8080` 으로 프록시합니다.
|
||
|
||
### Docker Compose 서비스
|
||
|
||
| 서비스명 | 이미지/빌드 | 역할 |
|
||
|---------|------------|------|
|
||
| `mysql` | mysql:8.0 | DB + Flyway 마이그레이션 대상 |
|
||
| `backend` | `backend/Dockerfile` | Spring Boot API, WebSocket(STOMP) |
|
||
| `frontend` | `frontend/Dockerfile` | React 빌드 + Nginx |
|
||
|
||
### 환경 변수 (.env)
|
||
|
||
서버 work-tree 루트에 `.env` 파일을 두세요. 예시는 [`.env.example`](./.env.example) 참고.
|
||
|
||
```bash
|
||
MYSQL_ROOT_PASSWORD=... # 운영 환경에서 반드시 변경
|
||
MYSQL_DATABASE=stockAnalyzer
|
||
MYSQL_USER=stock
|
||
MYSQL_PASSWORD=...
|
||
BACKEND_PORT=8080
|
||
FRONTEND_PORT=80
|
||
# backend/docker-compose 환경
|
||
GC_SECRETS_ENCRYPTION_KEY=... # 운영용 강한 키로 변경
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 사전 준비 (최초 1회)
|
||
|
||
### 3-1. 서버(exdev.co.kr) 인프라 확인
|
||
|
||
서버에 SSH 접속 후 실행합니다.
|
||
|
||
```bash
|
||
ssh aidev@exdev.co.kr
|
||
|
||
# Docker
|
||
docker --version
|
||
docker compose version
|
||
docker ps
|
||
|
||
# 디렉터리 생성
|
||
mkdir -p /Users/aidev/git
|
||
mkdir -p /Users/aidev/logs/goldenchart
|
||
mkdir -p /Users/aidev/dev/goldenChart
|
||
```
|
||
|
||
### 3-2. Git Bare 저장소 생성
|
||
|
||
```bash
|
||
# 서버에서 실행
|
||
git init --bare /Users/aidev/git/goldenChart.git
|
||
|
||
# work-tree (배포 대상) — 이미 clone이 있으면 생략 가능
|
||
# 최초 1회: bare에서 checkout
|
||
git --git-dir=/Users/aidev/git/goldenChart.git \
|
||
--work-tree=/Users/aidev/dev/goldenChart \
|
||
checkout -f main 2>/dev/null || true
|
||
```
|
||
|
||
### 3-3. deploy.sh 설치
|
||
|
||
서버에 공통 배포 스크립트를 둡니다. **부록 13-1** 의 `deploy.sh` 내용을 복사합니다.
|
||
|
||
```bash
|
||
# 로컬에서 서버로 복사 (예시)
|
||
scp scripts/deploy.sh aidev@exdev.co.kr:/Users/aidev/git/deploy.sh
|
||
|
||
# 서버에서
|
||
chmod +x /Users/aidev/git/deploy.sh
|
||
```
|
||
|
||
> 저장소에 `scripts/deploy.sh` 가 없으면 [부록 13-1](#13-1-deploysh-서버용) 을 참고해 서버에 직접 생성하세요.
|
||
|
||
### 3-4. post-receive 훅 등록 (Track A)
|
||
|
||
```bash
|
||
cat > /Users/aidev/git/goldenChart.git/hooks/post-receive << 'HOOK'
|
||
#!/bin/bash
|
||
set -euo pipefail
|
||
while read oldrev newrev refname; do
|
||
branch="${refname#refs/heads/}"
|
||
if [ "$branch" = "main" ]; then
|
||
echo "[post-receive] main push → deploy.sh"
|
||
SERVICE_NAME="${SERVICE_NAME:-all}" /Users/aidev/git/deploy.sh
|
||
else
|
||
echo "[post-receive] $branch — 배포 생략 (main만 자동 배포)"
|
||
fi
|
||
done
|
||
HOOK
|
||
|
||
chmod +x /Users/aidev/git/goldenChart.git/hooks/post-receive
|
||
```
|
||
|
||
### 3-5. 로컬 PC — Git remote 등록
|
||
|
||
```bash
|
||
cd ~/dev/goldenChart
|
||
|
||
# SSH remote (권장)
|
||
git remote add exdev ssh://aidev@exdev.co.kr/Users/aidev/git/goldenChart.git
|
||
|
||
# SSH 키 등록 (최초 1회)
|
||
ssh-copy-id aidev@exdev.co.kr
|
||
|
||
# 연결 테스트
|
||
git ls-remote exdev
|
||
```
|
||
|
||
### 3-6. 최초 push (저장소·work-tree 초기화)
|
||
|
||
```bash
|
||
cd ~/dev/goldenChart
|
||
git checkout main
|
||
git push -u exdev main
|
||
```
|
||
|
||
push 완료 시 서버 `post-receive` → `deploy.sh` 가 자동 실행됩니다.
|
||
|
||
### 3-7. Jenkins 설치 및 초기 설정 (Track B)
|
||
|
||
서버에 Jenkins가 없다면 Homebrew + PM2 또는 Docker 중 하나로 설치합니다.
|
||
|
||
**Homebrew 예시:**
|
||
|
||
```bash
|
||
brew install jenkins-lts openjdk@21
|
||
# Jenkins URL: http://127.0.0.1:8080 또는 8090 (설치 방식에 따름)
|
||
```
|
||
|
||
**Jenkins 전역 환경 변수 (필수):**
|
||
|
||
Jenkins → **Manage Jenkins** → **System** → **Global properties** → **Environment variables**
|
||
|
||
| 변수명 | 값 (macOS Apple Silicon 예시) |
|
||
|--------|------------------------------|
|
||
| `PATH` | `/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin` |
|
||
| `JAVA_HOME` | `/opt/homebrew/opt/openjdk@21` |
|
||
|
||
**Execute shell** 블록 맨 위에 항상 추가:
|
||
|
||
```bash
|
||
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||
```
|
||
|
||
### 3-8. 서버 .env 및 방화벽
|
||
|
||
```bash
|
||
cd /Users/aidev/dev/goldenChart
|
||
cp .env.example .env
|
||
# vi .env — 비밀번호·암호화 키 변경
|
||
|
||
# 외부에서 80/8080 접근 필요 시 (서버 방화벽·라우터 설정은 환경별로 확인)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 케이스 A: 로컬에서 Git push → 자동 배포
|
||
|
||
> **용도:** 개발 완료 후 exdev.co.kr에 반영
|
||
> **트리거:** `git push exdev main`
|
||
> **특징:** Track A — `main` 브랜치만 자동 배포
|
||
|
||
### 4-1. 일반 워크플로
|
||
|
||
```bash
|
||
cd ~/dev/goldenChart
|
||
|
||
git status
|
||
git add .
|
||
git commit -m "feat: 알림 팝업 레이아웃 개선"
|
||
|
||
git push exdev main
|
||
```
|
||
|
||
### 4-2. 자동 배포 흐름
|
||
|
||
```
|
||
[로컬] git push exdev main
|
||
↓
|
||
[exdev] post-receive (main만)
|
||
↓
|
||
/Users/aidev/git/deploy.sh
|
||
↓
|
||
Phase 1: git checkout → /Users/aidev/dev/goldenChart
|
||
Phase 2: docker compose build
|
||
Phase 3: docker compose up -d
|
||
Phase 4: HTTP/API 헬스 확인
|
||
```
|
||
|
||
### 4-3. 배포 로그 확인 (서버)
|
||
|
||
```bash
|
||
ssh aidev@exdev.co.kr
|
||
tail -f /Users/aidev/logs/goldenchart/deploy.log
|
||
```
|
||
|
||
### 4-4. feature 브랜치 (배포 없음)
|
||
|
||
```bash
|
||
git push exdev feature/my-branch
|
||
# → post-receive가 main이 아니면 deploy.sh 실행 안 함
|
||
```
|
||
|
||
### 4-5. Track A에 적합한 변경
|
||
|
||
| 변경 유형 | Track A | 비고 |
|
||
|----------|---------|------|
|
||
| `frontend/src/**` | ✅ | `SERVICE_NAME=frontend` 로 더 빠르게 가능 |
|
||
| `backend/src/**` (소규모) | ✅ | Flyway 변경 있으면 Track B 권장 |
|
||
| `db/migration/V*.sql` 신규 | ⚠️ | Jenkins·장 마감 후 배포 권장 |
|
||
| `docker-compose.yml` | ⚠️ | `docker-only` 또는 전체 재배포 |
|
||
|
||
---
|
||
|
||
## 5. 케이스 B: deploy-home.sh 수동 배포 (개발용)
|
||
|
||
> **용도:** Git/Jenkins 미구축 시, 또는 긴급 전체 동기화
|
||
> **실행 위치:** 로컬 Mac (`goldenChart` 루트)
|
||
|
||
```bash
|
||
cd ~/dev/goldenChart
|
||
|
||
# 전체 동기화 + Docker 재빌드 (기본, --no-cache)
|
||
./deploy-home.sh
|
||
|
||
# 프론트엔드만
|
||
./deploy-home.sh --frontend
|
||
|
||
# 백엔드만
|
||
./deploy-home.sh --backend
|
||
|
||
# 캐시 사용 (빠름)
|
||
./deploy-home.sh --use-cache
|
||
|
||
# 상태 / 로그 / 중지
|
||
./deploy-home.sh --status
|
||
./deploy-home.sh --logs
|
||
./deploy-home.sh --down
|
||
./deploy-home.sh --shell
|
||
```
|
||
|
||
| 옵션 | 설명 |
|
||
|------|------|
|
||
| (기본) | tar 동기화 → `docker compose down` → build → up |
|
||
| `--no-sync` | 서버 기존 소스로만 재빌드 |
|
||
| `--use-cache` | Docker 빌드 캐시 사용 |
|
||
| `--frontend` / `--backend` | 해당 서비스만 재빌드 |
|
||
|
||
환경 변수로 서버 지정:
|
||
|
||
```bash
|
||
REMOTE_HOST=exdev.co.kr REMOTE_USER=aidev ./deploy-home.sh
|
||
```
|
||
|
||
> **주의:** `deploy-home.sh` 는 서버 디렉터리를 **삭제 후 tar 전체 덮어쓰기** 합니다. 서버 전용 `.env`·로컬 수정 사항이 있다면 백업 후 사용하세요.
|
||
> 정식 운영은 **Git push + deploy.sh** 를 권장합니다.
|
||
|
||
---
|
||
|
||
## 6. 케이스 C: Jenkins 스케줄 자동 배포
|
||
|
||
> **용도:** Spring Boot + Flyway(DB 스키마) 정기 배포
|
||
> **트리거:** Jenkins 크론 또는 Poll SCM
|
||
|
||
### 6-1. Jenkins Job 생성 (최초 1회)
|
||
|
||
1. Jenkins UI 접속 (예: `http://127.0.0.1:8090` — 서버에서만 접근 가능할 수 있음)
|
||
2. **New Item** → `goldenChart-Pipeline` → **Freestyle project**
|
||
|
||
**Source Code Management:**
|
||
|
||
```
|
||
[x] Git
|
||
Repository URL : file:///Users/aidev/git/goldenChart.git
|
||
Branch : */main
|
||
```
|
||
|
||
**Build Triggers (택 1):**
|
||
|
||
| 시나리오 | 크론 표현식 |
|
||
|---------|-----------|
|
||
| 월~금 18:00 (장 마감 후) | `0 18 * * 1-5` |
|
||
| 매주 토 02:00 | `0 2 * * 6` |
|
||
| 1시간마다 변경 감지 | Poll SCM `H * * * *` |
|
||
|
||
**Build → Execute shell:**
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -euo pipefail
|
||
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||
|
||
echo "=== goldenChart Jenkins Build #${BUILD_NUMBER} ==="
|
||
echo "시각: $(date)"
|
||
|
||
SERVICE_NAME=backend /Users/aidev/git/deploy.sh
|
||
```
|
||
|
||
### 6-2. Jenkins에서 Git·Docker 경로 확인
|
||
|
||
빌드 로그에 다음이 출력되어야 합니다.
|
||
|
||
```bash
|
||
which git && which docker && docker compose version
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 케이스 D: Jenkins 수동·파라미터 배포
|
||
|
||
> **용도:** 배포 시점·대상·승인을 UI에서 제어
|
||
|
||
### 7-1. 파라미터 설정
|
||
|
||
Job → **General** → **This project is parameterized**
|
||
|
||
| 파라미터 | 타입 | 이름 | 선택값 |
|
||
|---------|------|------|--------|
|
||
| 배포 서비스 | Choice | `SERVICE_NAME` | `all` / `frontend` / `backend` / `docker-only` |
|
||
| 배포 유형 | Choice | `DEPLOY_TYPE` | `after_market` / `immediate` |
|
||
| 배포 승인 | Boolean | `DEPLOY_APPROVED` | default: false |
|
||
|
||
**Execute shell:**
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -euo pipefail
|
||
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||
|
||
if [ "${DEPLOY_APPROVED:-false}" != "true" ]; then
|
||
echo "[ERROR] DEPLOY_APPROVED=true 필요"
|
||
exit 1
|
||
fi
|
||
|
||
if [ "${DEPLOY_TYPE:-after_market}" = "after_market" ]; then
|
||
HOUR=$(date +%H)
|
||
if [ "$HOUR" -ge 9 ] && [ "$HOUR" -lt 18 ]; then
|
||
echo "[ERROR] 장중(09~18시) after_market 배포 불가"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
echo "SERVICE_NAME=${SERVICE_NAME:-all}"
|
||
SERVICE_NAME="${SERVICE_NAME:-all}" /Users/aidev/git/deploy.sh
|
||
```
|
||
|
||
### 7-2. UI에서 실행
|
||
|
||
1. `goldenChart-Pipeline` → **Build with Parameters**
|
||
2. `SERVICE_NAME`, `DEPLOY_TYPE`, `DEPLOY_APPROVED` 설정
|
||
3. **Build**
|
||
|
||
### 7-3. 파라미터 조합 예시
|
||
|
||
| 상황 | SERVICE_NAME | DEPLOY_TYPE | DEPLOY_APPROVED |
|
||
|------|-------------|-------------|-----------------|
|
||
| 장 마감 후 백엔드+DB | `backend` | `after_market` | ✅ |
|
||
| UI 긴급 수정 | `frontend` | `immediate` | ✅ |
|
||
| 컨테이너만 재시작 | `docker-only` | `immediate` | ✅ |
|
||
| 전체 배포 | `all` | `after_market` | ✅ |
|
||
|
||
---
|
||
|
||
## 8. 케이스 E: 서버에서 직접 배포
|
||
|
||
> **용도:** Jenkins/Git 없이 서버 터미널에서 즉시 배포
|
||
|
||
```bash
|
||
ssh aidev@exdev.co.kr
|
||
|
||
# 전체 배포
|
||
/Users/aidev/git/deploy.sh
|
||
|
||
# Git push (서버에서 개발한 경우)
|
||
cd /Users/aidev/dev/goldenChart
|
||
git add . && git commit -m "fix: ..."
|
||
git push exdev main # remote가 서버 bare를 가리킬 때
|
||
```
|
||
|
||
### Docker만 수동 재시작
|
||
|
||
```bash
|
||
cd /Users/aidev/dev/goldenChart
|
||
|
||
docker compose up -d
|
||
docker compose up -d --no-deps backend
|
||
docker compose up -d --no-deps frontend
|
||
docker compose logs -f gc-backend --tail 100
|
||
```
|
||
|
||
### Backend 수동 빌드 (디버깅)
|
||
|
||
```bash
|
||
cd /Users/aidev/dev/goldenChart
|
||
docker compose build backend
|
||
docker compose up -d --no-deps backend
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 케이스 F: 서비스별 선택 배포
|
||
|
||
`SERVICE_NAME` 환경 변수로 `deploy.sh` 실행 범위를 제한합니다.
|
||
|
||
| SERVICE_NAME | 빌드 대상 | 배포 |
|
||
|-------------|----------|------|
|
||
| `frontend` | `frontend` 이미지 | `docker compose up -d --no-deps frontend` |
|
||
| `backend` | `backend` 이미지 | `docker compose up -d --no-deps backend` |
|
||
| `docker-only` | 스킵 | `docker compose up -d` |
|
||
| `all` (기본) | 전체 | `docker compose up -d` |
|
||
|
||
```bash
|
||
SERVICE_NAME=frontend /Users/aidev/git/deploy.sh
|
||
SERVICE_NAME=backend /Users/aidev/git/deploy.sh
|
||
SERVICE_NAME=docker-only /Users/aidev/git/deploy.sh
|
||
```
|
||
|
||
### 변경 파일 → SERVICE_NAME 가이드
|
||
|
||
```
|
||
frontend/src/**, frontend/nginx.conf → frontend
|
||
backend/src/**, backend/pom.xml → backend
|
||
db/migration/V*.sql → backend (Track B 권장)
|
||
docker-compose.yml, .env → all 또는 docker-only
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 배포 후 검증
|
||
|
||
### 10-1. 컨테이너 상태
|
||
|
||
```bash
|
||
cd /Users/aidev/dev/goldenChart
|
||
docker compose ps
|
||
```
|
||
|
||
기대: `gc-mysql`, `gc-backend`, `gc-frontend` 가 **running (healthy)**.
|
||
|
||
### 10-2. Frontend
|
||
|
||
```bash
|
||
curl -s -o /dev/null -w "Frontend HTTP: %{http_code}\n" http://localhost:80/
|
||
# 기대: 200
|
||
```
|
||
|
||
브라우저: http://exdev.co.kr/
|
||
|
||
### 10-3. Backend API
|
||
|
||
```bash
|
||
curl -s -o /dev/null -w "Backend API HTTP: %{http_code}\n" \
|
||
http://localhost:8080/api/indicators/list
|
||
# 기대: 200
|
||
```
|
||
|
||
### 10-4. MySQL
|
||
|
||
```bash
|
||
docker exec gc-mysql mysqladmin ping -h localhost \
|
||
-u stock -p"${MYSQL_PASSWORD:-analyzer}"
|
||
```
|
||
|
||
### 10-5. 배포 로그
|
||
|
||
```bash
|
||
grep -E "배포 완료|배포 실패|ERROR" /Users/aidev/logs/goldenchart/deploy.log | tail -20
|
||
```
|
||
|
||
### 10-6. 로컬에서 원격 확인
|
||
|
||
```bash
|
||
./deploy-home.sh --status
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 일상 운영 명령어
|
||
|
||
```bash
|
||
cd /Users/aidev/dev/goldenChart
|
||
|
||
# 시작 / 중지
|
||
docker compose up -d
|
||
docker compose down
|
||
docker compose restart backend
|
||
|
||
# 로그
|
||
docker compose logs -f --tail 100
|
||
docker logs -f gc-backend --tail 100
|
||
docker logs -f gc-frontend --tail 50
|
||
|
||
# 이미지 정리 (디스크 부족 시)
|
||
docker system prune -f
|
||
```
|
||
|
||
| 하고 싶은 작업 | 명령어 |
|
||
|-------------|--------|
|
||
| 소스 반영 + 자동 배포 | `git push exdev main` |
|
||
| UI만 배포 | `SERVICE_NAME=frontend /Users/aidev/git/deploy.sh` |
|
||
| API만 배포 | `SERVICE_NAME=backend /Users/aidev/git/deploy.sh` |
|
||
| 긴급 전체 동기화 (로컬) | `./deploy-home.sh` |
|
||
| Jenkins 수동 배포 | Build with Parameters |
|
||
| 배포 로그 | `tail -f /Users/aidev/logs/goldenchart/deploy.log` |
|
||
|
||
---
|
||
|
||
## 12. 트러블슈팅
|
||
|
||
### 12-1. git push 후 배포가 안 됨
|
||
|
||
```bash
|
||
# 훅 권한
|
||
ls -la /Users/aidev/git/goldenChart.git/hooks/post-receive
|
||
chmod +x /Users/aidev/git/goldenChart.git/hooks/post-receive
|
||
|
||
# main 브랜치인지
|
||
git branch --show-current
|
||
|
||
# 훅 수동 테스트
|
||
echo "0000000000000000000000000000000000000000 $(git rev-parse HEAD) refs/heads/main" | \
|
||
/Users/aidev/git/goldenChart.git/hooks/post-receive
|
||
```
|
||
|
||
### 12-2. deploy.sh / Jenkins — docker 명령 없음
|
||
|
||
Jenkins **Global PATH** 및 shell 상단:
|
||
|
||
```bash
|
||
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||
which docker && docker compose version
|
||
```
|
||
|
||
### 12-3. 배포 락 파일
|
||
|
||
동시 배포 방지용 (deploy.sh에 구현 시):
|
||
|
||
```bash
|
||
cat /tmp/goldenchart_deploy.lock 2>/dev/null
|
||
# 프로세스 없을 때만
|
||
rm -f /tmp/goldenchart_deploy.lock
|
||
```
|
||
|
||
### 12-4. Backend 기동 실패 (Flyway / MySQL)
|
||
|
||
```bash
|
||
docker logs gc-backend --tail 150
|
||
docker logs gc-mysql --tail 50
|
||
docker compose up -d mysql
|
||
sleep 20
|
||
docker compose up -d --no-deps backend
|
||
```
|
||
|
||
### 12-5. Frontend 502 / API 연결 실패
|
||
|
||
```bash
|
||
docker compose ps
|
||
# backend healthy 후 frontend 재시작
|
||
docker compose up -d --no-deps backend
|
||
docker compose up -d --no-deps frontend
|
||
```
|
||
|
||
### 12-6. deploy-home.sh SSH 실패
|
||
|
||
```bash
|
||
ssh-copy-id -p 22 aidev@exdev.co.kr
|
||
ssh aidev@exdev.co.kr "docker info"
|
||
./deploy-home.sh --forget-password # 캐시된 비밀번호 초기화
|
||
```
|
||
|
||
### 12-7. 서버 .env가 push마다 삭제됨
|
||
|
||
`deploy-home.sh` 는 work-tree를 **rm -rf 후 재압축 해제** 합니다.
|
||
운영 `.env` 는 서버에만 두고, 로컬 `.env` 는 `.gitignore` 처리하세요. Git 기반 `deploy.sh` 는 work-tree를 덮어쓰지 않으므로 `.env` 가 유지됩니다.
|
||
|
||
---
|
||
|
||
## 13. 부록: deploy.sh · post-receive · Jenkinsfile
|
||
|
||
### 13-1. deploy.sh (서버용)
|
||
|
||
서버 `/Users/aidev/git/deploy.sh` 에 저장합니다. 프로젝트에 추가하려면 `scripts/deploy.sh` 로 복사해 Git에 포함할 수 있습니다.
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# GoldenChart — exdev.co.kr 공통 배포 스크립트
|
||
set -euo pipefail
|
||
|
||
GIT_DIR="${GIT_DIR:-/Users/aidev/git/goldenChart.git}"
|
||
WORK_TREE="${WORK_TREE:-/Users/aidev/dev/goldenChart}"
|
||
LOG_FILE="${LOG_FILE:-/Users/aidev/logs/goldenchart/deploy.log}"
|
||
LOCK_FILE="/tmp/goldenchart_deploy.lock"
|
||
SERVICE_NAME="${SERVICE_NAME:-all}"
|
||
|
||
export PATH="/opt/homebrew/bin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
|
||
|
||
mkdir -p "$(dirname "$LOG_FILE")"
|
||
exec >> "$LOG_FILE" 2>&1
|
||
|
||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
||
|
||
if [ -f "$LOCK_FILE" ] && kill -0 "$(cat "$LOCK_FILE")" 2>/dev/null; then
|
||
log "ERROR: 다른 배포 진행 중 (PID $(cat "$LOCK_FILE"))"
|
||
exit 1
|
||
fi
|
||
echo $$ > "$LOCK_FILE"
|
||
trap 'rm -f "$LOCK_FILE"' EXIT
|
||
|
||
log "========== GoldenChart 배포 시작 (SERVICE_NAME=$SERVICE_NAME) =========="
|
||
|
||
# Phase 1: checkout
|
||
if [ -d "$GIT_DIR" ]; then
|
||
log "Phase 1: git checkout -f main → $WORK_TREE"
|
||
git --git-dir="$GIT_DIR" --work-tree="$WORK_TREE" checkout -f main
|
||
else
|
||
log "WARN: GIT_DIR 없음 — work-tree 기존 소스 사용"
|
||
fi
|
||
|
||
cd "$WORK_TREE"
|
||
|
||
# Phase 2–3: build & up
|
||
case "$SERVICE_NAME" in
|
||
frontend)
|
||
log "Phase 2-3: build & up frontend"
|
||
docker compose build frontend
|
||
docker compose up -d --no-deps frontend
|
||
;;
|
||
backend)
|
||
log "Phase 2-3: build & up backend"
|
||
docker compose build backend
|
||
docker compose up -d mysql
|
||
docker compose up -d --no-deps backend
|
||
;;
|
||
docker-only)
|
||
log "Phase 2-3: docker compose up (no build)"
|
||
docker compose up -d
|
||
;;
|
||
all|*)
|
||
log "Phase 2-3: build & up all"
|
||
docker compose build
|
||
docker compose up -d
|
||
;;
|
||
esac
|
||
|
||
# Phase 4: verify
|
||
FRONTEND_PORT="${FRONTEND_PORT:-80}"
|
||
BACKEND_PORT="${BACKEND_PORT:-8080}"
|
||
HTTP_F=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${FRONTEND_PORT}/" || echo "000")
|
||
HTTP_B=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${BACKEND_PORT}/api/indicators/list" || echo "000")
|
||
log "검증: Frontend=$HTTP_F Backend=$HTTP_B"
|
||
docker compose ps
|
||
|
||
if [[ "$HTTP_F" =~ ^2 ]] && [[ "$HTTP_B" =~ ^2 ]]; then
|
||
log "========== 배포 완료 =========="
|
||
else
|
||
log "========== 배포 완료 (일부 헬스체크 비정상 — 로그 확인) =========="
|
||
fi
|
||
```
|
||
|
||
### 13-2. post-receive (요약)
|
||
|
||
[3-4절](#3-4-post-receive-훅-등록-track-a) 과 동일. `main` push 시에만 `deploy.sh` 호출.
|
||
|
||
### 13-3. Jenkinsfile (선택 — Pipeline Job용)
|
||
|
||
Pipeline Job을 쓸 경우 저장소 루트에 `Jenkinsfile` 추가 예시:
|
||
|
||
```groovy
|
||
pipeline {
|
||
agent any
|
||
parameters {
|
||
choice(name: 'SERVICE_NAME', choices: ['all', 'frontend', 'backend', 'docker-only'], description: '배포 대상')
|
||
booleanParam(name: 'DEPLOY_APPROVED', defaultValue: false, description: '배포 승인')
|
||
}
|
||
stages {
|
||
stage('Approve') {
|
||
steps {
|
||
script {
|
||
if (!params.DEPLOY_APPROVED) {
|
||
error('DEPLOY_APPROVED=true 필요')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
stage('Deploy') {
|
||
steps {
|
||
sh '''
|
||
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
||
SERVICE_NAME=${SERVICE_NAME} /Users/aidev/git/deploy.sh
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
post {
|
||
always {
|
||
sh 'tail -30 /Users/aidev/logs/goldenchart/deploy.log || true'
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 13-4. HFT 매뉴얼과의 차이
|
||
|
||
| 항목 | HFT (Mac Studio) | GoldenChart (exdev.co.kr) |
|
||
|------|------------------|---------------------------|
|
||
| 프로젝트명 | goldenAnalysis | **goldenChart** |
|
||
| 배포 스크립트 | `/Volumes/ADATA/git/deploy.sh` | `/Users/aidev/git/deploy.sh` (권장) |
|
||
| work-tree | `/Users/aidev/apps/goldenChart` | `/Users/aidev/dev/goldenChart` |
|
||
| PM2 / AI Agent | 있음 | **없음** (Docker만) |
|
||
| 수동 배포 | — | **`deploy-home.sh`** (tar+SSH) |
|
||
| 헬스체크 | Actuator `/actuator/health` | `/api/indicators/list` |
|
||
|
||
---
|
||
|
||
*GoldenChart · exdev.co.kr · Docker Compose · 구축 가이드 2026-05-23*
|