Automatically upload symbols and deploy releases from CI
Uploading symbol files and deploying OTA updates from your CI system is a useful way to automate the release of new firmware versions. This guide shows you how to use the Memfault CLI to upload symbol files and deploy OTA updates from your CI system.
Methodology
The Memfault CLI uploads symbol files and deploys OTA updates after the firmware is built. The steps are as follows:
-
Build the firmware, generating a symbol file and an OTA payload. In Zephyr, this is typically a
.elffile and a.binfile, respectively:build/zephyr/zephyr.elfbuild/zephyr/app_update.bin
Memfault recommends signing OTA artifacts to protect devices from installing counterfeit or invalid firmware. Zephyr has built-in support for signed OTA images using MCUBoot.
-
Use the Memfault CLI to upload the symbol file with the
upload-mcu-symbolscommand:memfault upload-mcu-symbols build/zephyr/zephyr.elf -
Use the Memfault CLI to create the release and deploy the OTA update with the
upload-ota-payloadanddeploy-releasecommands:# note: this command both creates the release, if it does not exist, and# uploads the OTA payloadmemfault upload-ota-payload \--hardware-version <hardware_version> \--software-type <software_type> \--software-version <software_version> \build/zephyr/app_update.binmemfault deploy-release \--release-version <software_version> \--cohort <cohort>
GitHub Action for uploading symbols
Memfault provides a GitHub Action for uploading symbol files. The following example shows how to use it:
- name: Upload symbol file to Memfault
uses: memfault/github-action-upload-symbols@v1
with:
MEMFAULT_ORG_TOKEN: ${{ secrets.MEMFAULT_ORG_TOKEN }}
MEMFAULT_ORG_SLUG: ${{ secrets.MEMFAULT_ORG_SLUG }}
MEMFAULT_PROJECT_SLUG: ${{ secrets.MEMFAULT_PROJECT_SLUG }}
symbol_file: symbols.elf
For the action source, see memfault/github-action-upload-symbols.
GitHub Actions example: upload symbols and deploy a release
The following example uses GitHub Actions to build, upload, and deploy a firmware release. The example uses Zephyr, but the same principles apply to other real-time operating systems or bare-metal projects.
Set up GitHub Actions secrets for the Memfault organization and project, and for the Organization Auth Token. For private repositories, the organization and project values do not need to be secrets.

The following workflow file shows the example:
# Depending on your project needs, you can have the build + upload trigger on
# different conditions. This example only uploads + deploys when a tag is
# created.
on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:
# run on tag creation
create:
tags:
env:
# these must be as returned by memfault_platform_get_device_info() in the
# firmware being built
HARDWARE_VERSION: mp
SOFTWARE_TYPE: nrf91ns-fw
# this cohort will be the target for the release deployment
COHORT: auto-ota
jobs:
build:
runs-on: ubuntu-latest
# In a real implementation, you will likely use a container.
# See a worked example here:
# https://interrupt.memfault.com/blog/ncs-github-actions
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build firmware
# need to run as bash for '== "refs/tags/"*' to work
shell: bash
id: build
run: |
# create a version string. if it is a tag build, use the tag name,
# otherwise use the most recent tag + git short sha
cd nrfconnect-ci-app
if [ -n "$GITHUB_REF" ]; then
if [[ "$GITHUB_REF" == "refs/tags/"* ]]; then
VERSION=$(echo $GITHUB_REF | sed 's/refs\/tags\///')
else
VERSION=$(git describe --tags --abbrev=0)
VERSION="${VERSION}+$(git rev-parse --short HEAD)"
fi
else
echo "ERROR: Cannot determine version"
exit 1
fi
# save the version string to this step's output
echo "version=${VERSION}" > ${GITHUB_OUTPUT}
<your build commands here, i.e. west build>
# if the Docker image used for the build does not already have Python,
# you can install it with this GitHub Action
- name: Set up Python
id: python
uses: actions/setup-python@v4
with:
python-version: 3.10
- name: Upload payload and deploy release
if: github.event_name == 'create' && github.event.ref_type == 'tag'
run: |
# install the memfault CLI
pip install memfault-cli==1.0.6
# upload the symbol file
memfault \
--org-token ${{ secrets.MEMFAULT_ORG_TOKEN }} \
--org ${{ secrets.MEMFAULT_ORG }} \
--project ${{ secrets.MEMFAULT_PROJECT }} \
upload-mcu-symbols build/zephyr/zephyr.elf
# create the release and upload the OTA payload. the version is from
# the build step
memfault \
--org-token ${{ secrets.MEMFAULT_ORG_TOKEN }} \
--org ${{ secrets.MEMFAULT_ORG }} \
--project ${{ secrets.MEMFAULT_PROJECT }} upload-ota-payload \
--hardware-version ${{ env.HARDWARE_VERSION }} \
--software-type ${{ env.SOFTWARE_TYPE }} \
--software-version ${{ steps.build.outputs.version }} \
build/zephyr/app_update.bin
memfault \
--org-token ${{ secrets.MEMFAULT_ORG_TOKEN }} \
--org ${{ secrets.MEMFAULT_ORG }} \
--project ${{ secrets.MEMFAULT_PROJECT }} deploy-release \
--release-version ${{ steps.build.outputs.version }} \
--cohort ${{ env.COHORT }}