Migrating from CircleCI to GitHub Actions

Building cross-platform Go binaries and Docker images

August 24, 2021

TL;DR

  • Our GitHub action file
  • thatisuday/go-cross-build to build cross-platform Go binaries
  • skx/github-action-publish-binaries to attach artifacts to the GitHub release
  • docker/setup-qemu-action@v1 to build docker images for multiple platforms
  • docker/setup-buildx-action@v1 not required but recommended
  • docker/metadata-action@v3 to attach labels and tags to the docker image
  • docker/login-action@v1 to login to a Docker registry
  • docker/build-push-action@v2 to build the Docker images and push them to the registry

Our Previous CI Setup with CircleCI

When we wrote our Kamailio Exporter, we chose to go the Prometheus way, which is the use of a Makefile and the promu tool.

This required at least 3 files:

Our CI was doing two things:

  • build cross-platform Go binaries
  • attach them to the GitHub release

The interesting parts in the CircleCI config file were:

  build:
    machine: true

    steps:
    - checkout
    - run: make promu
    - run: promu crossbuild -v
    - persist_to_workspace:
        root: .
        paths:
        - .build

  release_tags:
    docker:
    - image: circleci/golang:1.15

    steps:
    - checkout
    - run: mkdir -v -p ${HOME}/bin
    - run: curl -L 'https://github.com/aktau/github-release/releases/download/v0.7.2/linux-amd64-github-release.tar.bz2' | tar xvjf - --strip-components 3 -C ${HOME}/bin
    - run: echo 'export PATH=${HOME}/bin:${PATH}' >> ${BASH_ENV}
    - attach_workspace:
        at: .
    - run: make promu
    - run: promu crossbuild tarballs
    - run: promu checksum .tarballs
    - run: promu release .tarballs
    - store_artifacts:
        path: .tarballs
        destination: releases

First, we use promu crossbuild to build Go binaries for every platform configured in .promu.yml. Then, the release_tags step makes tarballs and attaches them to the GitHub release.

With the 3 files combined, that’s a total of 233 lines to cross-build binaries and attach them to the GitHub release.

Using GitHub Actions

When writing our FreeSWITCH exporter, we wanted to checkout GitHub Actions.

We were able to provide the same results in just 28 lines of one file:

name: Build binaries

on:
  release:
    types: [created]

jobs:
  binaries:
    name: Cross-platform binary builds
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Generate build files
        uses: thatisuday/go-cross-build@v1.1.0
        with:
          platforms: linux/amd64,linux/arm64
          name: freeswitch_exporter
          dest: dist

      - name: Upload build artifacts
        uses: skx/github-action-publish-binaries@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          args: ./dist/*

We use two community-built actions to do this:

Building and publishing docker images with GitHub Actions

With the FreeSWITCH exporter, we also wanted to provide docker images as well.

Here is how we did it:

  docker:
    name: Cross-platform Docker images
    runs-on: ubuntu-latest

    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v3
        with:
          tags: type=semver,pattern={{version}}
          images: florentchauveau/freeswitch_exporter

      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

We use the following actions:

  • docker/setup-qemu-action@v1 because we want to build for multiple platforms
  • docker/setup-buildx-action@v1 not required but recommended
  • docker/metadata-action@v3 to attach labels and tags to the docker image
  • docker/login-action@v1 to login to DockerHub (using secrets)
  • docker/build-push-action@v2 to build the docker images and push them to the registry

Writing the actions was pretty straightforward and worked right away.

The metadata action allows you to automatically apply tags and labels to your image. Because we are tagging our releases with semver, we chose to use this pattern for tags.

The v1.0.1 tagged release on GitHub created an Docker image tagged as florentchauveau/freeswitch_exporter:1.0.1 and latest.

The following labels were automatically applied to the image:

"Labels": {
    "author": "Florent CHAUVEAU <florent.chauveau@gmail.com>",
    "org.opencontainers.image.created": "2021-08-22T18:30:45.018Z",
    "org.opencontainers.image.description": "",
    "org.opencontainers.image.licenses": "MIT",
    "org.opencontainers.image.revision": "f46f3c6a35089892833241c3bc6356932a50ae7c",
    "org.opencontainers.image.source": "https://github.com/florentchauveau/freeswitch_exporter",
    "org.opencontainers.image.title": "freeswitch_exporter",
    "org.opencontainers.image.url": "https://github.com/florentchauveau/freeswitch_exporter",
    "org.opencontainers.image.version": "1.0.1"
}

Final notes

Playing with GitHub Actions turned out to be a good choice, because we were able to provide more, with fewer lines. It is also much simpler than using an external CI provider, 3 files, make, and promu. Simpler is (very often) better.

Further reading

Thanks for reading!

I am currently hiring a devops engineer to help me build the future of telcos at Callr.

Do you speak French and English, love what you do and know a thing or two about Unix, Docker, Ansible and software engineering?

Reach out to me and let's have a chat.