January 27, 2026

Auto-Deploy to VPS with GitHub Actions

Manual deployments are tedious. SSH in, pull, build, restart - every single time. Here's how to automate it.

The Setup

We use GitHub Actions to SSH into our VPS and run deploy commands whenever we push to main.

The Workflow

Create .github/workflows/deploy.yml:

name: Deploy to VPS

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to VPS
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_SSH_KEY }}
          port: 22
          script: |
            export NVM_DIR="$HOME/.nvm"
            [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
            cd ~/Projects/your-app
            git pull origin main
            npm install --production=false
            npm run build
            pm2 restart app-name

The Gotcha: NVM in Non-Interactive SSH

This is the part that trips people up. When GitHub Actions SSHs into your server, it's a non-interactive session. Your .bashrc doesn't load, which means nvm isn't initialized, which means npm and pm2 aren't in your PATH.

The fix is simple - source nvm at the start of your script:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Without this, your workflow will "succeed" (green checkmark) but nothing actually happens. Ask me how I know.

Required Secrets

Add these in your GitHub repo under Settings → Secrets → Actions:

  • SERVER_IP - Your VPS IP address
  • VPS_USER - SSH username (usually root)
  • VPS_SSH_KEY - Your private SSH key (the entire file contents)

The Result

Push to main → GitHub Actions triggers → SSH into VPS → Pull, build, restart → Done.

No more manual deploys. No more "let me SSH in real quick." Just push and forget.