Migrating from CircleCI to GitHub Actions

Building cross-platform Go binaries and Docker images

August 24, 2021


  • 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:

    machine: true

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

    - image: circleci/golang:1.15

    - 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

    types: [created]

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

      - name: Checkout
        uses: actions/checkout@v2

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

      - name: Upload build artifacts
        uses: skx/github-action-publish-binaries@master
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          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:

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

      - 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
          tags: type=semver,pattern={{version}}
          images: florentchauveau/freeswitch_exporter

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

      - name: Build and push
        uses: docker/build-push-action@v2
          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.