Building U-Boot and `rootfs.uImage` with the Docker Build Environment
1. Overview
This application note shows how to build and flash U-Boot and the Linux rootfs.uImage for two i.MX RT 1170 boards -- IMXRT1170-EVK (NXP) and MaaXBoard-RT (Avnet) -- using the containerized build environment shipped under tools/docker/. The pre-built FDPIC cross-toolchain provided with the BSP release is used; no host-side toolchain installation is needed.
The container runs the same kernel / U-Boot / project Makefiles you would run on the host. The difference is purely environmental: every build host dependency (~30 packages) is baked into the image, and the toolchain at tools/<sdk-name>/ is reached via the /work bind mount.
2. Prerequisites
A Linux host with Docker Engine 20.10 or newer; the current user must be in the
dockergroup (or havesudoaccess to Docker).Two tarballs provided by Emcraft alongside the BSP release:
linux-cm-imxrt117x-3.3.2.tar.bz2-- the release tree.arm-buildroot-uclinuxfdpiceabi_sdk-buildroot.tar.gz-- the pre-built FDPIC cross-toolchain.
A USB cable to the board's UART debug port (for console) and to the board's USB OTG / SDP port (for flashing).
3. One-time setup
3.1. Install the BSP release
Standard BSP installation steps -- extract the release tarball, then extract the pre-built FDPIC toolchain into the release tree's tools/ directory (the container reads the toolchain via the /work bind mount, so it must be in place before any build):
$ tar -xjf linux-cm-imxrt117x-3.3.2.tar.bz2 -C ~
$ cd ~/linux-cm-imxrt117x-3.3.2
$ tar -xzf /path/to/arm-buildroot-uclinuxfdpiceabi_sdk-buildroot.tar.gz -C tools/The toolchain ends up at tools/arm-buildroot-uclinuxfdpiceabi_sdk-buildroot/, where ACTIVATE.sh looks for it. The rest of this AN is run from the release-tree root.
3.2. Build the Docker image (optional)
The image builds on first run.sh invocation if it's missing, so you can skip ahead to section 4. To pre-build (e.g. on a CI runner or before going offline):
$ ./tools/docker/run.sh --buildPulls Ubuntu 24.04, installs build dependencies, and tags the image emcraft/linux-cm-build:<version> -- where <version> is taken from _MANIFEST automatically.
The image is ~600 MB. Subsequent invocations of run.sh reuse it without rebuilding.
4. Building for IMXRT1170-EVK
Open an interactive shell in the container, targeting the IMXRT1170-EVK:
$ ./tools/docker/run.sh --mcu IMXRT117X_NXPEVK
================================================================
Emcraft Linux-CM build environment
Target: IMXRT117X_NXPEVK
SDK: tools/ (legacy)
Release: 3.3.2
...
================================================================
[IMXRT117X_NXPEVK] /home/.../linux-cm-imxrt117x-3.3.2$The Target: line and the [$MCU] prompt confirm the active environment. The SDK: line shows which toolchain is in use — tools/ (legacy) for the pre-built FDPIC SDK that ships with the release, or a Buildroot defconfig name when a Buildroot-built SDK is selected (see below).
Pass --sdk <buildroot_defconfig> alongside --mcu to point the container's CROSS_COMPILE and PATH at a Buildroot-built SDK (under buildroot/output_<defconfig-stem>/host/) instead of the prebuilt one in tools/. The Buildroot output dir is shared between host and container via the bind mount, so the SDK is built once and reused either way.
$ ./tools/docker/run.sh --mcu IMXRT117X_NXPEVK --sdk arm_cortexm_sdk_lite_defconfigIf neither --mcu nor --sdk is passed, the container inherits both from .activate-defaults at the BSP root, so a host-side source ACTIVATE.sh <MCU> <defconfig> carries through to the container automatically.
4.1. Build U-Boot
[IMXRT117X_NXPEVK] /work$ cd /work/u-boot
[IMXRT117X_NXPEVK] /work/u-boot$ make imxrt1170-evk-demo_defconfig
[IMXRT117X_NXPEVK] /work/u-boot$ makeProduces (under u-boot/):
Artifact | Purpose |
|---|---|
| first-stage bootloader (in FlexSPI) |
| main U-Boot image (in FlexSPI) |
4.2. Build project uImage
[IMXRT117X_NXPEVK] /work/u-boot$ cd /work/projects/rootfs
[IMXRT117X_NXPEVK] /work/projects/rootfs$ makeProduces projects/rootfs/rootfs.uImage -- a unified image in U-Boot's uImage format, containing the Linux kernel with the root filesystem.
Exit the shell (exit or Ctrl-D); the artifacts persist in the release tree on the host.
5. Building for MaaXBoard-RT
Same flow, different MCU and U-Boot defconfig:
$ ./tools/docker/run.sh --mcu MAAXBOARD_RTInside the container:
[MAAXBOARD_RT] /work$ cd /work/u-boot
[MAAXBOARD_RT] /work/u-boot$ make maaxboard-rt_defconfig
[MAAXBOARD_RT] /work/u-boot$ make
[MAAXBOARD_RT] /work/u-boot$ cd /work/projects/rootfs
[MAAXBOARD_RT] /work/projects/rootfs$ makeThe U-Boot build produces a combined flash.bin (SPL + main U-Boot) suitable for the MaaXBoard-RT HyperFlash; the kernel build produces projects/rootfs/rootfs.uImage for the MAAXBOARD_RT device tree.
6. Installing onto the board
imxrt_install.sh (shipped under tools/bin/) flashes SPL, U-Boot, and rootfs.uImage to the board's FlexSPI-managed memory via SDP-over-USB. It is normally run directly on the host, picking up blhost and sdphost from NXP's MCUXpresso Secure Provisioning Tool (SPT) or the legacy Flashloader installation. When neither of those is available on the host, the script also runs inside the container, where blhost and sdphost come from the Python spsdk package baked into the optional installer image variant.
Host udev rule (one-time setup). The i.MX RT bootloader and flashloader present as /dev/hidraw* nodes owned by root:root with mode 0600 by default. NXP's MCUXpresso SPT installer ships its own udev rule covering this; if SPT is not installed, or if you flash from inside the container (which runs as a non-privileged user), add the following rule on the host once:
$ sudo tee /etc/udev/rules.d/99-imxrt.rules <<'EOF'
# NXP / Freescale i.MX RT bootloader (ROM mode + flashloader mode)
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1fc9", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", MODE="0666"
EOF
$ sudo udevadm control --reload-rules
$ sudo udevadm triggerThe chip re-enumerates from ROM to flashloader mode mid-flash; both modes share vendor ID 0x1fc9, so a single rule covers the whole sequence. Replug the board after applying the rule.
Alternative: run the installer container under sudo:
$ sudo WITH_INSTALLER=1 ./tools/docker/run.sh --mcu IMXRT117X_NXPEVK imxrt_install.sh -aUse this for the install step only — building under sudo would leave root-owned artefacts in the bind-mounted release tree. Note that ACTIVATE.sh (sourced by the container's entrypoint) writes .activate-defaults at the BSP root: under sudo, it will overwrite the existing file with root ownership when --mcu / --sdk differ from the persisted selection, or create a new root-owned file if .activate-defaults is absent.
Connect the board's USB OTG / SDP port to the host.
Set the board's boot switch to Serial Download mode (refer to the board's user manual for the exact dip-switch positions).
Power-cycle the board.
Run the install script. Either:
(a) On the host -- recommended when SPT or Flashloader is installed. Pass the target as
MCU=<TARGET>so the script picks the right per-board config (without it, the default fromACTIVATE.shis used):# for IMXRT1170-EVK $ MCU=IMXRT117X_NXPEVK ./tools/bin/imxrt_install.sh -a # for MaaXBoard-RT $ MCU=MAAXBOARD_RT ./tools/bin/imxrt_install.sh -a(b) Inside the container -- when neither SPT nor Flashloader is installed on the host (Linux only):
# for IMXRT1170-EVK $ WITH_INSTALLER=1 ./tools/docker/run.sh --mcu IMXRT117X_NXPEVK imxrt_install.sh -a # for MaaXBoard-RT $ WITH_INSTALLER=1 ./tools/docker/run.sh --mcu MAAXBOARD_RT imxrt_install.sh -aThe first call builds the installer image variant on demand (
emcraft/linux-cm-build:<version>-installer, ~150 MB heavier than the default image).run.shbind-mounts the host/devso the i.MX RT mid-flash chip re-enumeration is visible inside the container. macOS / Windows / WSL2 hosts have no/dev/bus/usb, so this path is Linux-only.
-a flashes everything from the previous build (SPL, U-Boot, kernel uImage). Use -S, -U, -I individually for partial reflashing (imxrt_install.sh -h lists every option).
Set the board back to internal boot (boot from QSPI / HyperFlash) and power-cycle. The kernel banner on the UART confirms boot.
7. Notes
Switching MCU / SDK between runs --
--mcu <MCU>and--sdk <buildroot_defconfig>are per-invocation. Either can also be passed via env (export MCU=...,export BUILDROOT_DEFCONFIG=...) and the flag dropped. With no flags and no env, the container reads both from.activate-defaultsat the BSP root; with neither flag nor env nor file,ACTIVATE.sh's built-in default MCU applies and the SDK falls back to legacy (tools/).--sdkrequires--mcu(orMCU=...) — Buildroot defconfig is the second positional arg toACTIVATE.sh, so it can't be set without the first.One-shot builds -- use
run.shnon-interactively to skip the shell:$ ./tools/docker/run.sh --mcu IMXRT117X_NXPEVK make -C projects/rootfsWhere artifacts live -- everything is on the host filesystem under the release-tree root; the container only adds the build environment. Closing the container does not lose work.