GitHub Actions로 VPS에 자동 배포(CI/CD) 구축하기
코드를 push하면 VPS에 자동으로 배포되는 환경. 한 번 맛보면 다시는 수동 FTP나 SSH 접속으로 파일을 올리지 못하게 됩니다. 2026년에는 GitHub Actions를 사용하면 별도의 CI/CD 서버 없이도 push 한 번에 VPS로 자동 배포하는 파이프라인을 무료로 만들 수 있습니다.
이 튜토리얼에서는 Node.js, Python, 정적 사이트 등 어떤 앱이든 적용 가능한 GitHub Actions → VPS 자동 배포 구성을 단계별로 설명합니다. Vultr, DigitalOcean, Hetzner VPS에 모두 동일하게 적용됩니다.
사전 준비: GitHub 저장소, 배포 대상 VPS 1대. 아직 VPS가 없다면 Vultr(시간 단위 과금)로 시작하는 것이 좋습니다.
CI/CD가 필요한 이유
수동 배포의 문제는 명확합니다: 실수하기 쉽고, 반복적이며, 팀이 커지면 엉킵니다. 자동 배포의 장점은:
- 일관성: 매번 동일한 절차로 배포 (환경 차이로 인한 버그 제로)
- 속도: push 후 1~2분 내 서비스 반영
- 롤백: 문제 발생 시 이전 커밋으로 즉시 복구
- 가시성: GitHub Actions 로그로 배포 이력 추적
- 무료: 퍼블릭 저장소 무제한, 프라이빗도 월 2,000분 무료
1단계: 배포 전략 선택하기
VPS 배포에는 주로 두 가지 방식을 씁니다. 상황에 맞게 선택하세요.
| 방식 | 동작 | 장점 | 단점 |
|---|---|---|---|
| rsync 배포 | GitHub Actions에서 SSH로 파일 전송 후 빌드 | 단순, 빠름, 정적/소규모 적합 | 서비스 중단 발생 가능 |
| Docker 배포 | 이미지 빌드 → 레지스트리 푸시 → VPS에서 pull | 무중단, 롤백 쉬움, 확장성 | 초기 설정 복잡 |
| Git pull 배포 | VPS에서 webhook 수신 후 git pull | 가장 단순 | 의존성 설치 누락 위험 |
이 튜토리얼에서는 가장 널리 쓰이는 rsync + SSH 배포를 기본으로 다루고, 끝에 Docker 방식을 간략히 소개합니다.
2단계: VPS에 배포 전용 SSH 키 만들기
GitHub Actions가 VPS에 접속하려면 전용 키가 필요합니다. 절대 개인 키를 쓰지 마세요.
로컬에서 키 생성
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/deploy_key -N ""
공개 키를 VPS에 등록
cat ~/.ssh/deploy_key.pub | ssh user@your-vps-ip "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
VPS에서 보안 강화 (선택)
배포 전용 계정을 만들어 권한을 최소화하는 것이 안전합니다. 보안 설정은 VPS 보안 완벽 가이드를 참고하세요.
3단계: GitHub Secrets 등록하기
비밀번호와 키는 절대 저장소에 커밋하면 안 됩니다. GitHub Secrets를 사용합니다.
- GitHub 저장소 → Settings → Secrets and variables → Actions
- New repository secret 클릭
등록할 Secrets:
| 이름 | 값 |
|---|---|
VPS_HOST |
VPS 공인 IP (예: 203.0.113.10) |
VPS_USER |
SSH 사용자 (예: deploy) |
VPS_SSH_KEY |
deploy_key 개인키 전체 내용 |
VPS_PORT |
SSH 포트 (기본 22) |
DEPLOY_PATH |
배포 경로 (예: /var/www/app) |
개인키 등록 시 -----BEGIN OPENSSH PRIVATE KEY-----부터 -----END...까지 전체를 복사해 넣습니다.
4단계: GitHub Actions 워크플로우 작성
저장소 루트에 .github/workflows/deploy.yml을 만듭니다.
name: Deploy to VPS
on:
push:
branches: [ main ]
workflow_dispatch: # 수동 실행 옵션
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: 코드 체크아웃
uses: actions/checkout@v4
- name: SSH 키 준비
run: |
mkdir -p ~/.ssh
echo "${{ secrets.VPS_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -p ${{ secrets.VPS_PORT }} -H ${{ secrets.VPS_HOST }} >> ~/.ssh/known_hosts
- name: VPS로 파일 동기화 (rsync)
run: |
rsync -avz --delete \
-e "ssh -p ${{ secrets.VPS_PORT }}" \
./ ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }}:${{ secrets.DEPLOY_PATH }}/
- name: VPS에서 배포 후 스크립트 실행
run: |
ssh -p ${{ secrets.VPS_PORT }} ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} \
"cd ${{ secrets.DEPLOY_PATH }} && npm ci --production && pm2 restart app"
이 워크플로우는 main 브랜치에 push되면 자동으로 실행됩니다. 마지막 줄의 명령어는 언어/프레임워크에 맞게 수정하세요 (예: Python은 pip install -r requirements.txt).
권한 설정 팁
rsync의 --delete 옵션은 VPS에서 저장소에 없는 파일을 삭제합니다. 로그/업로드 파일이 날아가는 것을 막으려면 --exclude를 추가하세요.
rsync -avz --delete \
--exclude '.env' \
--exclude 'logs/' \
--exclude 'uploads/' \
-e "ssh -p ${{ secrets.VPS_PORT }}" \
./ user@host:/path/
5단계: 무중단 배포로 업그레이드 (PM2)
Node.js 앱이라면 PM2를 사용하면 재시작 시 중단을 0초로 만들 수 있습니다. VPS에 설치:
sudo npm install -g pm2
pm2 start app.js --name app
pm2 startup
pm2 save
GitHub Actions의 배포 단계에서 pm2 reload app을 쓰면 클러스터 모드에서 순차 재시작으로 무중단이 됩니다(pm2 restart 대신).
6단계: Docker 기반 배포 (심화)
더 견고한 파이프라인을 원하면 Docker를 씁니다. 흐름은: GitHub Actions에서 이미지 빌드 → GHCR(GitHub Container Registry) 푸시 → VPS에서 pull & 재시작.
name: Docker Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: GHCR 로그인
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 이미지 빌드 & 푸시
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
- name: VPS에서 새 이미지 배포
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
docker pull ghcr.io/${{ github.repository }}:latest
docker compose up -d --remove-orphans
docker image prune -f
Docker Compose로 멀티 컨테이너를 관리하는 방법은 VPS Docker 셀프호스팅 가이드를 참고하세요.
GitHub Actions vs 다른 CI/CD 도구 비교
| 도구 | 비용 | VPS 배포 적합도 | 특징 |
|---|---|---|---|
| GitHub Actions | 무료(퍼블릭)/2천분(프라이빗) | ⭐⭐⭐⭐⭐ | 저장소 통합, 설정 최소 |
| GitLab CI | 무료(퍼블릭)/월 400분 | ⭐⭐⭐⭐ | 자체 Runner 옵션, 사설 저장소 강점 |
| Drone CI | 오픈소스 자체호스팅 | ⭐⭐⭐ | 가벼움, VPS 직접 설치 |
| Jenkins | 무료(자체호스팅) | ⭐⭐⭐ | 강력하지만 무거움, 대규모용 |
GitHub 저장소를 이미 쓰고 있다면 GitHub Actions가 당연한 선택입니다. 추가 비용·인프라 없이 바로 시작할 수 있습니다.
배포 실패 시 디버깅 체크리스트
워크플로우가 실패하면 다음을 순서대로 확인하세요:
- SSH 키 형식: 줄바꿈이 깨지지 않았는지 (Secret에 LF 보존)
- known_hosts:
ssh-keyscan단계가 정상 실행됐는지 - 포트: 비표준 SSH 포트면
-p옵션 누락 확인 - 권한: 배포 경로 쓰기 권한 (
chown으로 deploy 계정 소유로) - rsync 설치: VPS와 러너 모두에 rsync 필요 (기본 설치됨)
- 방화벽: UFW가 GitHub Actions IP를 차단하지 않는지 (SSH 포트는 퍼블릭)
Actions 로그의 빨간 단계를 펼치면 상세 에러가 나옵니다.
자주 묻는 질문 (FAQ)
Q. GitHub Actions 비용이 드나요?
퍼블릭 저장소는 무제한 무료입니다. 프라이빗 저장소는 개인 계정 월 2,000분, 조직은 월 3,000분까지 무료이고, 초과 시 분당 $0.008입니다. 소규모 프로젝트 배포는 사실상 무료입니다.
Q. 배포 중에 서비스가 잠깐 멈춰도 되나요?
개인 프로젝트나 트래픽이 적은 시간대라면 rsync + 재시작으로 충분합니다. 서비스 중단이 허용되지 않는다면 PM2 reload(무중단) 또는 Docker 기반 블루그린 배포를 사용하세요.
Q. 여러 서버에 동시 배포할 수 있나요?
네. matrix 전략을 쓰거나 여러 VPS_HOST를 Secret에 넣고 루프로 배포할 수 있습니다. 로드밸런서 뒤에 여러 VPS를 두는 구성에 적합합니다.
Q. SSH 키가 유출되면 어떻게 하나요?
즉시 GitHub Secrets에서 키를 삭제하고, VPS의 authorized_keys에서 해당 공개 키를 제거한 뒤 새 키를 발급하세요. 키는 저장소별·배포 전용으로만 쓰는 것이 중요합니다.
Q. .env 파일은 어떻게 배포하나요?
절대 저장소에 커밋하지 마세요. VPS에 수동으로 한 번만 배치하거나, Actions Secret에서 echo "${{ secrets.ENV_FILE }}" > .env로 주입하세요.
마무리: 자동화된 배포로 개발 속도 2배
수동 배포를 자동화하면 단순히 시간만 절약되는 게 아니라, 개발-배포-검증 사이클 전체가 빨라집니다. 코드를 더 자주, 더 안전하게 출시할 수 있게 됩니다. GitHub Actions는 이미 GitHub을 쓰고 있다면 진입 장벽이 거의 없는 최적의 선택입니다.
아직 배포 대상 VPS가 없다면 시간 단위 과금으로 부담 없이 시작할 수 있습니다.
관련 가이드: VPS Docker 셀프호스팅 가이드 | VPS 보안 완벽 가이드
관련 글
- VPS WireGuard VPN 구축 튜토리얼 2026: 15분 만에 자체 VPN
- 2026 VPS·클라우드 호스팅 가격 비교표 — 한눈에 보기
- k3s 경량 Kubernetes로 VPS 미니 클러스터 구축: 단일 노드 시작 완벽 가이드 (2026)



















