Why I am writing this post
At the beginning of this year, I bought/rented (whatever you want to call it) a VPS to host my personal projects, and I learned a lot by setting it up and managing it.
I have been making some small tools that I think are useful for me, and each of them goes through iterations.
At each iteration,
I check the code
Commit my changes
Push them to GitHub
SSH into my VPS
Navigate to the project directory
Pull the latest code
Run docker compose up -d --build to deploy the changes.
But me being lazy as I am, I often end up not even making the changes I want to make because I don't like to go through this just following a set flow approach.
There are a few reasons:
I learnt nothing new mostly
There is a delay in response from the VPS when I type any character
Most importantly, it is something I know can be easily automated
Having worked with CI/CD pipelines with GitLab at work, I am aware that this repetitive work can be reduced to just a single command.
git push
Now, for my personal projects, like millions of developers around the world, I use GitHub
And I know there is a CI/CD thingy called GitHub Actions, so this is my attempt at both learning and using GitHub Actions to make my life easier
Let's begin
Enable Actions in the project
To use actions, we go to the repository in GitHub and select the "Settings" tab.
Click on the “Actions” dropdown and the “General” option.
Select “Allow fazil-syed and select non-fazil-syed actions and reusable workflows”
and check
Allow actions created by GitHub
Allow actions by Marketplace
I am choosing this option for security reasons so no malicious action gets accidentally run.
and save.
Setting up GitHub variables
So how will GitHub (runner) do anything on my VPS? Obviously, at this point only I have the secret stuff needed to access it.
Hence, we need to create a new set of secret stuff or use existing ones and store them in GitHub as secrets.
The secret stuff in this case are
SSH Host (IP of the VPS)
SSH Keys
SSH Port
SSH Username
Generating New SSH Keys
So let's now create a new SSH key
- Generate a deployment key locally
ssh-keygen -t ed25519 -C "github-actions-deploy"
Enter the file path
/Users/syedfazil/.ssh/github_actions_deploy
The command creates two files:
github_actions_deploy
github_actions_deploy.pub
github_actions_deployis the private key. Store its contents in theSSH_PRIVATE_KEYGitHub Secret.github_actions_deploy.pubis the public key. Add it to the VPS'sauthorized_keysfile.
- Copy the public key
cat /Users/syedfazil/.ssh/github_actions_deploy.pub | pbcopy
- Add the public key to the VPS authorised SSH keys
vi ~/.ssh/authorized_keys
Add the copied SSH key at the end of the file
Storing the secrets in GitHub
We go to settings again
Scroll down, click on the “Secrets and variables” drop-down, and then on the “Actions” button.
Click on “New repository secret”
Finally, set the variable. And click on “Add secret”.
We need to repeat this process for all four of these variables.
GitHub Action Workflow
Now we are at the conclusion of our work so far
We will set up a GitHub action with this directory structure
.github/workflows/publish.yaml
Inside this file, let's throw the following code.
name: SSH into VPS and deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy over SSH
steps:
- name: Deploy project over SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
mkdir -p opensource-usefull
if [ ! -d opensource-usefull/rssskull/.git ]; then
git clone https://github.com/fazil-syed/rssskull.git opensource-usefull/rssskull
fi
cd opensource-usefull/rssskull
git pull --rebase -Xours
docker compose up -d --build
Now, on our next commit, this job will fire and deploy our latest changes
Let's go line by line
name: SSH into VPS and deploy
This defines the name of the action. Pretty self-explanatory.
on:
push:
branches:
- main
This determines the branch that will trigger the action. In this case, main.
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy over SSH
This does several things:
Determines the one job that we are using.
It runs on Ubuntu.
And the name of the job.
Now let's understand the complicated part
steps:
- name: Deploy project over SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
mkdir -p opensource-usefull
if [ ! -d opensource-usefull/rssskull/.git ]; then
git clone https://github.com/fazil-syed/rssskull.git opensource-usefull/rssskull
fi
cd opensource-usefull/rssskull
git pull --rebase -Xours
docker compose up -d --build
This step defines a script that deploys our project to our VPS via SSH.
- name: Deploy project over SSH
This sets the name of the step.
uses: appleboy/ssh-action@v1
This allows us to use SSH very easily using third-party actions.
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
This gives ssh-action access to the environment variables we set on GitHub. With these variables, we will be able to deploy to the VPS.
The Script
This is a simple script made in bash.
script: |
mkdir -p opensource-usefull
if [ ! -d opensource-usefull/rssskull/.git ]; then
git clone https://github.com/fazil-syed/rssskull.git opensource-usefull/rssskull
fi
cd opensource-usefull/rssskull
git pull --rebase -Xours
docker compose up -d --build
The script: section lets us execute a script.
Let's go over the logic of the script
mkdir -p opensource-usefull
This creates the parent directory if it doesn't exist
if [ ! -d opensource-usefull/rssskull/.git ]; then
Check if my project's directory exists on the VPS
git clone https://github.com/fazil-syed/rssskull.git opensource-usefull/rssskull
If not, then clone the project into the VPS
cd opensource-usefull/rssskull
We enter the directory
git pull --rebase -Xours
I do a rebase, discarding all local changes (changes in the VPS) in favour of the remote changes (changes in GitHub).
docker compose up -d --build
Finally, we are using docker compose to build and run the project
Let's see it in action
What I learned
How to set up a very simple continuous deployment pipeline with GitHub Actions, a VPS and SSH
Taking screenshots in the browser, adding little highlight boxes and pasting them into markdown is very annoying.










Top comments (0)