git push 한 번에 일본 서버까지 자동 배포되는 파이프라인을 만들면 휴먼 에러가 줄고, 새벽 배포 부담도 사라집니다. 이 글에서는 GitHub Actions + SSH 키 조합으로 가장 단순하고 안정적인 자동 배포 파이프라인을 구성합니다.

GitHub Actions → 일본 서버 자동 배포 파이프라인


전체 그림

배포 단계는 4가지입니다.

  1. 개발자가 main 브랜치에 push
  2. GitHub Actions가 빌드 및 테스트
  3. SSH로 일본 서버에 접속해 새 코드 동기화
  4. 서비스 재시작 (systemctl 또는 docker)

각 단계를 하나씩 살펴보겠습니다.


1단계 — 서버에 배포 전용 사용자 만들기

root로 직접 배포하면 사고 위험이 큽니다. 권한이 제한된 deploy 사용자를 따로 만듭니다.

# 일본 서버에서 실행
sudo adduser --disabled-password --gecos "" deploy
sudo mkdir -p /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh
sudo chown -R deploy:deploy /home/deploy/.ssh

# 애플리케이션 디렉토리 권한 부여
sudo mkdir -p /var/www/myapp
sudo chown -R deploy:deploy /var/www/myapp

# systemctl restart 권한만 sudo 없이 허용
echo 'deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp' | sudo tee /etc/sudoers.d/deploy

NOPASSWD로 허용하는 명령은 딱 필요한 것만 명시해야 합니다. ALL을 주면 사실상 root입니다.


2단계 — SSH 키 페어 생성

로컬 PC에서 배포 전용 키를 만듭니다. 평소 개인 키와 분리하는 것이 좋습니다.

ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -N "" -C "github-actions-deploy"

생성된 공개키를 일본 서버의 deploy 사용자에게 등록합니다.

# 로컬에서 일본 서버로 전송
ssh-copy-id -i ~/.ssh/deploy_key.pub deploy@your-server-ip

# 또는 수동으로
cat ~/.ssh/deploy_key.pub | ssh root@your-server-ip "cat >> /home/deploy/.ssh/authorized_keys && chown deploy:deploy /home/deploy/.ssh/authorized_keys && chmod 600 /home/deploy/.ssh/authorized_keys"

연결 테스트:

ssh -i ~/.ssh/deploy_key deploy@your-server-ip "whoami && pwd"
# → deploy
# → /home/deploy

3단계 — GitHub Repository Secrets 등록

GitHub 저장소 → Settings → Secrets and variables → Actions에서 다음 시크릿을 등록합니다.

이름
SERVER_HOST 일본 서버 IP 또는 도메인
SERVER_USER deploy
SSH_PRIVATE_KEY ~/.ssh/deploy_key 파일 내용 전체
SSH_PORT 22 (또는 변경한 포트)

SSH_PRIVATE_KEYcat ~/.ssh/deploy_key 결과를 그대로 복사해서 붙여넣습니다. -----BEGIN OPENSSH PRIVATE KEY----- 부터 -----END OPENSSH PRIVATE KEY----- 까지 모두 포함해야 합니다.


4단계 — GitHub Actions 워크플로

.github/workflows/deploy.yml 파일을 생성합니다. 아래는 Node.js 앱 기준 예시입니다.

name: Deploy to Japan Server

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install & build
        run: |
          npm ci
          npm run build
          npm test

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: $
          username: $
          key: $
          port: $
          script: |
            cd /var/www/myapp
            git fetch origin main
            git reset --hard origin/main
            npm ci --production
            sudo systemctl restart myapp

PHP/Python/Go도 빌드 단계만 바꾸면 동일한 구조로 적용 가능합니다.


5단계 — 정적 사이트(Jekyll, Hugo, Next 빌드 결과)인 경우

빌드 결과물을 서버로 전송하는 형태가 더 깔끔합니다. rsync를 사용합니다.

      - name: Build site
        run: |
          bundle install
          bundle exec jekyll build

      - name: Setup SSH key
        run: |
          mkdir -p ~/.ssh
          echo "$" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          ssh-keyscan -p $ $ >> ~/.ssh/known_hosts

      - name: Rsync to server
        run: |
          rsync -avz --delete -e "ssh -i ~/.ssh/deploy_key -p $" \
            _site/ $@$:/var/www/myapp/

--delete 옵션은 서버 쪽 불필요 파일을 제거하므로 의도하지 않은 파일이 남는 일을 막습니다.


6단계 — Docker 사용 시

이미지를 GitHub Container Registry에 올리고, 서버에서 pull → restart 하는 방식이 일반적입니다.

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: $
          password: $

      - name: Build & push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/$:latest

      - name: Deploy
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: $
          username: $
          key: $
          script: |
            cd /opt/myapp
            docker compose pull
            docker compose up -d
            docker image prune -f

docker compose up -d는 변경된 컨테이너만 재생성하므로 다른 서비스에는 영향이 없습니다.


보안 체크리스트

배포 자동화는 잘못 만들면 자동 해킹 도구가 됩니다. 반드시 확인하세요.

  • deploy 사용자에게 비밀번호 로그인 차단 (PasswordAuthentication no)
  • deploy 사용자의 sudo 권한은 재시작 명령으로만 제한
  • SSH 포트는 22 외 다른 포트로 변경 (자동화 도구의 무차별 공격 회피)
  • authorized_keys에는 GitHub Actions용 키 외 다른 키 없음
  • 워크플로 파일에 직접 IP/비밀번호를 적지 말고 항상 Secrets 사용
  • pull_request 트리거에는 secrets가 노출될 수 있으므로 신중히 사용

흔한 트러블슈팅

Permission denied (publickey)

  • 서버의 /home/deploy/.ssh/authorized_keys 권한이 600인지, 디렉토리는 700인지 확인
  • 등록한 시크릿이 공개키가 아닌 개인키인지 확인 (실수가 매우 잦습니다)

Host key verification failed

  • ssh-keyscan 단계가 누락되었거나, 서버가 호스트 키를 변경했을 때
  • appleboy/ssh-action 사용 시 자동 처리되지만, 직접 ssh 호출 시에는 명시 필요

빌드는 성공했는데 서버에서 옛날 버전이 도는 경우

  • git pull이 충돌로 실패한 채 무시되었을 가능성
  • git reset --hard origin/main으로 강제 동기화하거나, 빌드 결과를 rsync하는 방식으로 전환

마무리

자동 배포의 본질은 “에러는 자동화하지 않고, 안전한 동작만 자동화”입니다. 테스트 단계를 충실히 두면 잘못된 코드가 일본 서버에 닿기 전에 차단할 수 있습니다.

TCP-80.NET의 일본 VPS는 SSH 포트 변경, 방화벽 설정 등에 대해 텔레그램으로 한국어 안내를 제공합니다. CI/CD 환경 구성 중 문의가 있으시면 @tcp80net으로 부담 없이 문의해 주세요.