This is a clean, reusable GitHub Actions to VPS deployment flow you can copy into any project.

Step 1 — Generate SSH key for GitHub Actions

Run this on your local machine:

ssh-keygen -t rsa -b 4096 -f deploy_key -C "github-action-deploy"

You will get:

  • deploy_key (private key, keep secret)
  • deploy_key.pub (public key)

Do not upload the private key publicly.

Step 2 — Add public key to VPS

Copy the public key:

type deploy_key.pub  # Windows
cat deploy_key.pub   # Linux/macOS

On the VPS, add it to the deploy user’s authorized keys:

mkdir -p ~/.ssh
echo "PASTE_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Test SSH access:

ssh -i "deploy_key" user@vps.example.com

Step 3 — Add GitHub repository secrets

Go to GitHub > Repo > Settings > Secrets and Variables > Actions > New repository secret.

Create these secrets:

  • DEPLOY_SSH_KEY: contents of deploy_key (private key)
  • VPS_HOST: VPS IP or domain
  • VPS_USER: SSH username
  • VPS_PORT: SSH port (default 22)
  • KNOWN_HOSTS: host key (from the next step)

Step 4 — Generate known_hosts

ssh-keyscan -t rsa -p 22 vps.example.com > known_hosts

Copy the generated line from known_hosts into the KNOWN_HOSTS GitHub secret.

Step 5 — Prepare deployment folder on VPS

ssh -i "deploy_key" user@vps.example.com
mkdir -p ~/Projects/my_project

Replace ~/Projects/my_project with your real project directory.

Step 6 — Create deploy.sh on VPS

Create the script:

nano ~/Projects/my_project/deploy.sh

Use this starter script:

#!/usr/bin/env bash
set -e

echo "Starting deployment..."

cd ~/Projects/my_project

# Add project-specific commands here, for example:
# - install dependencies
# - build
# - restart service

echo "Deployment completed successfully."

Make it executable:

chmod +x ~/Projects/my_project/deploy.sh

Step 7 — Install rsync on VPS

apt update
apt install -y rsync

Step 8 — Create GitHub Actions workflow

Create the workflow file:

mkdir -p .github/workflows
nano .github/workflows/deploy.yml

Paste this workflow:

name: Deploy to VPS

on:
  push:
    branches: ["main"]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Start SSH agent and add key
        uses: webfactory/ssh-agent@v0.9.1
        with:
          ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}

      - name: Add known_hosts
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.KNOWN_HOSTS }}" > ~/.ssh/known_hosts
          chmod 644 ~/.ssh/known_hosts

      - name: Copy files to VPS
        run: |
          rsync -avz --delete --exclude='.git' ./ ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }}:~/Projects/my_project/
        env:
          RSYNC_RSH: "ssh -p ${{ secrets.VPS_PORT }} -o StrictHostKeyChecking=yes"

      - name: Run remote deploy script
        run: |
          ssh -p ${{ secrets.VPS_PORT }} ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} "cd ~/Projects/my_project && ./deploy.sh || echo 'No deploy.sh found'"

Commit and push:

git add .github/workflows/deploy.yml
git commit -m "Add generic deploy workflow"
git push origin main

Step 9 — Verify deployment

Open GitHub Actions and confirm the workflow runs on push.

Check logs for:

  • successful rsync
  • remote deploy.sh execution

Step 10 — Customize per project

Replace deploy.sh contents with project-specific commands.

Examples:

  • Node.js: npm install, npm run build, pm2 restart
  • Python: pip install -r requirements.txt, systemctl restart service
  • Docker: docker-compose pull && docker-compose up -d

Step 11 — Optional PM2 setup

npm install -g pm2
pm2 start server.js --name my_project
pm2 startup systemd
pm2 save

Step 12 — Troubleshooting

  • Permission denied (publickey): check DEPLOY_SSH_KEY and authorized_keys on VPS.
  • Host key verification failed: confirm KNOWN_HOSTS matches ssh-keyscan output.
  • rsync: command not found: install rsync on VPS with apt install -y rsync.
  • deploy.sh not found: verify script path and run chmod +x deploy.sh.