# Multi-File Application Bundle Update Module

**URL:** <https://hub.mender.io/t/multi-file-application-bundle-update-module/8176>\
**Category:** Update Modules\
**Created:** [January 23, 2026, 9:41am UTC](https://hub.mender.io/t/multi-file-application-bundle-update-module/8176 "2026-01-23T09:41:17Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![TheYoctoJester](https://yyz2.discourse-cdn.com/flex036/user_avatar/hub.mender.io/theyoctojester/32/1444_2.png) [@TheYoctoJester](https://hub.mender.io/u/TheYoctoJester)\
**Post date:** [January 23, 2026, 9:41am UTC](https://hub.mender.io/t/multi-file-application-bundle-update-module/8176/1 "2026-01-23T09:41:18Z")

</div>

Applications rarely consist of a single file. A typical deployment might include binaries, configuration files, assets, and supporting scripts that must be deployed together to maintain consistency. While Mender provides several ready-made Update Modules for common scenarios, the need for a custom solution arises when deploying application bundles where files have specific destination paths and require atomic rollback.

This tutorial demonstrates how to create a streaming Update Module that accepts multiple payload files, deploys them according to a manifest, and provides automatic rollback on failure.

## Prerequisites

- A device running Mender client v4.x or v5.x (for v3.x, replace `mender-update` with `mender` in commands below)
- `mender-artifact` tool installed on your development machine
- `jq` installed on the target device

## Understanding the Approach

Mender’s Update Module interface provides a `stream-next` mechanism for consuming payload files one at a time. Each call to `cat stream-next` returns the path to the next file in the artifact, or an empty string when no more files remain. Combined with artifact metadata (passed via `-m` during artifact creation), this allows the Update Module to know what files to expect and where to place them.

The deployment flow operates in distinct phases:

1. **Download** : Stream all files to a staging area
2. **ArtifactInstall** : Backup existing files, deploy staged files to final locations
3. **ArtifactCommit** : Remove backup (update successful)
4. **ArtifactRollback** : Restore backup if installation fails

## The Update Module

Install this script as `/usr/share/mender/modules/v3/app-bundle` on your target device:

```bash
#!/bin/sh
set -ue

STATE="$1"
FILES="$2"

WORK_DIR="/tmp/mender-app-bundle"
STAGING_DIR="${WORK_DIR}/staging"
BACKUP_DIR="${WORK_DIR}/backup"
MANIFEST="${FILES}/header/meta-data"

case "$STATE" in
    NeedsArtifactReboot)
        echo "No"
        ;;

    SupportsRollback)
        echo "Yes"
        ;;

    Download)
        rm -rf "$STAGING_DIR"
        mkdir -p "$STAGING_DIR"

        jq -r '.files[].name' "$MANIFEST" | while read -r name; do
            file="$(cat stream-next)"
            [-n "$file"] || { echo "Stream ended early" >&2; exit 1; }
            cat "$file" > "${STAGING_DIR}/${name}"
        done

        [-z "$(cat stream-next)"] || { echo "Unexpected extra files" >&2; exit 1; }
        ;;

    ArtifactInstall)
        base_path=$(jq -r '.base_path' "$MANIFEST")

        # Backup existing files
        rm -rf "$BACKUP_DIR"
        mkdir -p "$BACKUP_DIR"
        jq -r '.files[].dest' "$MANIFEST" | while read -r dest; do
            [-f "${base_path}/${dest}"] || continue
            mkdir -p "$(dirname "${BACKUP_DIR}/${dest}")"
            cp -a "${base_path}/${dest}" "${BACKUP_DIR}/${dest}"
        done
        cp "$MANIFEST" "${BACKUP_DIR}/manifest.json"
        echo "$base_path" > "${BACKUP_DIR}/base_path"

        # Deploy new files
        mkdir -p "$base_path"
        jq -r '.files[] | "\(.name)\t\(.dest)\t\(.mode // "")"' "$MANIFEST" | \
        while IFS='	' read -r name dest mode; do
            mkdir -p "$(dirname "${base_path}/${dest}")"
            cp "${STAGING_DIR}/${name}" "${base_path}/${dest}"
            [-z "$mode"] || chmod "$mode" "${base_path}/${dest}"
        done
        ;;

    ArtifactCommit)
        rm -rf "$BACKUP_DIR"
        ;;

    ArtifactRollback)
        [-d "$BACKUP_DIR"] || exit 0
        base_path=$(cat "${BACKUP_DIR}/base_path")

        jq -r '.files[].dest' "${BACKUP_DIR}/manifest.json" | while read -r dest; do
            if [-f "${BACKUP_DIR}/${dest}"]; then
                mkdir -p "$(dirname "${base_path}/${dest}")"
                cp -a "${BACKUP_DIR}/${dest}" "${base_path}/${dest}"
            else
                rm -f "${base_path}/${dest}"
            fi
        done
        ;;

    Cleanup)
        rm -rf "$STAGING_DIR"
        ;;
esac

exit 0

```

The critical streaming pattern appears in the `Download` state. The manifest tells us how many files to expect, and `stream-next` provides them in order:

```bash
file="$(cat stream-next)" # Get path to next payload file
cat "$file" > destination # Stream content to staging

```

## The Manifest Format

The manifest defines where files should be installed. Pass it to `mender-artifact` using the `-m` flag, which embeds it in the artifact’s metadata:

```json
{
  "base_path": "/opt/myapp",
  "files": [
    {"name": "myapp", "dest": "myapp", "mode": "0755"},
    {"name": "myapp.conf", "dest": "myapp.conf", "mode": "0644"}
  ]
}

```

The `name` field identifies the source file in the bundle directory, while `dest` specifies the path relative to `base_path`. The optional `mode` field sets file permissions after deployment.

File order in the manifest must match the order of `-f` arguments when creating the artifact, as `stream-next` delivers files in artifact order.

## Creating Artifacts

Structure your application bundle (you can use dummy text files as `myapp` and `myapp.conf` for testing):

```auto
my-bundle/
├── manifest.json
├── myapp
└── myapp.conf

```

Generate the artifact with `mender-artifact`:

```bash
mender-artifact write module-image \
    -T app-bundle \
    -n "myapp-v1.0" \
    -t <device-type> \
    -o myapp-v1.0.mender \
    -m my-bundle/manifest.json \
    -f my-bundle/myapp \
    -f my-bundle/myapp.conf

```

## Deployment

Install the Update Module to `/usr/share/mender/modules/v3/app-bundle` on your device. From there, standard Mender deployment applies: upload the artifact to Hosted Mender or your self-hosted server and deploy to your device fleet, or use standalone mode with `mender-update install`.

## Adapting for Your Use Case

The manifest-driven approach allows customization without modifying the Update Module itself - simply change `base_path` and `dest` fields in the manifest.

For service management, extend `ArtifactInstall` to stop services before deployment and restart them afterward:

```bash
ArtifactInstall)
    systemctl stop myapp || true
    # ... deployment code ...
    systemctl start myapp
    ;;

```

## Conclusion

This Update Module demonstrates how Mender’s `stream-next` mechanism handles multi-file payloads while maintaining rollback guarantees. The manifest-driven design keeps the Update Module generic, with deployment specifics defined at artifact creation time.
