Skip to main content

Uploading symbol files and deploying OTA updates from your CI system is a great way to automate the process of releasing new firmware versions. This guide will show you how to use the Memfault CLI to upload symbol files and deploy OTA updates from your CI system.


The Memfault CLI is used to upload symbol files and deploy OTA updates, once the firmware has been built. The steps are as follows:

  1. Build the firmware, generating a symbol file and an OTA payload. In Zephyr, this is typically a .elf file and a .bin file, respectively:

    • build/zephyr/zephyr.elf
    • build/zephyr/app_update.bin

    Memfault always recommends signing OTA artifacts, to protect devices from installing counterfeit/invalid firmware. Zephyr has built-in support for signed OTA images using MCUBoot.

  2. Use the Memfault CLI to upload the symbol file, with the upload-mcu-symbols command:

    memfault upload-mcu-symbols build/zephyr/zephyr.elf
  3. Use the Memfault CLI to create the Release and deploy the OTA update, with the upload-ota-payload and deploy-release commands:

    # note: this command both creates the Release, if it doesn't exist, and
    # uploads the OTA payload
    memfault upload-ota-payload \
    --hardware-version <hardware_version> \
    --software-type <software_type> \
    --software-version <software_version> \

    memfault deploy-release \
    --release-version <software_version> \
    --cohort <cohort>

GitHub Actions Example

Here's a full example using GitHub Actions to build + upload + deploy a firmware release. This example is using Zephyr, but the principles are the same for other RTOSes or bare-metal projects.

Set up some GitHub Actions secrets, for the Organization Auth Token and the Memfault Organization and Project (for private repos, those last two don't need to be secrets).

And this is the example workflow file:

# 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.
- main
- main


# run on tag creation

# these must be as returned by memfault_platform_get_device_info() in the
# firmware being built
SOFTWARE_TYPE: nrf91ns-fw

# this cohort will be the target for the release deployment
COHORT: auto-ota

runs-on: ubuntu-latest
# In a real implementation, it's likely a container would be used.
# See a worked example here:
- name: Checkout code
uses: actions/checkout@v4
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's 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\///')
VERSION=$(git describe --tags --abbrev=0)
VERSION="${VERSION}+$(git rev-parse --short HEAD)"
echo "ERROR: Cannot determine version"
exit 1

# 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 doesn't already have Python,
# you can install it with this GitHub Action
- name: Set up Python
id: python
uses: actions/setup-python@v4
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 ${{ }} \

memfault \
--org-token ${{ secrets.MEMFAULT_ORG_TOKEN }} \
--org ${{ secrets.MEMFAULT_ORG }} \
--project ${{ secrets.MEMFAULT_PROJECT }} deploy-release \
--release-version ${{ }} \
--cohort ${{ env.COHORT }}