The simplest CI setup ever

This weekend I started to go back and look at a few projects of mine. A few years ago I setup most of those using a mix of Travis CI for building/testing, docker hub for image building/hosting and then somehow wiring this all together with a custom stack. Today this feels outdated and overly complex. As an SRE I’m allergic to complexity. So I spent a few hours checking out the new Github packages and actions.


Both packages and actions come with a unlimited option for open source projects and a nice free tier for private repositories. For actions you get 2000 build minutes per month. For packages 500MB in storage is included. There is more fine print for data transfer but that seems really generous and will mostly suffice for any small to medium size project. (Don’t mine bitcoin in the Github actions runner please :-D)


Github has a somewhat unfair advantage here: their stuff is build right into the UI. But that is exactly why its nice. Github has been amazing at creating clear and understandable user interfaces. Compare to the mess that docker hub is, I can only applaud them for a job well done.

The docker registry neatly integrates with the rest of the interface. It provides the most needed information right away.

To get started, there is nothing else to do, than write a simple workflow file. If you already have your docker file setup, this can be enabled within 2 minutes. For my CalendarProxy project (written in PHP) I actually created two separate workflows. One for any pull requests to master and one for actual commits on master. First lets take a look at the build file for the pull requests:

name: CI
    branches: master
    runs-on: ubuntu-latest
    - uses: actions/checkout@v2
    - name: Validate composer.json and composer.lock
      run: composer validate
    - name: Install dependencies
      run: composer install --prefer-dist --no-progress --no-suggest
    - name: Run test suite
      run: composer test

The on pull_request section is the part when this code should trigger. So any time a pull request is targeting the master branch it runs all the jobs defined below. Jobs usually run in parallel and execute independently. This is nice to speed up your build times and run checks faster.

Inside the jobs section we can see the test section, which is just the name of that particular build step. runs-on specifies which host system os to use. This shouldn’t really matter in most cases, but Github offers Ubuntu, macOS and Windows. This is somewhat amazing, as in the past getting macOS runners for TravisCI was not part of the free plan. So building iOS apps was not possible. How can all of this be free? Amazing!

The steps sections defines individual commands for this build. Its very similar to what we all already know from other CI configurations. There is one difference though which just amazes me: the uses keyword. This is kind of a plugin architecture, where you can reuse build steps that might be more complex. The actions/checkout@v2 step is basically a “update the repo with the version that we currently want to test” build instruction. Check out the full source code of the action here.

I remember this to be a much more involved thing previously, where you would get a tag/branch name and would manually somehow construct the right git command to get the right repo setup. These actions are a superbly good selling point, as it makes it way easier to build complex pipelines quickly. Bitbucket pipelines have something similar called pipes but when I tried them about a year ago it felt much more complex to configure them correctly.

The rest of the above steps install dependencies, do some basic validation and run the test suite. That is enough to reason, if there are any breaking changes hopefully.

The other workflow I have configured gets triggered, whenever there is a push / merge onto the master branch:

name: CI
    branches: master
    runs-on: ubuntu-latest
    - name: Checkout repository
      uses: actions/checkout@v1
    - name: Build and push Docker images
      uses: docker/build-push-action@v1.1.0
        username: ${{ }}
        password: ${{ github.token }}
        repository: tum-dev/calendarproxy/server
        tags: latest
        tag_with_ref: true
        tag_with_sha: true
        add_git_labels: true

Here I use the docker/build-push-action@v1.1.0 action to do all the steps to build and publish the docker image. Again, the action is separate code configured here. Take a look, cause its ridiculously easy to create such actions. The action has a few nice parameters that you can read about here.

Github automatically triggers all build jobs on the configured events. The logs are neatly provided and have useful features like auto scroll and folding when complete.

One thing that amazed me here: using the default provided env variables in the github array we don’t need anything else to push to the Github integrated docker repository. No secrets to configure, no nothing. Its that easy.


I think Microsoft and Github are doing a great job. The vendor lock in here is getting super strong and not labeling anything here Azure is a good idea to keep away people that dislike it or had bad experiences. These actions and the integrated docker repository make it absolutely easy to do basic CI.

At this point in time there is no excuse to not use such setups for your systems. Anyone still doing git pull in a random webserver folder should go ahead and automate their work right away. Don’t be lazy, instead go crazy with automation.

I’m interested in how we can make this even simpler. Especially the story around continues deployment and spinning up an environment for each pull request is still something that seems overly complex. I’d hope we figure out better solutions for this in the future. (No I shouldn’t be required to use kubernetes for this)

Published by

Kordian Bruck

I'm a TUM Computer Science Alumni. Pizza enthusiast. Passionate for SRE, beautiful Code and Club Mate. Currently working as an SRE at Google. Opinions and statements in this blog post are my own.