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'
Let's break this down:
on:
push:
branches:
- 'main'
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:
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'
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
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
$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 }}
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'
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
Top comments (1)
Thanks a lot. Very helpful the ssh part.