Deploy Website to Server Using Github Actions

When deploying updated contents from local machine to server, copying files via rsync or scp to remote server is not good enough. Version control is necessary to keep track of the incremental changes, for roll-back or debugging. The ideal workflow would start with editing local files, git push that to a git repository (hosted on a local or remote server), then use the git repository to prompt the hosting server to update contents on the website. While this logic is fairly straightforward, it's not easy to find a good guide to do this right. Available guides returned from Google search either bring in too many idiosyncrasies that are not common denominators for such exercise, or are outdated with inaccurate references, or miss out important details, or are just plagued with bad graphics.

There are just simply too many stack choices at every step of the way. There are professional services that claim to handle the entire workflow end-to-end, such as Heroku, which seems like an overkill for small, hobby projects. There are various front-end frameworks that intend to keep the workflow entirely on the server so that they can eventually charge storage for a fee, such as good old Workpress, Wix and Gatsby, which would deprive the freedom of managing source files locally. There are different platforms for hosting git server, such as Github vs Gitlab, which leads to the debate between Github Actions vs Gitlab CI. Deployment scripts abound from the Internet, but often they are tied to specific backend such as AWS, Azure or GCP. The sheer number of possible combinations is mind-boggling.

Eventually I got it figured out though, for a simple deployment workflow that served my use case, which is the simplest setup involving a bare-bone static webpage on local machine, Github repo, and a virtual instance hosted on Digital Ocean, intended to be the smallest common denominator of any website/online application. When updating the website, all the edits shall be made locally to the content source files stored on local machine, then they are git push to Github's repo, which has an Github Action configured. Github Action will use stored secrets to essentially login to Digital Ocean instance and update the files on server.

1) Create a Github repo (under dummy user name sammy) that will host the content/application files for the website my-site. The folder should have a structure like

my-site
  |---public_html
    |---index.html
    |---img
    |---js

At this point, the index.html can be just a dummy page with minimum content. The real content, static or dynamic, can be added later on once the deployment workflow is up and running.

<html>
  <body>
      Hello World!
  </body>
</html>

On local machine, clone this repo to production environment in development environment

git clone git@github.com:sammy/my-site.git

2) Create SSH key pairs on VPS

Next we need to create an SSH key pairs on the VPS. Login to VPS' terminal via user dora (assuming you can do this by SSH from local machine without using password) and create a new pair of keys with ssh-keygen. Two new files, id_rsa and id_rsa.pub will be generated in the default folder .ssh.

We need to enable this Digital Ocean VPS instance to access Github. So the SSH key pair is created under user dora on VPS to allow dora to login to Github repo to perform git clone and git pull to update its own directory on the server.

On server terminal, copy the content of the public key cat ~/.ssh/id_rsa.pub. Open the ssh configuration file with sudo vim ~/.ssh/authorized_keys, paste the copied public ssh key to the end of the file. Enter :wq in vim editor to save the file and exit.

Give the configuration file proper permission so that ssh tasks can be performed:

chmod 700 ~/.ssh/authorized_keys

3) Enable VPS to access Github repo

Go to Github's user setting for sammy, in tab SSH and GPG keys, press New SSH key and save the copied ssh public key for VPS user dora, so that when dora calls Github for git clone or git pull  with encrypted private key, Github server can recognize dora and allow passwordless login via ssh by matching its stored public key to the transmitted (and encrypted) private key.

Click on tab Settings of the Github repo my-site, go to Secrets section. Click New repository secret to create 3 Action secrets and name them as below. It's important to note that the SSH_KEY here is the private key, NOT the public key.

Name Value
SSH_HOST IP for VPS instance (12.34.56.78)
SSH_KEY content from private key id_rsa for dora
SSH_USERNAME dora

Note that, the secrets' name MUST be identical to SSH_HOST, SSH_KEY and SSH_USERNAME. We'll be using a popular script created by a Github user. These three are the variables defined by this script so they must match.

4) Initialize file directory on hosting server

We now have all the necessary ingredients to initialize the directory on the server that will host the files for my-site. Let's go back to the server terminal.

This step can be quite tricky, as it involves making decision on the path of the folder that shall host the files. Assuming you have followed through this guide Set Up Nginx Server on Ubuntu, go to your Nginx server directory,

cd /etc/nginx/sites-enabled

Open the Nginx configuration file for my-site with vim my-site. Use sudo if necessary. Verify that in the first block of server:

  • The root path for my-site is configured as /var/www/my-site/public_html.
  • The default file to be loaded in this folder index shall be index.html

Make sure these two parameters are consistent with the file structure created on the local machine/Github repo. If not, Nginx server will not be able to work properly. Place other relevant files into the public_html folder as needed.

Now, enter into the CORRECT path for hosting the website cd /var/www. Note that it is NOT /var/www/my-site.

In /var/www, initialize the directory for my-site by git clone the files from Github repo

git clone git@github.com:sammy/my-site.git

Now on the hosting server 12.34.56.78, in directory /var/www, a new sub-directory /var/www/my-site (along with other sites like my-site-A, my-site-B, all under /var/www) is created. Based on Nginx's configuration in /etc/nginx/sites-enabled/my-site, visiting 12.34.56.78 will display the content on /var/www/my-site/public_html/index.html.

This git clone is a one-time action. All subsequent updates on VPS will be achieved via git pull command.

5) Create a Github Action to handle CI/CD pipeline to VPS

Switch back to Github, in the newly created repo my-site, click on the Actions tab and create a yaml file deploy.yml (or any other sensible file name with .yml extension). This file will define the workflow and tell Github how to send updated files to VPS. Copy and paste the below script into the yml file:

# This is a basic workflow to help you get started with Actions

name: Build & Deploy

# Controls when the workflow will run on:
  # Triggers the workflow on push or pull request events but only for the main branch
  push:
    branches: [ main ]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  deploy:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - name: Deploy Bootstrap 5 static webpage for my-site
        uses: appleboy/ssh-action@master # call a custom script by user appleboy for SSH on github
        with: # all below inputs are specifically defined in ssh-action script
          host: ${{secrets.SSH_HOST}} # IP address of the server
          key: ${{secrets.SSH_KEY}} #private key of the server
          username: ${{secrets.SSH_USERNAME}} # user of the server
          
          script: |
            cd /var/www/my-site # enter into the folder that will host the landing page for the first time
            git pull # update the working directory with the latest from the github repo
            echo 'Deployment successful to Digital Ocean' # Display message to reflect deployment status

Let's try to understand what is happening here. Read up Github Actions' official documentation to understand its syntax and common uses. The key portion of the script that kicks off the deployment workflow starts with steps:.

First, the script calls a custom script ssh-action made by Github user appleboy from master branch of his repo with command uses:. This particular script then uses 3 parameters host, key and username, which point to the 3 secrets already stored SSH_HOST, SSH_KEY, and SSH_USERNAME with command with:. The objective of this script is to establishes SSH access between VPS and Github. There are many similar custom deployment scripts on Github. They all come in different shapes and forms and could use entirely different parameters that don't look like host, key or username. I just happened to stumble upon this one made by appleboy and it worked well. This script  carries 1.4K stars at the moment, demonstrating wide popularity and strong community support. For casual personal website projects, it's fine to rely on such third-party open-sourced script to avoid the hassle of recreating the wheel. For more serious business endeavors, it would be ideal to write your own deployment script.

Second, after VPS establishes passwordless SSH access to Github (via user dora with the ssh key pair created on VPS), user dora enters into the right path /var/www/my-site and performs a git pull with an onscreen status message. Given the file structure in my-site.git repo, this action will pull all the incremental changes in sub-directory public_html.

That's it! With Github Action and a very helpful third-party script, a full deployment workflow is created. Going forward, you just make all the edits in the repo folder in the local machine, git push to Github repo my-site.git, and Github Action will automatically trigger subsequent steps to SSH into the hosting server and sync up contents there via git pull from the same Github repo.

You can now go ahead to do something fancy on the plain vanilla index.html.


1. Thanks for reading! 1082.xyz newsletter shares the learning and discovery for aspiring polymath, on crypto, blockchain, investment, startups, productivity hacks and technology in general. If you're forwarded this email, you can sign up for the free 1082.xyz newsletter here or just press the "Subscribe" button to receive the latest articles directly in your inbox. The free tier would be just fine to receive all available contents.
2. You're welcome to share your feedback and comments in the comment section. No email registration is needed. You can even do so anonymously.
3. If you find this article helpful, please share it with like-minded friends. Thank you.