Using Github Actions To Test Before You Deploy

I’ve been using DigitalOcean for quite some time now and had recently setup their App Platform to run my website. Their platform is great in that I’m able to build a docker container running Openresty and it handles all of my needs. The platform does a great job of catching docker build failures and stops attempting a deployment when this happens. A few weeks ago, I had a concerning thought in that they don’t catch problems with my Openresty configuration until it’s too late. The moment their platform executes openresty inside the container, everything pukes and my site goes offline.

This isn’t terrible because I don’t make many changes to the server configuration. If I do, I could just run the docker locally and make sure nothing is wrong. As I have continued into the DevOps world more, I realized how outdated and manual this process was. I also realized that not every change I make to the server configuration is checked manually.

The fear is real

Fast forward to today and you’ll see that I broke my site a few times tinkering. I am wanting to do some additional tricks and configuration changes in Openresty so I started making them. Just as luck would have it, the site went down and I didn’t notice right away.

Github Actions to Save The Day

I figured the easiest thing would be to replicate what I was doing manually. The easiest thing would be to create a Github action that would check my configuration files using openresty -t just like I did that one time I actually checked my configuration before push a commit. I figured I’d take some simple steps:

  1. Run the action on any commit
  2. Load the files into a container
  3. Run openresty -t
  4. Wait for Digitalocean to build and load the changes

Problem #1 – Where do I even go next?

What I wanted to do was easy as I was doing it in Dockerfile in the repo already. My initial research and knowledge had me going down the route of building the container, deploying the container somewhere, and then attempting to run the container. This seemed like way too much overhead so I found the combination of run-on and container

jobs:

  build:

    runs-on: ubuntu-latest
    container: 
      image: openresty/openresty:1.19.9.1-4-alpine-fat

The runs-on just tells Github what underlying platform you’d like to run on for the host OS. The container allows you to also run on any docker container available. This does even support private repos and a host of other configuration parameters if needed.

Problem #2 – How do I mount the repos files into the container?

My first thought was to simply use the volumes parameter and tell it to mount GITHUB_WORKSPACE to the container and off we go! This was a terrible thought as you can tell by the error that I got running the action.

github action error

If you look closely at the docker command, you’ll see that there’s a workdir specified like so --workdir /__w/k8-rev-proxy/k8-rev-proxy. It looks like the container is starting itself with a working directory of my repo. This means that the repo is already being mounted into the container! I’m now able to extend my Action to do more!

jobs:

  build:

    runs-on: ubuntu-latest
    container: 
      image: openresty/openresty:1.19.9.1-4-alpine-fat

    steps:
    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.
    - name: Checkout Branch
      uses: actions/checkout@v3

    - name: Copy Server Config
      id: copy-server-config
      run: cp conf/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf

    - name: Copy Default Config
      id: copy-default-config
      run: cp conf/default.conf /etc/nginx/conf.d/

    - name: Copy Site Configs
      id: copy-site-configs
      run: cp conf/site-*.conf /etc/nginx/conf.d/

    - name: Create Lua Directory
      id: create-lua-dir
      run: mkdir /etc/nginx/lua

    - name: Copy Access Lua File
      id: copy-lua-access
      run: cp lua/access.lua /etc/nginx/lua/access.lua

    - name: Test OpenResty Configuration
      id: test-openresty
      run: openresty -t

Problem #3 – The Action Didn’t Stop The Digitalocean Build

Digitalocean only watches a branch for pushes and then starts a build on any push. I was hoping that my Action would fail the push and therefore stop the Digitalocean build. This was not the case. This was an easy fix, I just decided to make sure all of my commits were done to a dev branch and configured the Action to trigger on that. I updated my Action a little more

name: Openresty Configuration Check

on:
  push:
    branches: [ "dev" ]
  pull_request:
    branches: [ "dev" ]

jobs:

  build:

    runs-on: ubuntu-latest
    container: 
      image: openresty/openresty:1.19.9.1-4-alpine-fat

    steps:
    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.
    - name: Checkout Branch
      uses: actions/checkout@v3

    - name: Copy Server Config
      id: copy-server-config
      run: cp conf/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf

    - name: Copy Default Config
      id: copy-default-config
      run: cp conf/default.conf /etc/nginx/conf.d/

    - name: Copy Site Configs
      id: copy-site-configs
      run: cp conf/site-*.conf /etc/nginx/conf.d/

    - name: Create Lua Directory
      id: create-lua-dir
      run: mkdir /etc/nginx/lua

    - name: Copy Access Lua File
      id: copy-lua-access
      run: cp lua/access.lua /etc/nginx/lua/access.lua

    - name: Test OpenResty Configuration
      id: test-openresty
      run: openresty -t

Problem #4 – I’m Lazy and Don’t Want to Constantly Merge My Changes

I found an Action in the Github Marketplace that would automatically merge my changes from one branch to another. Now I have my final Action.

name: Openresty Configuration Check

on:
  push:
    branches: [ "dev" ]
  pull_request:
    branches: [ "dev" ]

jobs:

  build:

    runs-on: ubuntu-latest
    container: 
      image: openresty/openresty:1.19.9.1-4-alpine-fat

    steps:
    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.
    - name: Checkout Branch
      uses: actions/checkout@v3

    - name: Copy Server Config
      id: copy-server-config
      run: cp conf/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf

    - name: Copy Default Config
      id: copy-default-config
      run: cp conf/default.conf /etc/nginx/conf.d/

    - name: Copy Site Configs
      id: copy-site-configs
      run: cp conf/site-*.conf /etc/nginx/conf.d/

    - name: Create Lua Directory
      id: create-lua-dir
      run: mkdir /etc/nginx/lua

    - name: Copy Access Lua File
      id: copy-lua-access
      run: cp lua/access.lua /etc/nginx/lua/access.lua

    - name: Test OpenResty Configuration
      id: test-openresty
      run: openresty -t

    - name: Merge dev -> main
      uses: devmasx/merge-branch@master
      with:
          type: now
          target_branch: main
          github_token: ${{ github.token }}

Conclusion

Now, I can save me from myself! Anytime that I need to make changes to my configuration, I commit the changes to dev. From there, my Action runs and makes sure that the Openresty configuration is ok. If that runs fine, then I automatically merge my changes into main and that triggers the Digitalocean App build.