DEV Community

Cover image for Deploying on a VPS with GitHub Actions
Syed Fazil
Syed Fazil

Posted on • Originally published at blog.syedfazil.dev

Deploying on a VPS with GitHub Actions

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,

  1. I check the code

  2. Commit my changes

  3. Push them to GitHub

  4. SSH into my VPS

  5. Navigate to the project directory

  6. Pull the latest code

  7. 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:

  1. I learnt nothing new mostly

  2. There is a delay in response from the VPS when I type any character

  3. 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
Enter fullscreen mode Exit fullscreen mode

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

  1. Allow actions created by GitHub

  2. 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

  1. SSH Host (IP of the VPS)

  2. SSH Keys

  3. SSH Port

  4. SSH Username

Generating New SSH Keys

So let's now create a new SSH key

  1. Generate a deployment key locally
ssh-keygen -t ed25519 -C "github-actions-deploy"
Enter fullscreen mode Exit fullscreen mode

Enter the file path

/Users/syedfazil/.ssh/github_actions_deploy

Enter fullscreen mode Exit fullscreen mode

The command creates two files:

github_actions_deploy
github_actions_deploy.pub
Enter fullscreen mode Exit fullscreen mode
  • github_actions_deploy is the private key. Store its contents in the SSH_PRIVATE_KEY GitHub Secret.

  • github_actions_deploy.pub is the public key. Add it to the VPS's authorized_keys file.

  1. Copy the public key
cat /Users/syedfazil/.ssh/github_actions_deploy.pub | pbcopy
Enter fullscreen mode Exit fullscreen mode
  1. Add the public key to the VPS authorised SSH keys
vi ~/.ssh/authorized_keys
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This defines the name of the action. Pretty self-explanatory.

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

This determines the branch that will trigger the action. In this case, main.

jobs:
  deploy:
    runs-on: ubuntu-latest
    name: Deploy over SSH
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 }}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The script: section lets us execute a script.

Let's go over the logic of the script

mkdir -p opensource-usefull
Enter fullscreen mode Exit fullscreen mode

This creates the parent directory if it doesn't exist

if [ ! -d opensource-usefull/rssskull/.git ]; then
Enter fullscreen mode Exit fullscreen mode

Check if my project's directory exists on the VPS

git clone https://github.com/fazil-syed/rssskull.git opensource-usefull/rssskull
Enter fullscreen mode Exit fullscreen mode

If not, then clone the project into the VPS

cd opensource-usefull/rssskull
Enter fullscreen mode Exit fullscreen mode

We enter the directory

git pull --rebase -Xours 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Finally, we are using docker compose to build and run the project

Let's see it in action

What I learned

  1. How to set up a very simple continuous deployment pipeline with GitHub Actions, a VPS and SSH

  2. Taking screenshots in the browser, adding little highlight boxes and pasting them into markdown is very annoying.

Top comments (0)