Using Github Actions to Deploy Updates to my VPS

martinandersongraham

Martin Graham

Posted on August 12, 2022

Using Github Actions to Deploy Updates to my VPS

I self-host my martingraham.dev website on a VPS on Linode. The source code is hosted on github, and so every time I wanted to committed updates I had to remote into my VPS to pull the latest version of the repo, build the site and restart the relevant processes. What is this, 1985? Enter Github Actions.

Github actions introduction

Github actions are customizable automations that can be triggered by various github-related events. In my case, my action occurs when a commit to my main branch occurs.

We define our actions in a .yml file located in .github/workflows. Its worth saying at the top - this file will be visible in our repository, so we want to be careful about what information is exposed. Security first!

name: Update Linode VPS Action
on: 
  push:
    branches:
      - 'main'
jobs:
  Push-to-linode:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh/
          echo "$SSH_KEY" > ~/.ssh/staging.key
          chmod 600 ~/.ssh/staging.key
          cat >>~/.ssh/config <<END
          Host staging
            HostName $SSH_HOST
            User $SSH_USER
            IdentityFile ~/.ssh/staging.key
            StrictHostKeyChecking no
          END
        env:
          SSH_USER: ${{ secrets.STAGING_SSH_USER }}
          SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
          SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
      - name: Run npm run deploy
        run: |
          ssh staging 'cd ~/martingraham-dot-dev; npm run deploy'

Enter fullscreen mode Exit fullscreen mode

Let's break this down:

on: 
  push:
    branches:
      - 'main'
Enter fullscreen mode Exit fullscreen mode

This bit defines when the action will run. Mine is super simple, but events are very configurable for advanced deployments.

A workflow can have multiple jobs - mine only has one, entitled Push-to-linode

jobs:
  Push-to-linode:
Enter fullscreen mode Exit fullscreen mode

This conditional isn't doing anything (due to my event setup), but I left it in for my own future reference of the capability

if: github.event_name == 'push' && github.ref == 'refs/heads/main'
Enter fullscreen mode Exit fullscreen mode

I should mention the runs-on: ubuntu-latest line. Github actions run on so-called runners. In our case, the runner is operated by github. Our later commands will be run from within this runner environment. You can self-host your own runner, but that could potentially expose the machine to arbitrary code execution (say you have an action that runs on PRs or forks), and so hosting your own run is discouraged for public repos.

jobs:
  Push-to-linode:
    runs-on: ubuntu-latest
Enter fullscreen mode Exit fullscreen mode

SSH and secrets

Our job runs a few steps, the first of which is to configure an SSH connection to my VPS. The run command executes command within the runner environment, so here we are creating an ssh configuration file.

run: |
  mkdir -p ~/.ssh/
  echo "$SSH_KEY" > ~/.ssh/staging.key
  chmod 600 ~/.ssh/staging.key
  cat >>~/.ssh/config <<END
  Host staging
    HostName $SSH_HOST
    User $SSH_USER
    IdentityFile ~/.ssh/staging.key
    StrictHostKeyChecking no
  END
Enter fullscreen mode Exit fullscreen mode

$SSH_USER is a pretty weird user account to use on my VPS, no? In fact, I don't want the whole internet to know the name of my VPSs internal user accounts. And I certainly don't want them to have a copy of my private SSH key. That's where env: comes in

env:
  SSH_USER: ${{ secrets.STAGING_SSH_USER }}
  SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
  SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
Enter fullscreen mode Exit fullscreen mode

This sets up environment variable on the runner which can be accessed by my jobs. In our Github repository we head into the settings and we can define secrets (SSH keys, API keys, etc) that are accessible by our Github Actions. Apparently they are also are redacted in logs, but you should always be careful about not logging such values.

NPM scripts

The next step uses my newly configured SSH connection to connect to my VPS and run commands.

- name: Run npm run deploy
  run: |
    ssh staging 'cd ~/martingraham-dot-dev; npm run deploy'
Enter fullscreen mode Exit fullscreen mode

Notice ssh staging - if you look up above you will see that in the configuration file we named the target by writing HOST staging, and that is what is being referenced here.

In my case, I am running a node app (SvelteKit to be precise) that requires a build step, and then the pm2 process associated with the app has to be restarted. I utilize an npm script to pull the latest version of the repo, build the app and restart the process. I like this way fo doing it because if I need to change my build process I don't need to mess with my github action configuration.

Conclusion

So there's a simple introduction to Github Actions - go and save yourself some steps!

Photo by Alex Knight on Unsplash

💖 đŸ’Ș 🙅 đŸš©
martinandersongraham
Martin Graham

Posted on August 12, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related