Deploy with Rsync on Github Actions

Deploy with Rsync on Github Actions Featured Image

There’s a lot of great ways to deploy code into production nowadays.

Some cloud providers even handle deployment themselves, you setup a repository in Bitbucket or Github, connect the Cloud provider and they’ll build and deploy the code based on your specifications. There’s also a bunch of third-party providers that can do this for you, acting as the “deployment middle man”.

This is great for teams or complex systems with complex build requirements, but what if you have a simple or legacy app that doesn’t support these methods and you don’t want to continue relying on manually uploading code via SFTP (or FTPS… shudder)?

Well it’s not an ideal solution but you can use Rsync on Github Actions for this. Github also has a generous free plan so it usually doesn’t cost a penny - depending on how often you deploy.

If you’re not familiar Github Actions is a container-like automation service built into Github, you can write some YAML code to run a specific container and some code/actions during a Pull Request, merge or during a commit to the main branch etc.

Note: Although Bitbucket has a similar system called Pipelines, which also uses YAML for the configuration, they work fundamentally differently to Github Actions and are less flexible for these sort of project. So we’ll focus on Github Actions here.

Setup Rsync to Deploy on Github Actions

Most Linux based servers that support SFTP/FTPS also support other basic Linux tools like SSH and Rsync, both of which are needed for this type of deployment to work.

Warning: This sort of rsync deployment works great for legacy or simple projects, like old-school PHP projects, but it’s not fool-proof. Updates are technically handled file-by-file (although usually quickly) so if you’ve got a complicated project or some serious load on your system it may not be a good idea to use rsync as all the files may not be updated at once, potentially momentarily breaking your application. If you’re in this sort of situation you should really opt for a more robust method of deployment. That being said, let’s jump in.

When setting up this sort of CI/CD automation (Continuous Integration / Continuous Delivery), it’s best to run some tests on your code before deploying it into production.

You may have your own tests to run or you can use something simple and all-encompassing like Github’s “Superlinter” to catch the bare basics of sanity testing before deployment.

In the deployment YAML code below, we’ll assume you already have another Github Actions workflow running your tests called “Web Sanity Test Linting” and we’ll assume your main Git branch is called “master” (more modern repositories commonly name their main branch as “main”).

The deployment will only happen after those tests successfully complete.

Step 1. On Github, go to your project’s repo, then go to Actions tab, and then choose “Setup a workflow yourself”.

You’ll be taken to a simple code editor in the web-browser. Once you write the Action and commit it, it’ll be added to your repository under .github/workflows/<<filename>>.yml.

Step 2. Copy the YAML code below and paste it into your Github Action, editing the config variables to suit your situation. I’ll explain them in more detail below.

Step 3. Commit something to your main branch that passes the tests and it’ll start to deploy. You can monitor the deployment logs in the Github Action’s tab.

Deployed! 🚀

name: Production Deploy 

on:
  workflow_run:
    workflows: ["Web Sanity Test Linting"]
    branches: [master]
    types:
     - completed

jobs:
  deploy:
    # only deploy if the previous workflow was a success 
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    # run on ubuntu container
    runs-on: ubuntu-latest

    steps:
    # grab the repo
    - uses: actions/checkout@v2
    - name: Rsync Deploy
      env:
        # define SSH/Rsync destination environment var
        dest: '<<USER>>@<<SERVER>>:/srv/<<PATH>>'
      run: |
        echo "${{secrets.DEPLOY_KEY}}" > deploy_key
        chmod 600 ./deploy_key
        rsync -chrlvzi --delete \
          -e 'ssh -i ./deploy_key -p 22 -o StrictHostKeyChecking=no' \
          --exclude '/deploy_key' \
          --exclude '/.*/' \
          --exclude '.*' \
          --exclude '/cgi-bin/' \
          --exclude 'README.md' \
          ./ ${{env.dest}}

GitHub Actions Rsync Deploy Explained

on:
  workflow_run:
    workflows: ["Web Sanity Test Linting"]
    branches: [master]
    types:
     - completed

This section runs the deploy Action after the previous testing/linting action completes - however “completes” does not necessarily mean “completes successfully”, so we need to make sure the tests actually pass before deploying.

jobs:
  deploy:
    # only deploy if the previous workflow was a success 
    if: ${{ github.event.workflow_run.conclusion == 'success' }}

This section under jobs makes sure our deploy only runs after our tests from the previous action complete successfully.

      env:
        # define SSH/Rsync destination environment var
        dest: '<<USER>>@<<SERVER>>:/srv/<<PATH>>'

Make sure to edit the dest variable in the env environment variable section. This is where you’ll need to enter your SSH server details.

Warning: Make sure the path to deploy your application after the colon : is correct e.g. /var/www - otherwise, you may end up destroying parts of your server. If you try to deploy files to the wrong location, which already contains other files, and your SSH user has write permissions, it’ll delete the existing files while trying to sync your new files from the repo.

      run: |
        echo "${{secrets.DEPLOY_KEY}}" > deploy_key
        chmod 600 ./deploy_key
        rsync -chrlvzi --delete \
          -e 'ssh -i ./deploy_key -p 22 -o StrictHostKeyChecking=no' \
          --exclude '/deploy_key' \
          --exclude '/.*/' \
          --exclude '.*' \
          --exclude '/cgi-bin/' \
          --exclude 'README.md' \
          ./ ${{env.dest}}

This is the rsync command that actually connects to your server and attempts to deploy the code.

Notice it uses ${{secrets.DEPLOY_KEY}}, this means you’ll need to copy your SSH private key into your Github repos secrets vault (via the settings) so that it can connect to rsync via SSH on your server without a password.

Setting up key-based SSH login on a Linux server is beyond this post, but there’s plenty of tutorials out their should you need one.

You can also change the default SSH port 22 if yours is a custom port.

You’ll also notice that there are a few --exclude flags, these are some basic examples of files you might not want to deploy into your server from your repo. Like the default README.md file and any hidden directories/files starting with a dot . (if you use .htaccess files you’ll need to include these hidden files).

Good luck, try on a testing server first and watch the logs!

Comments & Questions

Reply by email to send in your thoughts.

Comments may be featured here unless you say otherwise. You can encrypt emails with PGP too, learn more about my email replies here.

PGP: 9ba2c5570aec2933970053e7967775cb1020ef23