Soonlife DOCS
배포

배포

GitHub Actions 자동 배포, 비상 수동 배포, 서버 구성 스크립트

두 개의 배포 경로

경로트리거쓰는 때
GitHub Actions (.github/workflows/deploy.yml)main 브랜치 push 또는 수동 실행평소
수동 배포 (scripts/deploy.sh)로컬에서 직접 실행runner나 GitHub이 사용 불가할 때

두 경로 모두 서버 구성 로직은 같은 scripts/server-setup.sh를 공유합니다. 그래서 어느 쪽으로 배포해도 서버 상태가 갈라지지 않습니다.


기본 경로 — GitHub Actions

main 브랜치에 push되면 프로덕션 서버의 self-hosted runner(portal-prod 라벨)가 다음 단계를 실행합니다.

1. Checkout
2. Verify environment      — node/npm/python 확인, 서버의 backend/.env 존재 확인
3. Build frontend          — npm install --legacy-peer-deps && npm run build
4. Sync code               — rsync로 배포 디렉토리 동기화
5. Server setup            — scripts/server-setup.sh
6. Health check            — systemctl is-active + GET /api/chat/health/  (※ 아래 경고 참고)

concurrency: portal-production으로 직렬화되며, 진행 중인 배포를 취소하지 않습니다(cancel-in-progress: false). 취소하면 중간 상태로 남을 수 있기 때문입니다.

rsync에 --delete 계열 옵션을 절대 추가하지 마세요. 제외 대상인 backend/.env와 backend/venv/가 서버에서 지워져 서비스가 통째로 죽습니다.

제외 목록

.git/, .github/, backend/.venv/, backend/venv/, backend/staticfiles/, backend/logs/, backend/db.sqlite3, backend/.env, frontend/, __pycache__/, *.pyc, .DS_Store, scripts/deploy.sh

frontend/는 통째로 제외하고 빌드 산출물(frontend/dist/)만 따로 전송합니다. node_modules와 소스는 서버로 보내지 않습니다.

헬스체크

워크플로는 http://127.0.0.1:8000/api/chat/health/를 3초 간격으로 최대 10회 시도하고, HTTP 200이면 배포 성공으로 봅니다(응답 본문의 ok가 false면 경고만 남김).

현재 이 헬스체크는 통과할 수 없습니다. /api/chat/health/는 챗봇 뷰(views/chat.py)에 있던 엔드포인트라 챗봇 제거(2026-09-28)와 함께 사라졌습니다. 게다가 이 경로를 HTTPS 리다이렉트에서 빼 주던 SECURE_REDIRECT_EXEMPT도 settings.py에서 같이 삭제되어, 프로덕션(DEBUG=False)에서는 127.0.0.1 직접 호출이 301로 돌아옵니다. 결과적으로 10회 재시도 후 API 헬스체크 실패로 배포 잡이 실패합니다.

해결하려면 워크플로(.github/workflows/deploy.yml)의 헬스체크를 남아 있는 공개 엔드포인트(예: /api/auth/turnstile/)로 바꾸고 그 경로를 SECURE_REDIRECT_EXEMPT에 다시 등록하거나, 새 헬스 엔드포인트를 만들어야 합니다. 코드가 반영될 때까지 이 문서의 헬스체크 설명은 워크플로 현재 상태를 그대로 기술한 것입니다.


비상 경로 — scripts/deploy.sh

로컬(mac/Linux)에서 직접 rsync + ssh로 배포합니다.

bash scripts/deploy.sh   # 저장소 어느 위치에서 실행해도 됨
1. SSH 연결 확인
2. 프론트엔드 빌드 (로컬에서)
3. 코드 rsync (워크플로와 동일한 제외 목록)
4. ssh로 scripts/server-setup.sh 실행

scripts/server-setup.sh가 하는 일

서버에서 실행되며, 코드가 이미 배포 디렉토리에 놓여 있다고 전제합니다.

단계내용
1venv 생성(없으면) + pip install -r requirements.txt
2LibreOffice·한글 폰트 설치 (없을 때만)
3manage.py migrate --noinput
4sch-portal.service 작성 후 systemctl restart
5nginx 설정 검사 후 reload

2단계(LibreOffice·한글 폰트 설치)는 챗봇의 첨부파일(hwp/docx → 페이지 PNG) 렌더링용이었습니다. 챗봇이 제거되어 현재 포털은 이 패키지를 쓰지 않지만, 스크립트에는 그대로 남아 있어 soffice가 없는 서버에서는 약 250MB를 내려받습니다. 사용처가 없으므로 스크립트에서 제거해도 됩니다.

gunicorn 설정

gunicorn portal.wsgi:application \
    --bind 0.0.0.0:8000 \
    --workers 3 --worker-class gthread --threads 8 --timeout 600

gthread는 원래 챗봇의 긴 스트리밍 응답이 sync 워커의 하트비트 부재로 --timeout에 강제 종료되던 문제 때문에 도입했습니다. 챗봇은 사라졌지만 동시 처리량이 3(워커) → 24(워커 × 스레드)로 늘어난 이점이 있어 설정은 유지됩니다. --timeout 600은 챗봇 시절의 값이라 줄여도 됩니다.

systemd 유닛의 PATH에는 venv의 bin과 /usr/local/bin이 들어 있는데, 이것도 챗봇 첨부 변환(hwp5odt/hwp5html, soffice)용 설정의 잔재입니다.


backend/.env는 서버 파일이 정본

배포 스크립트도 워크플로도 backend/.env를 만들지도, 덮지도 않습니다. 키를 추가·변경하려면 서버에서 직접 편집하고 서비스를 재시작해야 합니다.

ssh wonchan@<서버>
vi /home/wonchan/SCH-WEB-PORTAL/backend/.env
sudo systemctl restart sch-portal

워크플로는 배포를 시작하기 전에 이 파일의 존재를 확인하고, 없으면 그 자리에서 실패합니다.


로그 확인

# 서비스 상태
systemctl is-active sch-portal
sudo systemctl status sch-portal

# 최근 로그
journalctl -u sch-portal --no-pager -n 50

# gunicorn 액세스·에러 로그
tail -f /home/wonchan/SCH-WEB-PORTAL/backend/logs/access.log
tail -f /home/wonchan/SCH-WEB-PORTAL/backend/logs/error.log

On this page