Continuous Integration with Docker Containers, GitLab Runner, and CI/CD Automation for Simulink Check
By Dalton L'Heureux, MathWorks
Introduction
The importance of software reliability and integrity continues to escalate as systems become more complex and interconnected. Despite adherence to established engineering best practices, the development of high-integrity systems remains a technically demanding and financially intensive endeavor. Given the substantial effort involved in reviews, analysis, testing, and regression work, engineers are always looking for a way to reduce waste while improving overall software quality. One of the latest trends has been a push towards continuous integration and continuous deployment (CI/CD) solutions.
CI/CD practices have transformed software development, enabling rapid and consistent delivery of high-quality software. But the rapid advancement of this technology, along with the number of available tools, can make creating a robust CI/CD solution extremely challenging. This article presents a comprehensive approach to implementing CI/CD pipelines using GitLab® Runner, Docker®, and CI/CD Automation for Simulink Check™.
Importance of CI/CD for Safety-Critical Software Development and Certification
CI/CD are essential practices in modern software development, especially for safety-critical systems such as those used in healthcare, automotive, and aerospace industries. Deploying an efficient CI/CD solution can help enhance quality and reliability, provide faster detection and resolution of issues, and ensure continued compliance with regulatory standards. As a single change in requirements can cause a cascade of regression work, having a strong CI/CD process in place is essential in cost-effectively managing any safety-critical software development program.
This article will present the following steps to configure tooling outside of the native modeling environment for integration with the MATLAB® and Simulink® tool suite:
- Simulink Check Process Advisor
- GitLab and GitLab Runners
- Configuring GitLab CI/CD Pipelines
- Docker
- Creating the Docker Image
- Build Arguments
- Build Image and Launch Container
- Run GitLab Runner to Listen for Jobs
- Testing and Troubleshooting the Docker Image
Simulink Check Process Advisor
The Process Advisor app, available as part of CI/CD Automation for Simulink Check, is a powerful tool for developing a Model-Based Design pipeline within the MATLAB environment that can then be used in an external environment via the provided template .yml file. For model developers aiming to configure a process pipeline within the MATLAB environment, please explore the following documentation:
- Setting up a process model: Customize Your Process Model
- Automating model verification and validation: Automate and Run Tasks with Process Advisor
- Best practices for using Process Advisor: Best Practices for Process Model Authoring
GitLab and GitLab Runners
At this point, you are ready to bring your process model into a CI/CD pipeline in GitLab. To ensure that updates to your Simulink process model are automatically reflected in the GitLab pipeline, a unique approach is used: Instead of recreating your process model into a static .yml file, we instead use a .yml file which generates another .yml file containing the current tasks in the process model. See the example pipeline configuration file (.gitlab-ci.yml) in Appendix A. Note that there are two stages:
stages:
- SimulinkPipelineGeneration
- SimulinkPipelineExecution
The first stage generates the pipeline for the second stage.
At this time, it is important to consider how to license the modeling and verification tools used in the pipeline. One approach is to use batch tokens. To use batch tokens, you must request one from Batch Licensing Pilot and include it in the variables section of your .yml file.
variables: MLM_LICENSE_TOKEN: "<MATLAB_BATCH_TOKEN>"
Not all products support batch tokens. In such cases, you may need to use a traditional license file (.lic). You can find more information on batch tokens here: MATLAB Batch Licensing Tokens. Note that it is best practice to avoid hard-coding tokens (and other credential-type information) into your .yml or Dockerfile. For a more secure approach, consider building your Docker image using environment variables.
Configuring GitLab CI/CD Pipelines
Configuring your GitLab project to manage jobs is straightforward. You first need to make sure that GitLab knows where to find your .yml file. Under Settings > CI/CD > General pipelines > CI/CD configuration file, provide the path to your project’s .yml file, ideally named “.gitlab-ic.yml” (Figure 1).
Next, you’ll need to create a new runner in GitLab. Doing this will provide a gitlab-runner-token that you will later use to register your Docker container to your GitLab runner instance (Figure 2).
Under Settings > CI/CD > Runners > New project runner, provide a tag and click Create Runner (Figure 3).
Once the runner is created, copy and save the runner authentication token (Figure 4).
Docker
Creating the Docker Image
This is arguably the most difficult part of the process, as you need to ensure that your Docker image not only has all the tooling dependencies installed, but is also licensed to use those tools. It’s also important to note that your Docker image can be one of any number of OS distributions. This example will be using Ubuntu 22.04.
Start by installing all the tooling needed to run your Simulink process model and GitLab runners. In your Dockerfile, pull a base MATLAB image:
FROM mathworks/matlab-deps:${MATLAB_DOCKER_RELEASE}
Subsequently, you’ll get right into installing some of the base dependencies required. Note that build variables will be discussed later. See the example Dockerfile in Appendix B for the full installation breakdown. The Dockerfile starts with installing dependencies for MPM, which will then be used to install your required MATLAB and Simulink products (except for Simulink Code Inspector™). Next, install dependencies for matlab-proxy. Once these are installed, use MPM to install your required tools and configure your licensing method. Note that you will also need to install gitlab-runner on your Docker image and register it using the gitlab-runner-token you saved from earlier. The snippet below shows how to install and register with desirable configuration options:
RUN curl -L
"https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash && \
sudo apt-get install --no-install-recommends --yes \
gitlab-runner && \
sudo apt-get clean && sudo apt-get autoremove && \
gitlab-runner start && \
sudo gitlab-runner register --non-interactive \
--url "<gitlab-url>" \
--token "${GITLAB_TOKEN}" \
--docker-image ${IMAGE_NAME} \
--executor "shell"
As shown in the Dockerfile, MPM can install all the required MATLAB and Simulink tools except for Simulink Code Inspector. For Simulink Code Inspector, you’ll need to download an .iso file from the MathWorks website and install it from that file. See the mpm install documentation for more information.
Now that your products are installed, you need to configure your licensing method. There are currently three methods available for licensing:
- Use a batch token: Install
matlab-batchto enable the use of MATLAB batch licensing tokens. Note that Polyspace Code Prover Server™ and Polyspace Bug Finder Server™ do not support batch token licensing, and that using Polyspace Code Prover™ and Polyspace Bug Finder™ for desktop in CI/CD environments breaks the software license agreement. To installmatlab-batch, add the following to your Dockerfile:RUN wget -q https://ssd.mathworks.com/supportfiles/ci/matlab-batch/v1/glnxa64/matlab-batch \ && sudo mv matlab-batch /usr/local/bin \ && sudo chmod +x /usr/local/bin/matlab-batch
- Use a license server: Specify the host and port of the machine that serves the network licenses if you want to bind in the license info as an environment variable. This is the preferred option for licensing. It is possible to build using the following:
--build-arg LICENSE_SERVER=27000@MyServerName
Alternatively, you could specify the license server directly in your Dockerfile using:
ENV MLM_LICENSE_FILE=27000@flexlm-server-name
- Use a license (.lic) file: Putting a license file directly into a container is strongly discouraged. However, you can alternatively mount a drive to your container that contains the required license files. When using a mounted drive for your license file, include one of the following lines in the example Dockerfile:
COPY ${PATH_TO_LICENSE} /opt/matlab/licenses/ADD ${PATH_TO_LICENSE} /opt/matlab/licenses/
One last thing about the installed dependencies: Some reports require a display to capture screenshots of models. Xvfb provides a virtual display for tasks such as padv.builtin.task.GenerateSDDReport, padv.builtin.task.GenerateSimulinkWebView, and padv.builtin.task.GenerateModelComparison. This should be installed as part of the MPM dependencies. Be sure that MPM is installed by the user who will be running the pipeline.
Build Arguments
In this example, the Dockerfile is configured to accept several build arguments. Build arguments can be valuable for maintaining one Dockerfile that can be used to build multiple images or for allowing users to pass in build information that may change over time or be undesirable to hard-code into the Dockerfile itself.
This Dockerfile contains the following build arguments (--build-arg):
# docker build --build-arg MATLAB_DOCKER_RELEASE=<matlab-docker-release> # --build-arg MATLAB_VERSION=<matlab-release> # --build-arg GITLAB_TOKEN=<gitlab-token> # --build-arg MATLAB_BATCH_TOKEN=<matlab-token> # --build-arg IMAGE_NAME=<image-image> # --build-arg PATH_TO_LICENSE=<path-to-license>
At the beginning of your Dockerfile, you can specify the default value for each argument in case a value is not provided to the build command:
ARG MATLAB_DOCKER_RELEASE=r2023b-ubuntu22.04 ARG MATLAB_VERSION=r2023b ARG MATLAB_BATCH_TOKEN="<USER>|TOKEN_ML|<TOKEN>" ARG GITLAB_TOKEN=<TOKEN> ARG IMAGE_NAME=matlab_image ARG PATH_TO_LICENSE=<PATH_TO_LICENSE>
After the FROM statement in your Dockerfile, you must declare the variables that will be used:
FROM mathworks/matlab-deps:${MATLAB_DOCKER_RELEASE}
ARG MATLAB_VERSION
ARG MATLAB_BATCH_TOKEN
ARG GITLAB_TOKEN
ARG IMAGE_NAME
ARG PATH_TO_LICENSE
Note in the snippet above that the MATLAB_DOCKER_RELEASE value is accessed by using the ${<var>} syntax.
Build Image and Launch Container
Now that your Dockerfile is ready, you can build your first container from the Docker terminal:
- From the Docker terminal, type cd into the Dockerfile’s location and verify:
> cd <path-to-dockerfile> > ls
- Build the Docker Image:
> docker build -t <image-name> -f <dockerfile-name>
If your Docker file uses build arguments, then your build command may look something more like:
> docker build --build-arg PATH_TO_LICENSE=<path-to-license> --build-arg GITLAB_TOKEN=<gitlab-runner-token> --build-arg MATLAB_BATCH_TOKEN="<USER>|TOKEN_ML|<TOKEN>" -t <image-name> -f <dockerfile-name>
Once the container is built, you can run it from Docker using the UI or from the terminal:
> docker run --name <container_name> -v <optional_volume> <image_name>:latest
Your Dockerfile already registered your image as your gitlab-runner. To confirm this, view the file /etc/gitlab-runner/config.toml within Docker and verify the correct runner is registered (Figure 5).
If the runner registration is incorrect or needs to be changed or restarted, use the following in the Docker image’s terminal to unregister the runner, and then restore the correct runner:
> sudo gitlab-runner unregister --token "<gitlab_token>" > sudo gitlab-runner register --non-interactive --url "<gitlab-url>" --token "<gitlab-runner-token>" --executor "shell"
Run GitLab Runner to Listen for Jobs
Now that the container is running and registered as your GitLab runner, you can start the gitlab-runner and begin listening for jobs (Figure 6). From the container’s terminal, start the runner using:
> sudo gitlab-runner run
There are many ways to kick off your pipeline, including scheduled builds, manual or on-demand builds, and builds based on new commits to your GitLab repository (Figure 7).
- Scheduled builds: In GitLab, use Build > Pipeline schedule to schedule when pipelines should be run.
- On commit builds: By default, GitLab will run the pipeline every time the repository is pushed to. To control how builds are triggered, you’ll need to modify the .yml file or add specific tags to commit messages. See Control how jobs run for more information.
- Manual builds: In GitLab, use Settings > CI/CD > Run pipeline to trigger manual builds. Settings > CI/CD > Pipeline trigger tokens contains information on how to use
curlto trigger builds from a terminal.
Once the pipeline has finished executing, results can be viewed and downloaded from the last job in your pipeline—Collect_Artifacts (Figure 8).
Testing and Troubleshooting the Docker Image
When setting up and configuring your Docker image, it is important to be able to test things out along the way. Here are a few helpful steps you can perform in the Docker container’s terminal to make sure things are working as expected:
- Verify the MATLAB install from the container’s terminal:
$ matlab-batch "disp('Hello, World.')"
- To generate certain reports (SDD), MATLAB needs a display. For this, you can use Xvfb. To test Xvfb:
$ sudo xvfb-run -e /dev/stdout -a matlab-batch "disp('Hello, World.')"
- Manually run the MATLAB pipeline:
- Clone the repo (you may be prompted for credentials—2FA requires access tokens, which can be found in GitLab by going to the left sidebar and selecting your avatar: Select Edit profile > Access Tokens > Add new token)
$ sudo git clone <gitlab-repo-url> <local-repo> $ sudo git clone https://<user>:<access_token>@<gitlab-repo-url> <local-repo>
- Change the directory into a MATLAB project:
$ cd <local-repo>/<project-path>
- Run the first stage of the pipeline:
$ sudo -E matlab-batch -logfile "MATLAB_LOG_FILE" -nodesktop "cp = openProject(pwd); padv.pipeline.generatePipeline( padv.pipeline.GitLabOptions(PipelineArchitecture = padv.pipeline.Architecture.SerialStagesGroupPerTask, Tags = 'docker-matlab', GeneratedYMLFileName = 'simulink_pipeline.yml', GeneratedPipelineDirectory = fullfile('derived','pipeline'), MatlabLaunchCmd = 'xvfb -a matlab-batch', AddBatchStartupOption = false, EnableArtifactCollection = 'always'));" - Verify generation of the
simulink_pipeline.ymlfile:$ cd derived $ ls
- Clone the repo (you may be prompted for credentials—2FA requires access tokens, which can be found in GitLab by going to the left sidebar and selecting your avatar: Select Edit profile > Access Tokens > Add new token)
Conclusion
The integration of CI/CD is paramount for maintaining high standards of quality, reliability, and compliance. CI/CD practices streamline the development process, enabling rapid and consistent delivery of updates while ensuring that all changes meet stringent certification requirements. This approach not only enhances productivity but also significantly reduces the risk of errors and noncompliance, which are critical in certified environments.
By applying the tools and practices discussed in this article, organizations should be able to set up a robust environment using Docker and GitLab Runner to create an efficient and cost-saving CI/CD pipeline. This pipeline should facilitate a streamlined, reliable, and compliant development lifecycle, and ultimately help to deliver high-quality certifiable systems with greater confidence and efficiency.
About the Author
Dalton L'Heureux is a senior consultant at MathWorks, where he supports engineers working on safety-critical and high-integrity systems. His focus includes helping teams apply tools for systems engineering, verification & validation, and code generation in applications such as DO-178C certification.
Prior to joining MathWorks, Dalton was a systems engineer at Rockwell Collins, where he became a subject matter expert in specification modeling and test case generation. His work contributed to cost-effective testing of flight software for aircraft including the Boeing 777X and Bombardier C-Series.
Dalton holds a BSE in aerospace engineering and an MSE in unmanned and autonomous systems engineering from Embry-Riddle Aeronautical University. Across his roles, Model-Based Design and MATLAB have been consistent themes in his approach to developing and verifying complex systems.
# Copyright 2023 - 2025 The MathWorks, Inc.
variables:
MATLAB_LOG_FILE: "MATLAB_Log_Output.txt"
GIT_SUBMODULE_STRATEGY: recursive
MLM_LICENSE_TOKEN: "<USER>|TOKEN_ML|<BATCHTOKEN>"
stages:
- SimulinkPipelineGeneration
- SimulinkPipelineExecution
# Do not change the name of the jobs in this pipeline
SimulinkPipelineGeneration:
stage: SimulinkPipelineGeneration
tags:
- docker-matlab
script:
# Open the project and generate the pipeline using appropriate options
sudo -E matlab-batch
-logfile "MATLAB_LOG_FILE"
-nodesktop
"cp = openProject(pwd);
padv.pipeline.generatePipeline(
padv.pipeline.GitLabOptions(
PipelineArchitecture = padv.pipeline.Architecture.SerialStagesGroupPerTask,
Tags = 'docker-matlab',
GeneratedYMLFileName = 'simulink_pipeline.yml',
GeneratedPipelineDirectory = fullfile('derived','pipeline'),
MatlabLaunchCmd = 'xvfb-run -a matlab-batch',
AddBatchStartupOption = false,
EnableArtifactCollection = 'always'));"
artifacts:
paths:
# This file is generated automatically by
# padv.pipeline.generatePipeline. Update this field if the
# name or location of the generated pipeline file is changed
- derived/pipeline
SimulinkPipelineExecution:
stage: SimulinkPipelineExecution
trigger:
include:
- artifact: derived/pipeline/simulink_pipeline.yml
job: SimulinkPipelineGeneration
strategy: depend
# Do not change the name of this variable
variables:
PADV_ROOT_PIPELINE_ID: $CI_PIPELINE_ID
# Copyright 2023 - 2025 The MathWorks, Inc.
# docker build --build-arg MATLAB_DOCKER_RELEASE=<matlab-docker-release>
# --build-arg MATLAB_VERSION=<matlab-release>
# --build-arg GITLAB_TOKEN=<gitlab-token>
# --build-arg MATLAB_BATCH_TOKEN=<matlab-token>
# --build-arg IMAGE_NAME=<image-image>
# -t <image-image>
# -f <dockerfile-name> .
# Example: $ docker build --build-arg PATH_TO_LICENSE=<path-to-license> --build-arg GITLAB_TOKEN=<gitlab-token> --build-arg MATLAB_BATCH_TOKEN="<USER>|TOKEN_ML|<TOKEN>" -t matlab_image -f matlab.Dockerfile .
# Note: It is best practice to pass the MATLAB Batch Token during execution
# time rather than at build time as shown here. The token was passed at
# build time here for simplicity purposes.
# To specify which MATLAB release to install in the container, edit the value of the MATLAB_RELEASE argument.
# Use lower case to specify the release, for example: ARG MATLAB_RELEASE=r2023b
ARG MATLAB_DOCKER_RELEASE=r2023b-ubuntu22.04
ARG MATLAB_VERSION=r2023b
ARG MATLAB_BATCH_TOKEN="<USER>|TOKEN_ML|<TOKEN>"
ARG GITLAB_TOKEN=<TOKEN>
ARG IMAGE_NAME=matlab_image
ARG PATH_TO_LICENSE=<PATH_TO_LICENSE>
# When you start the build stage, this Dockerfile by default uses the Ubuntu-based matlab-deps image.
# To check the available matlab-deps images, see: https://hub.docker.com/r/mathworks/matlab-deps
FROM mathworks/matlab-deps:${MATLAB_DOCKER_RELEASE}
# Declare the global argument to use at the current build stage
ARG MATLAB_VERSION
ARG MATLAB_BATCH_TOKEN
ARG GITLAB_TOKEN
ARG IMAGE_NAME
ARG PATH_TO_LICENSE
RUN sudo apt-get update && \
sudo apt-get install --no-install-recommends --yes \
curl && \
sudo apt-get clean && sudo apt-get autoremove
RUN curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash && \
sudo apt-get install --no-install-recommends --yes \
gitlab-runner && \
sudo apt-get clean && sudo apt-get autoremove && \
gitlab-runner start && \
sudo gitlab-runner register --non-interactive \
--url "https://external-git.mathworks.com/" \
--token "${GITLAB_TOKEN}" \
--docker-image ${IMAGE_NAME} \
--executor "shell"
# Install mpm dependencies
RUN export DEBIAN_FRONTEND=noninteractive && \
sudo apt-get update && \
sudo apt-get install --no-install-recommends --yes \
wget \
ca-certificates \
xvfb \
build-essential \
clang \
libopenblas-dev \
liblapacke-dev \
liblapack-dev \
libomp-dev \
unzip \
iproute2 \
git \
libeigen3-dev \
cmake \
psmisc && \
sudo apt-get clean && sudo apt-get autoremove
RUN sudo apt-get update && sudo apt-get install libunwind-dev -y && \
sudo apt-get clean && sudo apt-get autoremove
# Install dependencies for matlab-proxy
RUN DEBIAN_FRONTEND=noninteractive && \
sudo apt-get update && sudo apt-get install --no-install-recommends -y \
python3 \
python3-pip \
&& sudo apt-get clean \
&& sudo rm -rf /var/lib/apt/lists/*
RUN python3 -m pip install matlab-proxy
# Add "matlab_user" user and grant sudo permission.
RUN adduser --shell /bin/bash --disabled-password --gecos "" matlab_user && \
echo "matlab_user ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/matlab_user && \
chmod 0440 /etc/sudoers.d/matlab_user
# Set user and work directory
USER matlab_user
WORKDIR /home/matlab_user
# Run mpm to install MATLAB in the target location and delete the mpm installation afterwards
# Add toolboxes on --products line replacing spaces with _ aka Simulink_Test
# Note: Simulink_Code_Inspector is only supported by mpm when installing from an iso file:
RUN wget -q https://www.mathworks.com/mpm/glnxa64/mpm && \
chmod +x mpm && \
sudo ./mpm install \
--release=${MATLAB_VERSION} \
--destination=/opt/matlab \
--products MATLAB Simulink Stateflow \
Requirements_Toolbox \
Simulink_Check CI/CD_Automation_for_Simulink_Check Simulink_Design_Verifier \
Simulink_Test Simulink_Coverage \
MATLAB_Coder MATLAB_Compiler Simulink_Coder Simulink_Compiler Embedded_Coder \
Polyspace_Bug_Finder_Server Polyspace_Code_Prover_Server \
MATLAB_Report_Generator Simulink_Report_Generator \
DSP_System_Toolbox Simulink_3D_Animation Phased_Array_System_Toolbox \
Computer_Vision_Toolbox Image_Processing_Toolbox \
System_Identification_Toolbox Instrument_Control_Toolbox Aerospace_Toolbox \
Aerospace_Blockset Signal_Processing_Toolbox Symbolic_Math_Toolbox \
Automated_Driving_Toolbox DDS_Blockset Geoid_Data_for_Aerospace_Toolbox \
|| (echo "MPM Installation Failure. See below for more information:" && cat /tmp/mathworks_root.log && false) && \
sudo rm -rf mpm /tmp/mathworks_root.log && \
sudo ln -s /opt/matlab/bin/matlab /usr/local/bin/matlab
# One of the following 3 ways of configuring the license server to use must be
# uncommented.
# 1) BATCH TOKEN
# Install matlab-batch to enable the use of MATLAB batch licensing tokens.
RUN wget -q https://ssd.mathworks.com/supportfiles/ci/matlab-batch/v1/glnxa64/matlab-batch \
&& sudo mv matlab-batch /usr/local/bin \
&& sudo chmod +x /usr/local/bin/matlab-batch
# 2) LICENSE SERVER
#ARG LICENSE_SERVER
# Specify the host and port of the machine that serves the network licenses
# if you want to bind in the license info as an environment variable. This
# is the preferred option for licensing. It is either possible to build with
# Something like --build-arg LICENSE_SERVER=27000@MyServerName, alternatively
# you could specify the license server directly using
# ENV MLM_LICENSE_FILE=27000@flexlm-server-name
#ENV MLM_LICENSE_FILE=$LICENSE_SERVER
# 3) LICENSE FILE
# Alternatively, you can put a license file into the container.
# You should fill this file out with the details of the license
# server you want to use and uncomment the following line.
#COPY ${PATH_TO_LICENSE} /opt/matlab/licenses/
ADD ${PATH_TO_LICENSE} /opt/matlab/licenses/
ENV ENV="/home/matlab_user/.profile"
ENV BASH_ENV="/home/matlab_user/.profile"
ENV MLM_LICENSE_TOKEN=${MATLAB_BATCH_TOKEN}
ENTRYPOINT ["xvfb-run"]
CMD ["/bin/bash"]
Published 2025