GitLab CI/CD pipelines and container registry for ARM Cortex firmware builds 
Tags: opensource, cicd, gitlab, development
Published: 2020-05-21 19:36:16

---
title: GitLab CI/CD pipelines and container registry for ARM Cortex firmware builds 
published: true
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/thw880krgcn37w4jos0o.png
description: From a community question on the first steps with CI/CD to a source code analysis and fixed build provided in a Merge Request. 
tags: opensource,cicd,gitlab,development
---


You'll learn best about a specific tool or program by helping users with their problems and questions. I have experienced this when starting with Nagios/Icinga more than 10 years ago. Today I am trying to help the GitLab community and learn myself a lot. Often I just have an idea but never tackled the code or environment myself.

Today I have found a question on the GitLab forums on the [first steps with CI/CD](https://forum.gitlab.com/t/gitlab-ci-first-try-2-issues/37787). It actually involved building [source code](https://gitlab.com/spectrum70/zion-firmware) for an ARM Cortex hardware architecture:

> to build a small cortex-m4 firmware

The question was about a specific build step and `make` not being found. Also, whether the Docker-in-Docker image build really is the correct way of doing it.

Never did that before for ARM Cortex builds. The question also included a link to the project repository and its `.gitlab-ci.yml` with all the details there. It also showed the build error right in front of me.

Guess what I did next? Right, the power of open source and collaboration - I forked the repository into my [account](https://gitlab.com/dnsmichi)!

## Analyse the first make error

The Docker-in-Docker approach is used to build a new image providing the cortex-m4 hardware specific build tools. Among the container build steps there was one which executed a `test.sh` script which ran `make`.

Ok, then let's just see how to install `make` into the `docker:git` image. Wait, which OS is that?

```
$ docker run -ti docker:git sh

/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.11.6
PRETTY_NAME="Alpine Linux v3.11"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"

/ # apk add make

OK: 26 MiB in 26 packages
```

The CI build runs fine now, the script does something. But does it really compile the binaries ... it is executed in the outer Alpine Docker container, not inside the newly created build image.

## Build the image, then compile inside the image

Then I remembered the two stages for GitLab CI/CD and the container registry: 

- First, create a new build image with your tool chain. Push that into the container registry. 
- Second, define a new job which uses this image to spawn a container and compile the source code from the repository 

To better visualize this, let's create two stages as well:

```
stages:
  - prepare
  - build
```

![CI/CD stages](https://dev-to-uploads.s3.amazonaws.com/i/16ez21ydqgwjpbki4nqf.png)

> **Tip**
>
> Use the Web IDE for syntax highlighting and direct CI/CD feedback in the upper right corner.

To prepare this environment, define these variables: `DOCKER_DRIVER` is read by the CI/CD Runner environment in the background, while the `IMAGE_TAG` allows a shorter variable reference for the GitLab registry URL to the container image.

```
variables:
  DOCKER_DRIVER: overlay2
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
```

### Build and push the Docker image

The next step is to define the build image. Ensure that the [Dockerfile](https://gitlab.com/spectrum70/zion-firmware/-/blob/master/dockerfile) is located in the main repository.

Wait, there was Docker-in-Docker? Yes, the CI/CD runners use the Docker executor, so every job on its own is run inside a container.

- A container does not run any services by default.
- You want to build a new Docker image inside this job container.
- There is no Docker daemon answering `docker login` at first.

The trick here is to virtually share the runner's Docker daemon socket with the job container building the image. This happens in the background, only visible with the settings for `image: docker` and `service: docker:dind`. 

Things to note:

- The job name `prepare/image` can be freely defined and is just an example.
- The `image` is defined in the job scope and uses the DinD `docker:git` image
- The job belongs to the `prepare` stage
- Before any script is run, the login into the container registry is persisted once
- The `docker:dind` service must be running to communicate with the outside Docker daemon
- The script invokes building the Docker image and tagging it accordingly. If this steps success, it pushes the newly tagged image to authenticated container registry.

```
# Build the docker image first
prepare/image:
  image: docker:git
  stage: prepare

  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_JOB_TOKEN" $CI_REGISTRY

  services:
  - docker:dind

  script:
    - docker build --pull -t "$IMAGE_TAG" .
    - docker push "$IMAGE_TAG"
```


### Compile the source code

The reason for creating the Docker image is a different architecture and specific cross compilation requirements for the ARM Cortex architecture. A similar approach is needed when building binaries for Raspberry Pis with using QEMU for example. 

Things to note:

- Use the previously tagged `image`
- Assign the job to the `build` stage
- Run the compilation steps in a script provided in the same Git repository. It is located in `scripts/build.sh`.
- The binaries need to be stored somewhere. The easiest way is to define them as artifacts and store them on the GitLab server in the job.


```
# Build the source code based on the newly created Docker image
build/source:
  image: $IMAGE_TAG
  stage: build
  script:
    - ./scripts/build.sh 
  artifacts:
    paths:
      - "thermo/*.bin"
```

![GitLab CI/CD job result](https://dev-to-uploads.s3.amazonaws.com/i/vrk3fldipmmpkyummser.png)

### Conclusion

I didn't achieve this in one shot, so don't fear a build to fail and another Git commit to attempt to fix it. The hardest challenge is to understand the code and scripts from someone else, and iteratively learn.

Open source definitely helps as we can collaborate and work on the different challenges. You can see my attempt in [this merge request](https://gitlab.com/spectrum70/zion-firmware/-/merge_requests/1). 

More questions on GitLab CI/CD first steps? Hop onto the [forum](https://forum.gitlab.com) and maybe we'll write another dev.to post from our findings!