Persistent App

Description

The Persistent App Update Module installs files to a user-defined directory on the persistent data partition, ensuring deployments survive rootfs A/B updates.

This module uses the data-partition software filesystem namespace, which means version tracking in mender show-provides persists across system updates. When a rootfs update clears rootfs-image.* provides, the data-partition.* provides remain intact.

The module supports rollback by maintaining backups on the same persistent partition.

Example use-cases:

  • Deploy application files that must persist across OS updates
  • Deploy configuration bundles to /data
  • Deploy container images, ML models, or other large files to persistent storage

For a detailed tutorial on persistent update modules, visit Mender Hub.

Specification

Module name persistent-app
Supports rollback yes
Requires restart no
Artifact generation script yes
Full system updater no
Source code Update Module, Artifact Generator

Install the Update Module

Download the latest version of this Update Module by running:

mkdir -p /usr/share/mender/modules/v3 && wget -P /usr/share/mender/modules/v3 https://raw.githubusercontent.com/mendersoftware/mender-update-modules/master/persistent-app/module/persistent-app && chmod +x /usr/share/mender/modules/v3/persistent-app

Create artifact

To download persistent-app-artifact-gen, run the following:

wget https://raw.githubusercontent.com/mendersoftware/mender-update-modules/master/persistent-app/module-artifact-gen/persistent-app-artifact-gen && chmod +x persistent-app-artifact-gen

Generate Mender Artifacts using the following command:

ARTIFACT_NAME="myapp-v1.0.0"
DEVICE_TYPE="raspberrypi4"
DEST_DIR="/data/myapp"
SOFTWARE_NAME="myapp"
SOFTWARE_VERSION="1.0.0"
OUTPUT_PATH="myapp-v1.0.0.mender"
./persistent-app-artifact-gen \
    -n ${ARTIFACT_NAME} \
    -t ${DEVICE_TYPE} \
    -d ${DEST_DIR} \
    --software-name ${SOFTWARE_NAME} \
    --software-version ${SOFTWARE_VERSION} \
    -o ${OUTPUT_PATH} \
    ./payload/

The artifact generator automatically includes --software-filesystem data-partition to ensure version tracking survives rootfs updates.

Note on directory structure: The artifact generator flattens all input files into a single directory. If you pass a directory like ./payload/ containing bin/app and etc/config.json, both files end up directly in the destination directory (e.g., /data/myapp/app and /data/myapp/config.json), not in subdirectories. This module is intended as a simple example; for complex directory hierarchies, consider using the dir-overlay module or adapting this module to your needs.

After deploying, verify with:

mender show-provides | grep data-partition
# Output: data-partition.myapp.version=1.0.0

How it works

  1. ArtifactInstall: The module backs up any existing content at the destination directory to <dest_dir>.backup, then copies the new files.

  2. ArtifactCommit: On successful deployment, the backup is removed.

  3. ArtifactRollback: If the deployment fails or is rejected, the backup is restored.

  4. Persistence: Both the deployed files and the backup reside on the data partition (typically /data), which is not affected by rootfs A/B updates.

Full explanation and tutorial

Understanding the Persistence Mechanism

This section explains the underlying concepts that make persistent deployments work, followed by Yocto integration, verification testing, and guidance for building custom variants.

The Persistence Problem

Mender uses an A/B partition scheme for rootfs updates:

┌─────────────────────────────────────────────────────────────┐
│  Boot                                                       │
├────────────────────────┬────────────────────────────────────┤
│  rootfs A (active)     │  rootfs B (inactive)               │
│  /usr, /opt, /etc...   │  (next update goes here)           │
├────────────────────────┴────────────────────────────────────┤
│  data partition (/data)                                     │
│  Survives all updates                                       │
└─────────────────────────────────────────────────────────────┘

When a rootfs update arrives, Mender writes it to the inactive partition and switches boot targets. Everything on the old rootfs becomes unreachable. If an update module installed files to /opt/myapp/, those files are gone.

The data partition is different. It’s never overwritten by system updates—that’s where Mender stores its database, logs, and where applications can persist data across updates.

Software Versioning Namespaces

Mender tracks installed software through “provides” entries in its database. When you deploy an artifact, it records what version was installed. The problem: rootfs updates clear most provides entries to reflect that the entire system has changed.

The key is the --software-filesystem flag when creating artifacts:

# Default behavior - provides get cleared by rootfs updates
mender-artifact write module-image --type myapp ...
# Creates: rootfs-image.myapp.version (CLEARED on rootfs update)

# Persistent behavior - provides survive rootfs updates
mender-artifact write module-image --type persistent-app \
    --software-filesystem data-partition ...
# Creates: data-partition.myapp.version (SURVIVES rootfs update)

Rootfs updates only clear provides matching rootfs-image.*. By using data-partition as the filesystem namespace, version tracking survives system updates.

The artifact generator automatically includes this flag, so you get the correct behavior without remembering to add it manually.

Installing via Yocto

For embedded Linux builds, create a recipe to install the module. In your layer:

recipes-mender/mender-persistent-app-module/mender-persistent-app-module.bb:

SUMMARY = "Mender persistent-app update module"
DESCRIPTION = "Update module for deploying files to the persistent data partition"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"

SRC_URI = "https://raw.githubusercontent.com/mendersoftware/mender-update-modules/master/persistent-app/module/persistent-app"
SRC_URI[sha256sum] = "${@bb.fetch2.get_checksum('sha256', d)}"

S = "${WORKDIR}"

do_install() {
    install -d ${D}${datadir}/mender/modules/v3
    install -m 0755 ${WORKDIR}/persistent-app ${D}${datadir}/mender/modules/v3/persistent-app
}

FILES:${PN} = "${datadir}/mender/modules/v3/persistent-app"

Add to your image:

IMAGE_INSTALL:append = " mender-persistent-app-module"

Alternatively, for quick testing during development, fetch the module directly:

# In local.conf
do_fetch_persistent_app() {
    install -d ${IMAGE_ROOTFS}${datadir}/mender/modules/v3
    wget -O ${IMAGE_ROOTFS}${datadir}/mender/modules/v3/persistent-app \
        https://raw.githubusercontent.com/mendersoftware/mender-update-modules/master/persistent-app/module/persistent-app
    chmod 0755 ${IMAGE_ROOTFS}${datadir}/mender/modules/v3/persistent-app
}
ROOTFS_POSTPROCESS_COMMAND += "do_fetch_persistent_app;"

Verifying Persistence Across Rootfs Updates

This test proves that deployed applications survive a rootfs update.

Step 1: Deploy the application

Create and deploy a test artifact:

mkdir -p payload
echo "version=1.0.0" > payload/version.txt
echo "My persistent application" > payload/readme.txt

./persistent-app-artifact-gen \
    -n myapp-v1.0.0 \
    -t your-device-type \
    -d /data/myapp \
    --software-name myapp \
    --software-version 1.0.0 \
    -o myapp-v1.0.0.mender \
    payload/

Transfer and install on the device:

mender install myapp-v1.0.0.mender
mender commit

Step 2: Record the current state

# Note the current rootfs version
mender show-provides | grep rootfs-image.version

# Verify app is installed
cat /data/myapp/version.txt

# Check provides include the data-partition namespace
mender show-provides | grep data-partition
# Output: data-partition.myapp.version=1.0.0

Step 3: Deploy a rootfs update

Build or obtain a rootfs update artifact and deploy it:

mender install core-image-full-cmdline-yourdevice.mender
# Device reboots...
mender commit

Step 4: Verify persistence

After reboot:

# Application files should still exist
cat /data/myapp/version.txt
# Output: version=1.0.0

# Provides should be preserved
mender show-provides

The output should show:

data-partition.myapp.version=1.0.0
rootfs-image.version=<new-rootfs-version>

The rootfs-image.version changed (new OS version), but data-partition.myapp.version survived. The rootfs update cleared rootfs-image.* provides but left data-partition.* intact.

Rollback Testing

Verify rollback works correctly:

# Create and install v2
./persistent-app-artifact-gen \
    -n myapp-v2.0.0 \
    -t your-device-type \
    -d /data/myapp \
    --software-name myapp \
    --software-version 2.0.0 \
    -o myapp-v2.0.0.mender \
    payload-v2/

mender install myapp-v2.0.0.mender

# Verify new version before committing
cat /data/myapp/version.txt
# Output: version=2.0.0

# Trigger rollback
mender rollback

# Verify original version restored
cat /data/myapp/version.txt
# Output: version=1.0.0

Building Your Own Variant

For specialized use cases, build a custom variant using persistent-app as the starting point.

Post-install actions — restart services, reload configuration, or run migration scripts:

ArtifactInstall)
    # ... existing install logic ...

    # Custom: restart the application
    systemctl restart myapp.service
    ;;

Validation before commit — verify the deployment works before making it permanent:

ArtifactCommit)
    # Custom: verify the app is healthy
    if ! curl -s http://localhost:8080/health | grep -q "ok"; then
        log "Health check failed"
        exit 1
    fi
    # ... existing commit logic ...
    ;;

Different backup strategies — for large deployments, consider hard links or rsync instead of full copies.

Nested directory structures — the default artifact generator flattens files. For complex directory trees, modify the generator to use tar (see dir-overlay module for this pattern).

When building a custom variant:

  1. Copy the module as your starting point
  2. Rename it to reflect your use case (e.g., myapp-deploy)
  3. Modify the artifact generator to match
  4. Keep the --software-filesystem data-partition flag to maintain persistence

Troubleshooting

Module not found after rootfs update

The update module lives on the rootfs at /usr/share/mender/modules/v3/persistent-app. If a rootfs update doesn’t include the module, it won’t be present even though deployed files remain on /data.

Solution: Include the module in all images that need to deploy or manage persistent applications.

Provides cleared unexpectedly

If mender show-provides doesn’t show data-partition.* entries after a rootfs update:

  1. Verify the artifact was created with --software-filesystem data-partition
  2. Check with mender-artifact read your.mender — the Provides section should show data-partition. prefix
  3. The artifact generator includes this automatically; if using manual commands, ensure the flag is present

Permission denied on /data

The data partition must be mounted read-write:

mount | grep data

Standard Mender partition layouts mount /data as writable. If permissions are wrong, check fstab or systemd mount units.

Files missing after rootfs update

If files are missing from the destination directory:

  1. Verify the destination starts with /data/
  2. Check that /data is the persistent partition, not a tmpfs
  3. Ensure the backup wasn’t restored due to a failed commit

mender show-provides shows nothing

The Mender database at /var/lib/mender/ should be a symlink to /data/mender/:

ls -la /var/lib/mender

If this symlink is broken or points to rootfs storage, provides won’t survive reboots.

Extended Use Cases

The module handles these scenarios directly:

  • Configuration bundles: Configs that persist across OS updates but remain deployable and rollbackable
  • Static assets: Web assets, firmware blobs, or files independent of the OS
  • Version markers: Version files for applications that self-update but need Mender tracking

For complex scenarios, extend the pattern:

  • Docker containers: Run docker load after copying image tarballs, or docker-compose up after deploying compose files
  • Machine learning models: Add validation that loads the model and runs test inference before committing
  • Database migrations: Include migration scripts in ArtifactInstall with rollback support

The key principles:

  1. Deploy to /data/ for persistence
  2. Keep backups on /data/ for rollback
  3. Use --software-filesystem data-partition for correct provides namespace
  4. Include the module in all rootfs images that need it