[클라우드 인프라 구축기] GitHub Actions로 CI/CD 파이프라인 구축하기

이태수·2026년 1월 18일
post-thumbnail

TL;DR

  • GitHub Actions로 Frontend, Backend, AI 세 개의 워크플로우를 분리해서 운영한다.
  • Path 기반 트리거로 변경된 부분만 선택적으로 배포하고, ProxyJump를 활용해
    Private Subnet에 있는 서버도 안전하게 자동 배포한다.

  1. KakaoCloud 2-Tier 아키텍처 설계
    2. GitHub Actions로 CI/CD 구축하기 ⬅️ 현재 글
  2. SSH 브루트포스 5,227건 대응기
  3. Nginx 심화 설정
  4. 서버 자동화 스크립트

1. 들어가며


이전 글에서 KakaoCloud 2-Tier 아키텍처를 설계했다.
이번에는 코드 변경 시 자동으로 배포되는 CI/CD 파이프라인을 구축한 과정을 정리한다.


1.1 배포 환경

서버역할접근 방식
Web ServerNginx, FastAPI, RedisPublic IP (SSH 52222)
AI ServerPostgreSQL, AI ServicePrivate IP (ProxyJump)

1.2 목표

요구사항구현 방식
자동 배포dev, main 브랜치 push 시 트리거
선택적 배포Path 필터로 변경된 부분만 배포
Private 서버 배포ProxyJump로 Bastion 경유

1.3 왜 GitHub Actions를 선택했는가?

CI/CD 도구장점단점선택 이유
GitHub ActionsGitHub 통합, YAML 기반, 무료 티어 충분복잡한 파이프라인엔 한계✅ 선택
Jenkins커스터마이징 무한대, 플러그인 풍부별도 서버 필요, 러닝커브서버 비용 부담
GitLab CIGitLab과 완벽 통합GitHub 사용 시 번거로움저장소가 GitHub
CircleCI빠른 빌드, 병렬 처리 강점무료 크레딧 제한무료 티어 부족

GitHub Actions 선택 근거:

  1. 저장소 통합: 코드와 CI/CD가 같은 곳에 있어서 관리 편함
  2. 무료 크레딧: Public 레포 무제한, Private도 월 2,000분 무료
  3. Secrets 관리: GitHub 자체 Secrets로 민감 정보 관리
  4. Marketplace: 커뮤니티 Actions으로 SSH 배포, SCP 전송 등 쉽게 구현

2. 브랜치 전략


2.1 Git Flow 기반 브랜치 운영

main (Production)
  │
  └─── dev (Development/Staging)
         │
         ├── fe-dev (Frontend 개발)
         ├── be-dev (Backend 개발)
         ├── ai-dev (AI 개발)
         └── db-dev (DB 마이그레이션)
브랜치역할배포 환경병합 조건
main프로덕션 코드운영 서버dev에서 테스트 완료 후
dev통합 테스트운영 서버 (동일)기능 브랜치 PR 승인
fe-devFrontend 개발로컬코드 리뷰 후 dev 병합
be-devBackend 개발로컬코드 리뷰 후 dev 병합
db-devDB 마이그레이션로컬스키마 검증 후 dev 병합

2.2 배포 트리거 브랜치

on:
  push:
    branches: [dev, main]

왜 dev와 main 둘 다 트리거하는가?

브랜치용도배포 빈도
dev개발 중 테스트 배포자주 (하루 여러 번)
main안정 버전 배포가끔 (기능 완성 시)

프로젝트 규모가 작고 별도 스테이징 서버가 없어서
dev, main 모두 같은 운영 서버에 배포
한다.
대규모 프로젝트라면 dev는 스테이징 서버, main은 프로덕션 서버로 분리하는 게 맞다.

2.3 실제 배포 흐름

1. fe-dev에서 Frontend 작업
   │
   ▼ PR 생성 & 코드 리뷰
2. dev 브랜치에 병합
   │
   ▼ push 이벤트 발생
3. deploy-frontend.yml 워크플로우 트리거
   │
   ▼ CI: npm run build (타입 체크)
   ▼ CD: SCP로 서버에 배포
4. 배포 완료, 테스트
   │
   ▼ 이상 없으면
5. main 브랜치에 병합 (운영 반영)

3. 워크플로우 설계


3.1 전체 구조


3.2 왜 워크플로우를 분리했는가?

방식장점단점
단일 워크플로우관리 단순작은 변경에도 전체 배포, 시간 낭비
분리 워크플로우변경 부분만 배포, 빠름파일 3개 관리

분리 워크플로우를 선택한 이유는 명확하다.
Frontend만 수정했는데 AI 서버까지 재배포할 필요가 없다.
path 필터로 어떤 파일이 변경됐는지 감지하면 해당 워크플로우만 실행할 수 있다.


4. Path 기반 트리거


4.1 설정 방법

GitHub Actions에서는 paths 키워드로 특정 경로의 파일이 변경됐을 때만 워크플로우를 실행할 수 있다.

on:
  push:
    branches: [dev, main]
    paths:
      - "backend/**"
      - "docker-compose.yml"

4.2 워크플로우별 트리거 경로

워크플로우트리거 경로실행 조건
deploy-frontend.ymlfrontend/**, frontend-console/**React 코드 변경
deploy-backend.ymlbackend/**, docker-compose.ymlFastAPI 코드 변경
deploy-gpu.ymlai/inference/**, ai/api.pyAI 모델/API 변경

효과:

변경 사항실행되는 워크플로우
README.md만 수정없음
frontend/만 수정deploy-frontend.yml
backend/ + ai/ 수정deploy-backend.yml + deploy-gpu.yml

불필요한 배포를 방지하고 CI/CD 파이프라인 실행 시간을 최소화할 수 있다.


4.3 동시 배포 방지 (Concurrency)

같은 브랜치에 빠르게 여러 번 push하면 배포가 겹칠 수 있다.
concurrency 설정으로 이전 배포를 취소하고 최신 배포만 실행한다.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
옵션설명
group워크플로우 이름 + 브랜치 조합으로 그룹 생성
cancel-in-progress같은 그룹의 이전 실행 취소

동작 예시:

시간이벤트결과
10:00dev 브랜치 push → 배포 #1 시작실행 중
10:01dev 브랜치 push → 배포 #2 시작배포 #1 취소, #2 실행
10:05배포 #2 완료최신 코드만 배포됨

5. CI 단계: 배포 전 검증


5.1 Backend (Python)

ci:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - uses: actions/setup-python@v5
      with:
        python-version: "3.11"
        cache: "pip"

    - name: Install Dependencies
      run: pip install -r backend/requirements.txt

    - name: Syntax Check
      run: python -m py_compile backend/main.py

Python 3.11 환경에서 의존성을 설치한 후, py_compile 모듈로 문법 오류를 검사한다.


5.2 Frontend (TypeScript)

ci:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4

    - uses: actions/setup-node@v4
      with:
        node-version: "20"

    - name: Build
      run: |
        cd frontend
        npm ci
        npm run build

Node.js 20 환경에서 의존성을 설치하고 TypeScript 빌드를 실행한다.
타입 오류가 있으면 빌드가 실패한다.


5.3 CI 통과 조건

대상검증 방식실패 시
Pythonpy_compile 문법 체크CD 단계 진입 차단
TypeScriptnpm run buildCD 단계 진입 차단

실제 사례: 미사용 import로 인한 빌드 실패

error TS6133: 'X' is declared but its value is never read.

CI가 없었다면 이런 코드가 프로덕션에 배포되어 서비스 장애로 이어질 수 있었다.


6. CD 단계: 서버 배포


6.1 Frontend 배포 (정적 파일)

배포 흐름:

GitHub Actions Runner
    │
    ├─ npm run build (Demo App)
    ├─ npm run build (Console App)
    │
    ▼ SCP 전송
Web Server (/tmp/frontend-build)
    │
    ▼ SSH로 파일 이동
/var/www/demo/     ← Demo App
/var/www/console/  ← B2B Console
/var/www/landing/  ← Landing Page

Step 1: SCP로 빌드 결과물 전송

- name: Deploy Demo App via SCP
  uses: appleboy/scp-action@v0.1.7
  with:
    host: ${{ secrets.APP_HOST }}
    username: ${{ secrets.APP_USER }}
    key: ${{ secrets.SSH_KEY }}
    port: 52222
    source: "frontend/dist/*"
    target: "/tmp/frontend-build"
    strip_components: 2

Step 2: SSH로 Nginx 디렉토리에 복사

- name: Move to Nginx Directory
  uses: appleboy/ssh-action@v1.0.3
  with:
    host: ${{ secrets.APP_HOST }}
    username: ${{ secrets.APP_USER }}
    key: ${{ secrets.SSH_KEY }}
    port: 52222
    script: |
      sudo rm -rf /var/www/demo/*
      sudo cp -r /tmp/frontend-build/* /var/www/demo/
      sudo chown -R www-data:www-data /var/www/demo/

왜 2단계로 나눴는가?

단계이유
SCP → /tmp/SCP는 sudo 권한이 없어 /var/www/ 직접 쓰기 불가
SSH → /var/www/sudo 권한으로 파일 이동 및 소유권 변경

6.2 Backend 배포 (Docker)

배포 흐름:

GitHub Actions Runner
    │
    ▼ SSH 접속
Web Server
    │
    ├─ git pull (최신 코드)
    ├─ 환경변수 파일 생성
    ├─ docker compose down
    ├─ docker compose up --build
    │
    ▼ 헬스체크
http://localhost:8000/

핵심 스크립트:

- name: Deploy Backend via SSH
  uses: appleboy/ssh-action@v1.0.3
  with:
    host: ${{ secrets.APP_HOST }}
    username: ${{ secrets.APP_USER }}
    key: ${{ secrets.SSH_KEY }}
    port: 52222
    script: |
      cd ~/MovieSir
      git fetch --all
      git reset --hard origin/${{ github.ref_name }}

      # 환경변수 주입
      cat > .env.production << 'ENVEOF'
      ${{ secrets.ENV_PRODUCTION_APP }}
      ENVEOF

      # 컨테이너 재시작
      docker compose --env-file .env.production down || true
      docker compose --env-file .env.production up -d --build backend redis

      # 헬스체크
      sleep 3
      curl -s http://localhost:8000/

git fetch --allgit reset --hard로 최신 코드를 강제 동기화한다.
환경변수는 GitHub Secrets에서 Here Document로 주입하고,
컨테이너를 재시작한 후 헬스체크를 수행한다.


6.3 GPU 서버 배포 (ProxyJump)

문제: AI Server는 Private Subnet에 있어서 인터넷에서 직접 SSH 접속이 불가능하다.

해결: Web Server를 Bastion Host로 사용하는 ProxyJump 방식 적용

GitHub Actions Runner
    │
    ▼ SSH (52222)
Web Server (Bastion)
    │
    ▼ SSH (22) - Private Network
AI Server

핵심 스크립트:

- name: Deploy to GPU Server via SSH
  uses: appleboy/ssh-action@v1.0.3
  with:
    host: ${{ secrets.GPU_PRIVATE_IP }}      # 10.0.35.62
    username: ${{ secrets.GPU_USER }}
    key: ${{ secrets.SSH_KEY }}
    proxy_host: ${{ secrets.APP_HOST }}      # Web Server 경유
    proxy_username: ${{ secrets.APP_USER }}
    proxy_key: ${{ secrets.SSH_KEY }}
    proxy_port: 52222
    script: |
      cd ~/MovieSir
      git fetch --all
      git reset --hard origin/${{ github.ref_name }}

      docker compose -f docker-compose.gpu.yml down || true
      docker compose -f docker-compose.gpu.yml up -d --build

proxy_* 옵션 설명:

옵션역할
proxy_hostWeb Server Public IPBastion Host 주소
proxy_port52222Bastion Host SSH 포트
hostAI Server Private IP최종 접속 대상

7. 환경변수 관리


7.1 GitHub Secrets 구조

Secret 이름내용용도
APP_HOSTWeb Server Public IPSSH 접속
APP_USERubuntuSSH 사용자
GPU_PRIVATE_IPAI Server Private IPProxyJump 대상
SSH_KEYPEM 파일 내용SSH 인증
ENV_PRODUCTION_APP전체 .env 내용Web Server 환경변수
ENV_PRODUCTION_GPUGPU용 .env 내용AI Server 환경변수

7.2 GitHub Secrets 설정 방법

1. GitHub Repository → Settings → Secrets and variables → Actions

2. New repository secret 클릭

3. 필요한 Secrets 등록

등록할 Secret값 예시
APP_HOST61.109.xxx.xxx
APP_USERubuntu
SSH_KEYPEM 파일 내용 전체 (-----BEGIN 부터 END----- 까지)
ENV_PRODUCTION_APP.env 파일 내용 전체

SSH_KEY 등록 시 주의:

# 로컬에서 PEM 파일 내용 복사
cat ~/.ssh/your-key.pem

출력된 전체 내용을 그대로 복사해서 Secret 값으로 붙여넣는다.


7.3 환경변수 주입 방식

cat > .env.production << 'ENVEOF'
${{ secrets.ENV_PRODUCTION_APP }}
ENVEOF
구성 요소설명
ENVEOFHere Document 구분자
'ENVEOF' (따옴표)내부 변수 확장 방지 ($, ` 등 특수문자 그대로 전달)
${{ secrets.* }}GitHub Secrets에서 값 주입

왜 Here Document를 사용하는가?

방식문제점
echo여러 줄 환경변수 처리 어려움
파일 SCP 전송별도 단계 필요, 파일 관리 복잡
Here Document✅ 여러 줄 그대로 전달, 한 번에 처리

배포할 때마다 GitHub Secrets에서 최신 환경변수를 가져와 적용한다.
코드 저장소에 민감한 정보가 노출되지 않는다.


8. Docker 설정


8.1 Backend Dockerfile

FROM python:3.11-slim

WORKDIR /app

# 의존성 설치 (캐시 활용)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 소스 코드 복사
COPY . .

# 포트 노출
EXPOSE 8000

# 실행
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

8.2 docker-compose.yml (Web Server)

version: "3.8"

services:
  backend:
    build: ./backend
    container_name: moviesir-backend
    restart: always
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET_KEY=${JWT_SECRET_KEY}
      - AI_SERVICE_URL=${AI_SERVICE_URL}
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    container_name: moviesir-redis
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

volumes:
  redis_data:

8.3 docker-compose.gpu.yml (AI Server)

version: "3.8"

services:
  ai:
    build: ./ai
    container_name: moviesir-ai
    restart: always
    ports:
      - "8001:8001"
    environment:
      - DATABASE_URL=${DATABASE_URL}
    volumes:
      - ./ai/training:/app/training
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

GPU 사용 설정:

항목설명
deploy.resources.reservations.devicesDocker에서 GPU 사용 선언
driver: nvidiaNVIDIA GPU 드라이버 사용
count: 1GPU 1개 할당
capabilities: [gpu]GPU 기능 활성화

서버에 nvidia-container-toolkit이 설치되어 있어야 한다.


9. 배포 흐름 정리

9.1 Frontend 변경 시

단계동작
1frontend/ 코드 push
2deploy-frontend.yml 트리거
3CI: npm run build (타입 체크 + 빌드)
4CD: SCP로 빌드 결과물 전송
5CD: SSH로 /var/www/에 배포

9.2 Backend 변경 시

단계동작
1backend/ 코드 push
2deploy-backend.yml 트리거
3CI: Python 문법 체크
4CD: SSH 접속 → git pull
5CD: docker compose up --build
6헬스체크

9.3 AI 서비스 변경 시

단계동작
1ai/ 코드 push
2deploy-gpu.yml 트리거
3CI: Python 문법 체크
4CD: ProxyJump로 AI Server 접속
5CD: docker compose up --build

10. 트러블슈팅

10.1 Frontend CI 빌드 실패 (TypeScript 타입 에러)

문제: Frontend CI 단계에서 npm run build 실패

> movisir@0.0.0 build
> tsc -b && vite build

src/components/layout/SideRecommendationPopup/SideRecommendationPopup.tsx(2,10):
error TS6133: 'X' is declared but its value is never read.

Error: Process completed with exit code 2.

원인: import 문에서 선언한 변수를 실제로 사용하지 않음

해결: 미사용 import 삭제

// Before
import { X, ChevronRight } from 'lucide-react';  // X 미사용

// After
import { ChevronRight } from 'lucide-react';

교훈: CI가 없었다면 이런 코드가 프로덕션에 배포될 수 있었다.
TypeScript의 엄격한 타입 체크가 빌드 단계에서 오류를 잡아준다.


10.2 Docker 빌드 캐시 오류

문제: Docker 이미지 빌드 중 캐시 관련 에러 발생

err: failed to solve: failed to prepare extraction snapshot
"extract-995087796-Mtuk sha256:0228f645b7ab...":
parent snapshot sha256:689e2f68e3bf... does not exist: not found

원인: Docker BuildKit 캐시 손상 또는 이전 빌드 레이어 삭제

해결:

echo "📌 빌드 캐시 정리"
docker builder prune -f || true

echo "📌 AI 이미지 빌드"
cd ~/MovieSir/ai && docker build -t moviesir-ai:latest .

교훈: Docker 빌드 실패 시 코드보다 캐시부터 의심하기.
docker builder prune -f로 캐시를 정리하면 대부분 해결된다.


10.3 Docker 컨테이너 이름 충돌

문제: docker compose up 실행 시 컨테이너 이름 충돌 에러

err: Container dozzle Error response from daemon: Conflict.
The container name "/dozzle" is already in use by container "4f3f88ab4cdbf49ef6a472...".
You have to remove (or rename) that container to be able to reuse that name.
err: Error response from daemon: Conflict.

원인: 동일한 이름의 컨테이너가 이미 존재하는 상태에서 새 컨테이너 생성 시도

해결: 기존 컨테이너 먼저 정리

# 배포 스크립트에서 down 먼저 실행
docker compose down || true
docker compose up -d --build

교훈: 배포 스크립트에서 docker compose down을 먼저 실행해서 기존 컨테이너를 정리한 후 새로 생성해야 한다.


10.4 SSH TCP 연결 타임아웃

문제: 배포 스크립트는 완료됐는데 워크플로우가 실패로 표시됨

2026/01/14 01:29:22 dial tcp ***:22: i/o timeout

분석:

항목상태
배포 스크립트정상 완료 ("백엔드 배포 완료" 메시지 출력)
SSH 세션종료 시점에 연결 끊김
실제 배포성공
GitHub Actions실패로 표시

원인: 서버 부하(Docker 빌드)로 인한 SSH 응답 지연

대응: 워크플로우 실패 시 서버에서 직접 배포 상태 확인 필요

교훈: GitHub Actions 실패가 곧 배포 실패를 의미하지 않는다.
워크플로우 실패 시 서버에서 docker ps로 실제 상태를 확인해야 한다.


10.5 환경변수 누락

문제: 컨테이너에서 환경변수를 읽지 못함

원인: docker-compose.yml에 환경변수 매핑 누락

# docker-compose.yml
environment:
  - NEW_VAR=${NEW_VAR}  # 이 줄 없으면 컨테이너에 전달 안 됨

교훈: 환경변수 추가 시 체크리스트

위치확인 사항
GitHub Secrets값 등록
.env 파일키=값 추가
docker-compose.ymlenvironment 매핑

10.6 Permission Denied

문제: SCP로 /var/www/ 직접 쓰기 시 권한 오류

scp: /var/www/demo/index.html: Permission denied

원인: GitHub Actions Runner의 SSH 사용자가 /var/www/ 쓰기 권한 없음

해결: 2단계 배포 방식

# Step 1: /tmp/에 먼저 전송 (권한 문제 없음)
- name: SCP to temp
  uses: appleboy/scp-action@v0.1.7
  with:
    target: "/tmp/frontend-build"

# Step 2: sudo로 이동 (SSH에서는 sudo 사용 가능)
- name: Move with sudo
  uses: appleboy/ssh-action@v1.0.3
  with:
    script: |
      sudo cp -r /tmp/frontend-build/* /var/www/demo/

교훈:

원칙설명
시스템 디렉토리 직접 접근 금지/var/www/ 등은 sudo 필요
2단계 배포/tmp/ 경유 → sudo 이동
권한 분리보안상 올바른 구조, 우회하지 말 것

10.7 Git Reset 충돌

문제: 서버에서 직접 파일 수정 후 git reset --hard 실행 시 변경 사항 유실

원인: 서버에서 임시로 수정한 코드가 있었음

교훈:

원칙설명
서버 직접 수정 금지모든 변경은 Git을 통해서만
로컬 → GitHub → 서버단방향 배포 흐름 유지
긴급 수정 시서버에서 수정 후 바로 Git에 반영

11. 결과


11.1 배포 시간

대상평균 소요 시간
Frontend~2분
Backend~3분
AI Server~5분

11.2 개선 효과

항목BeforeAfter
배포 방식SSH 접속 → git pull → 수동 빌드push만 하면 자동 배포
배포 범위전체 서비스 재시작변경된 부분만 배포
일관성배포 중 실수 가능스크립트로 일관성 보장

12. 마무리


12.1 파이프라인 장점

항목설명
자동화push만 하면 배포 완료
선택적 배포Path 필터로 불필요한 배포 방지
Private 서버 지원ProxyJump로 안전하게 배포
환경변수 보안GitHub Secrets로 관리

12.2 개선 가능한 점

영역현재개선안
테스트문법 체크만유닛 테스트, E2E 테스트 추가
롤백수동배포 실패 시 자동 롤백
알림없음Slack 배포 성공/실패 알림
배포 방식다운타임 존재Blue-Green 무중단 배포

부록: 전체 워크플로우 파일


A. deploy-frontend.yml

name: Deploy Frontend

on:
  push:
    branches: [dev, main]
    paths:
      - "frontend/**"
      - "frontend-console/**"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Build Demo App
        run: |
          cd frontend
          npm ci
          npm run build

      - name: Build Console App
        run: |
          cd frontend-console
          npm ci
          npm run build

      - name: Upload Demo Artifact
        uses: actions/upload-artifact@v4
        with:
          name: demo-dist
          path: frontend/dist

      - name: Upload Console Artifact
        uses: actions/upload-artifact@v4
        with:
          name: console-dist
          path: frontend-console/dist

  cd:
    needs: ci
    runs-on: ubuntu-latest
    steps:
      - name: Download Demo Artifact
        uses: actions/download-artifact@v4
        with:
          name: demo-dist
          path: demo-dist

      - name: Download Console Artifact
        uses: actions/download-artifact@v4
        with:
          name: console-dist
          path: console-dist

      - name: Deploy Demo via SCP
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.APP_HOST }}
          username: ${{ secrets.APP_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: 52222
          source: "demo-dist/*"
          target: "/tmp/frontend-build"
          strip_components: 1

      - name: Deploy Console via SCP
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.APP_HOST }}
          username: ${{ secrets.APP_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: 52222
          source: "console-dist/*"
          target: "/tmp/console-build"
          strip_components: 1

      - name: Move to Nginx Directory
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.APP_HOST }}
          username: ${{ secrets.APP_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: 52222
          script: |
            sudo rm -rf /var/www/demo/*
            sudo rm -rf /var/www/console/*
            sudo cp -r /tmp/frontend-build/* /var/www/demo/
            sudo cp -r /tmp/console-build/* /var/www/console/
            sudo chown -R www-data:www-data /var/www/
            rm -rf /tmp/frontend-build /tmp/console-build

B. deploy-backend.yml

name: Deploy Backend

on:
  push:
    branches: [dev, main]
    paths:
      - "backend/**"
      - "docker-compose.yml"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"

      - name: Install Dependencies
        run: pip install -r backend/requirements.txt

      - name: Syntax Check
        run: python -m py_compile backend/main.py

  cd:
    needs: ci
    runs-on: ubuntu-latest
    steps:
      - name: Deploy Backend
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.APP_HOST }}
          username: ${{ secrets.APP_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: 52222
          command_timeout: 30m
          script: |
            cd ~/MovieSir
            git fetch --all
            git reset --hard origin/${{ github.ref_name }}

            cat > .env.production << 'ENVEOF'
            ${{ secrets.ENV_PRODUCTION_APP }}
            ENVEOF

            docker compose --env-file .env.production down || true
            docker compose --env-file .env.production up -d --build backend redis

            sleep 3
            curl -s http://localhost:8000/ || echo "Health check warning"
            echo "Backend deploy completed"

C. deploy-gpu.yml

name: Deploy AI Service

on:
  push:
    branches: [dev, main]
    paths:
      - "ai/inference/**"
      - "ai/api.py"
      - "ai/Dockerfile"
      - "ai/requirements.txt"
      - "docker-compose.gpu.yml"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Syntax Check
        run: python -m py_compile ai/api.py

  cd:
    needs: ci
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to GPU Server via ProxyJump
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.GPU_PRIVATE_IP }}
          username: ${{ secrets.GPU_USER }}
          key: ${{ secrets.SSH_KEY }}
          proxy_host: ${{ secrets.APP_HOST }}
          proxy_username: ${{ secrets.APP_USER }}
          proxy_key: ${{ secrets.SSH_KEY }}
          proxy_port: 52222
          command_timeout: 30m
          script: |
            cd ~/MovieSir
            git fetch --all
            git reset --hard origin/${{ github.ref_name }}

            cat > .env.production << 'ENVEOF'
            ${{ secrets.ENV_PRODUCTION_GPU }}
            ENVEOF

            echo "Cleaning build cache..."
            docker builder prune -f || true

            docker compose -f docker-compose.gpu.yml --env-file .env.production down || true
            docker compose -f docker-compose.gpu.yml --env-file .env.production up -d --build

            sleep 5
            curl -s http://localhost:8001/health || echo "Health check warning"
            echo "AI Service deploy completed"

GitHub


이 글은 스나이퍼팩토리 카카오클라우드 AIaaS 마스터 클래스 2기 프로젝트 경험을 바탕으로 작성되었습니다.

0개의 댓글