Replacing Travis CI With BuildBot


Back when I reactivated this blog I posted about using Travis CI to automate the build process. Sadly at the end of last year Travis announced they were ending free builds for all public repositories, and only authorised open source projects will now get free build credits.

The repository for this blog is publically accessible, partly in case anyone wants to see my draft posts, or raise a merge request to fix a typo, but mostly because why not? That previously allowed me to not worry about the cost of building the site, but it’s not unreasonable for a private company who need to make a profit to want to focus their generosity on actual open-source projects. I certainly don’t blame them for the policy change, although I hope the approval process for open source projects is easy and widely applied, so it’s not just a few big projects that can take advantage of it.

There are many other build services out there, but I don’t really want to pay to build my blog, so I focused on finding an easy to use open-source CI system I could run locally. In the end, I settled on BuildBot which has been around since the early 2000s, and I actually have some experience with it from around that time. BuildBot has the advantage of being Python-based (one of my preferred languages) and it uses Python as its configuration language so setting it up should be straightforward.

I already have a number of Docker containers running on my Synology NAS. Adding Buildbot requires adding two more, a buildmaster and a worker. The build master needs no special handling, you can run the standard Docker image and just mount a configuration file into the right location. For the worker, it needs a little more thought as you need to bake any dependencies into your image so they’re available to the builds. It is relatively straightforward to build on top of the default worker image, and my Dockerfile can be found here.

One problem I encountered was that the Buildbot documentation is focused on the model of one project that has one BuildBot instance, but I need to share the builder across multiple projects. Fortunately, it works well if you have a SingleBranchScheduler per project, and use a ChangeFilter to filter the incoming webhooks from GitHub.


Each scheduler points to a different builder, which uses a unique BuildFactory to specify the build steps. I try and keep my configuration in a within the projects themselves.

factory = util.BuildFactory()
        command=["bash", "./"],

One advantage of Travis was that much of the build process was automated, and you didn’t need to configure it. With BuildBot you need to specify every step, and the running of code only on master, or only on a tag is not as simple. After much trial and error, the following code works. You can see the full file in the glowprom project. First, we extract the branch name of the current checkout, then compare it to master or use a prefix match to determine if we’re on a tagged version. When running on master the Semantic release call is the final step, so we know all the tests have passed. This will tag a release, which again triggers the pipeline, but this is a tag with a v as the initial character, so we build a Docker image and push it to the Docker Hub.

BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "Building branch $BRANCH"
if [[ "$BRANCH" == "master" ]]; then
  semantic-release publish
if [[ ${BRANCH:0:7} == "heads/v" ]]; then

Timeliness of feedback on your commit is an important factor in enabling quick developer cycle times. Travis enabled that by using a webhook so it was notified immediately when you pushed a commit. The problem with running BuildBot internally is that it isn’t externally accessible. BuildBot makes it easy to run a scheduled build, so you can build every day, hour or even more frequently if you want. The problem is that you have to wait for that build to be triggered to get feedback on your commit, or you could manually trigger it, but who wants to add a manual step like that?

Fortunately, I already host this blog on Linode, and I have a VPN set up between the server and my internal network. I added the below config to the Apache instance that serves the blog. For my external DNS name it uses mod_proxy to forward requests to /change_hook/github on to the BuildBot instance, which will go via the VPN. GitHub and BuildBot already supports a shared secret for authentication that the request is valid. This is set up when you add the webhook to the GitHub API.

<VirtualHost *:80>

    <Location /change_hook/github>
        <Limit POST>
            Order allow,deny
            Allow from all

Requiring a remote server and VPN adds a significant amount of complexity to the setup. I believe most home routers support some form of port forwarding, so you could probably also use that. However, I would be concerned about exposing more of my home network to the internet than necessary. Please let me know in the comments if you have any suggestions on how to simplify the setup.

This setup has been running for a couple of months and seems to be working well. It is both reliable and means I can sleep easy, knowing I’m not leeching of a company who were just trying to help real open source projects.

Photo Brick laying by Scott Lewis.