Recently, I wanted to auto-deploy one of my websites using GitHub Actions. The website is an SPA based on SvelteKit and I want to deploy it to my VPS using rsync.

The problem is finding a tutorial about using rsync with GitHub Actions is hard. Most of the tutorial is deploying static sites to GitHub Pages.

So, after some googling, here is what I found.

Create and Add SSH Private Key to GitHub Secrets

To make a connection from GitHub Actions to VPS, we need to create SSH Private Key and add it to GitHub Secrets.

  1. The best way to make an SSH key is to create it locally using this command.

    IMPORTANT: Do not add a passphrase to the key, so we don’t need to send a passphrase to GitHub Actions.

    cd ~/.ssh
    
    ssh-keygen -t ed25519 -C "[email protected]" -f deploy_key
    

    Now we have deploy_key and deploy_key.pub in the ~/.ssh directory.

  2. Next, we need to add the content of deploy_key to GitHub Secrets. The Secrets can be found in Settings > "Secrets and variables" > Actions > New repository secret.

    In my case, I add SSH_KEY as the name and paste the content of deploy_key as the value.

  3. In the VPS we need to add the content of deploy_key.pub to ~/.ssh/authorized_keys. In home directory, create .ssh directory if it doesn’t exist.

    mkdir ~/.ssh
    

    Then, create an authorized_keys file and paste the content of deploy_key.pub to it.

    touch ~/.ssh/authorized_keys
    

    IMPORTANT: Make sure to change the permission of .ssh directory to 700 and authorized_keys to 600 or 400.

    chmod 700 ~/.ssh
    chmod 600 ~/.ssh/authorized_keys
    
  4. Create the rest of the Secrets that GitHub Actions would use, it is good practice for a public repository to use Secrets for sensitive information.

    DEPLOY_DIR # The target directory in VPS, e.g. /var/www
    HOSTNAME # The hostname or IP of VPS
    PORT # The port of ssh connection
    USERNAME # The account name for the ssh in VPS
    
    All GitHub Actions Secrets

Setup GitHub Actions

To set up The GitHub Actions workflow we need to create a cd.yml file in the .github/workflows directory.

  1. Create a workflow directory if it does not exist and create a cd.yml file.
    mkdir -p .github/workflows
    touch .github/workflows/cd.yml
    
  2. Add the following content to the cd.yml file.
    name: Deploy
    
    on:
      push:
        branches: [ main ]
    
    jobs:
    
      deploy:
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
        - name: Build with Node.js 20
          uses: actions/setup-node@v3
          with:
          node-version: 20
          cache: 'npm'
        - run: npm ci
        - run: npm run build
        - name: Configure SSH
          run: |
          mkdir -p ~/.ssh/
          echo "$SSH_KEY" > ~/.ssh/staging.key
          chmod 600 ~/.ssh/staging.key
          cat >>~/.ssh/config <<END
          Host prod_server
            HostName $SSH_HOST
            User $SSH_USER
            Port $SSH_PORT
            IdentityFile ~/.ssh/staging.key
            StrictHostKeyChecking no
          END
          env:
          SSH_USER: ${{ secrets.USERNAME }}
          SSH_KEY: ${{ secrets.SSH_KEY }}
          SSH_HOST: ${{ secrets.HOSTNAME }}
          SSH_PORT: ${{ secrets.PORT }}      
        - name: Deploy to VPS with SSH
          run: rsync -avrzh --update --delete ./build/ ${{ secrets.USERNAME }}@prod_server:${{ secrets.DEPLOY_DIR }}
    

The key to using rsync in GitHub Actions is configuring SSH connection in the workflow.

#...
jobs:
  deploy:
    #...
    - name: Configure SSH
      run: |
      mkdir -p ~/.ssh/
      echo "$SSH_KEY" > ~/.ssh/staging.key
      chmod 600 ~/.ssh/staging.key
      cat >>~/.ssh/config <<END
      Host prod_server
          HostName $SSH_HOST
          User $SSH_USER
          Port $SSH_PORT
          IdentityFile ~/.ssh/staging.key
          StrictHostKeyChecking no
      END
      env:
      SSH_USER: ${{ secrets.USERNAME }}
      SSH_KEY: ${{ secrets.SSH_KEY }}
      SSH_HOST: ${{ secrets.HOSTNAME }}
      SSH_PORT: ${{ secrets.PORT }}      

Then we can use rsync to deploy our site to VPS.

#...
jobs:
  deploy:
      #...
      - name: Deploy to VPS with SSH
        run: rsync -avrzh --update --delete ./build/ ${{ secrets.USERNAME }}@prod_server:${{ secrets.DEPLOY_DIR }}

This step will deploy the content of the ./build directory to DEPLOY_DIR.

The rest of the workflow is just installing dependencies and building the project.


References