Overview
The SIMATIC IOT2050 is Siemens' industrial IoT gateway built on the TI ARM64 (Sitara) platform. The device ships in two hardware variants — IOT2050 Basic (1 GB RAM, 4 GB eMMC) and IOT2050 Advanced (2 GB RAM, 16/32 GB eMMC) — and is supported by the open-source meta-iot2050 build layer, which produces a Debian-based Ubuntu ARM64 image using the Isar (Image Creation and Assembly) build framework rather than classic Yocto/BitBake packaging.
This reference covers three production-proven strategies for pre-integrating a metrics stack (Telegraf + InfluxDB + Grafana) and a container runtime (Docker) into a redistributable IOT2050 image so the end customer can flash a single USB stick and run the device with no further shell work:
- Golden Image — install on hardware, capture the on-disk state, re-flash it as the shipping image.
-
Isar Debian Layer — describe every install step in a reproducible recipe using
dpkg-prebuiltandIMAGE_INSTALL_append. - First-Boot Service — ship a small bootstrap service that fetches a setup script from a known server and runs it once after the first-boot filesystem resize.
Each method is evaluated against reproducibility, build-cycle time, and operator complexity.
Prerequisites
| Item | Value / Version |
|---|---|
| Host OS (build) | Debian 11 (bullseye) or Ubuntu 20.04 LTS, 64-bit |
| Disk on host | ≥ 80 GB free (Isar cache + sstate) |
| RAM on host | ≥ 8 GB (16 GB recommended for parallel build) |
| Docker on host | Optional but recommended for host-side testing of the Telegraf container |
| meta-iot2050 branch |
jan/ubuntu for the Ubuntu ARM64 image |
| Isar | Submodule pinned to a release tag matching meta-iot2050
|
| Target IOT2050 firmware | Example Image ≥ V1.2.x (matches BSP supplied with the layer) |
| Telegraf | 1.27.x ARM64 static binary or upstream telegraf deb from InfluxData repo |
DEPLOY_APT_REPO / APT_REPO in your image recipe.
Method 1: Golden Image (fastest path)
The Golden Image approach is appropriate when reproducibility is secondary to time-to-delivery. You treat one already-flashed IOT2050 as the canonical device, freeze its software state, and re-deploy that exact image to every other unit.
Workflow
- Flash the upstream
iot2050-image-example-ubuntu.wicfrommeta-iot2050to the IOT2050 Advanced using the Siemens DP image tool orbalenaEtcher. - Insert the device, expand the rootfs on first boot, and confirm
resize2fsran cleanly:
df -h /
systemctl is-system-running
- Install Docker and Telegraf directly on the device as outlined in Section 4.
- Enable and start the services, validate metrics ingestion, then power down cleanly.
- Pull the eMMC with
ddto a remote host (e.g.dd if=/dev/mmcblk0 | ssh user@host "dd of=iot2050-golden-$(date +%F).img"). - Shrink the image with
pishrinkorimage-shrink.sh, then re-flash onto other units with the Siemens DP Image tool.
truncate -s 0 /etc/machine-id; systemd-machine-id-setup; rm /etc/ssh/ssh_host_* on the target box or use a post-flash hook before declaring the device production-ready.
Method 2: Isar Debian Layer (reproducible, recommended)
Method 2 produces a deterministic image by declaring the desired software state inside BitBake/Isar recipes. The build runs in a chroot, but the recipes are themselves plain Debian packaging, so you can ship pre-built .deb files that you have already validated on a running IOT2050.
Layer layout
meta-mycustomer/
├── recipes-app/
│ ├── telegraf/
│ │ └── telegraf_1.27.4.bb
│ ├── prebuilt-deb/
│ │ └── my-setup_1.0.0.bb
│ └── docker-ce/
│ └── docker-ce_24.0.bb
├── recipes-core/
│ └── images/
│ └── iot2050-image-customer.bbappend
└── conf/
└── layer.conf
conf/layer.conf
BBPATH .= ":${LAYERDIR}"
BBFILE_COLLECTIONS += "mycustomer"
BBFILE_PATTERN_mycustomer = "^${LAYERDIR}/"
BBFILE_PRIORITY_mycustomer = "10"
LAYERSERIES_COMPAT_mycustomer = "kirkstone"
LAYERDEPENDS_mycustomer = "iot2050 isar"
recipes-core/images/iot2050-image-customer.bbappend
inherit image
IMAGE_INSTALL += " \
telegraf \
docker-ce \
my-setup \
prebuilt-deb-example \
"
IMAGE_INSTALL_append = " curl ca-certificates systemd"
The IMAGE_INSTALL variable is the single source of truth for the runtime package set. The jan/ubuntu branch of meta-iot2050 ships a base recipe at recipes-core/images/iot2050-image-example.bb that you append to rather than fork.
Including a prebuilt .deb with dpkg-prebuilt
Use the dpkg-prebuilt class from Isar to wrap an external Debian package without rebuilding it from source. The following recipe mirrors the pattern in the upstream meta-isar example:
# recipes-app/prebuilt-deb/prebuilt-deb-example_1.0.0.bb
# SPDX-License-Identifier: MIT
inherit dpkg-prebuilt
SRC_URI = "https://artifacts.example.com/debs/example-prebuilt_1.0.0-0_all.deb"
SRC_URI[sha256sum] = "REPLACE_WITH_REAL_SHA256"
PROVIDES += "example-prebuilt"
RPROVIDES_${PN} += "example-prebuilt"
Place the recipe in recipes-app/prebuilt-deb/ and add example-prebuilt to IMAGE_INSTALL. Isar will fetch the deb, place it in the image's apt pool, and install it during image assembly. The build is fully reproducible: every package, every hash, every file is declared in the recipe.
Including systemd-enabled .debs
Isar's image assembly uses a chroot environment, not a running systemd --system instance. A .deb postinst that calls systemctl enable foo.service will fail with Failed to connect to bus during the build. Two robust patterns exist:
-
Ship a systemd unit but enable it via a recipe hook. Drop the unit file under
/lib/systemd/system/foo.servicefrom the .deb, and add the unit to a startup symlink from an Isar recipe:
inherit dpkg-prebuilt
do_install_append() {
install -d ${D}/etc/systemd/system/multi-user.target.wants
ln -sf /lib/systemd/system/foo.service \
${D}/etc/systemd/system/multi-user.target.wants/foo.service
}
-
Use an enable-presets file. Isar honors
/lib/systemd/system-preset/and will enable matching units at first boot through the preset mechanism, which is the same code path used bysystemctl preset-all.
# In your .deb, ship:
# /lib/systemd/system-preset/90-customer.preset
enable foo.service
enable bar.service
Method 3: First-Boot Service (most flexible)
This method keeps the image lean and pushes configuration to runtime. A small systemd unit, enabled in the base image, runs once on first boot, fetches a setup script from a known URL, and exits. The setup script can install anything you need — including debs that ship their own systemd units.
Drop-in service file (shipped inside the image)
# /lib/systemd/system/iot2050-firstboot.service
[Unit]
Description=IOT2050 First-Boot Provisioning
ConditionPathExists=/var/lib/iot2050-firstboot.lock
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/iot2050-firstboot.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Bootstrap script (also shipped in the image)
#!/bin/sh
# /usr/local/bin/iot2050-firstboot.sh
set -e
LOCK=/var/lib/iot2050-firstboot.lock
URL="https://setup.example.com/iot2050/setup.sh"
curl -fsSL "$URL" -o /tmp/setup.sh
sh /tmp/setup.sh
touch "$LOCK"
systemctl disable iot2050-firstboot.service
iot2050-resize-fs.service before any network-dependent unit, expanding the rootfs to fill the eMMC. Order your custom service After= iot2050-resize-fs.service and network-online.target to ensure the disk is full size and the network is up before setup runs. Add the dependency in the unit file: After=iot2050-resize-fs.service network-online.target.
Enable the service from the image recipe
# In your b.bbappend or custom image recipe:
SYSTEMD_SERVICE_${PN} += "iot2050-firstboot.service"
SYSTEMD_AUTO_ENABLE_${PN} = "enable"
Telegraf Configuration
Telegraf collects system, Docker, and (optionally) Modbus/OPC UA metrics and pushes them to InfluxDB, Prometheus, MQTT, or any other supported output. Two integration paths are common on the IOT2050.
Path A — Native Telegraf binary
Use the ubuntu/telegraf image only as a reference for the upstream configuration schema. On the IOT2050 itself, install the binary from InfluxData's apt repo or from a deb prebuilt on your build host:
wget -qO- https://repos.influxdata.com/influxdb.key | gpg --dearmor \
-o /usr/share/keyrings/influxdb.gpg
echo "deb [signed-by=/usr/share/keyrings/influxdb.gpg] \
https://repos.influxdata.com/debian bullseye stable" \
> /etc/apt/sources.list.d/influxdb.list
apt update
apt install -y telegraf=1.27.*
Path B — Telegraf inside Docker
For deployments that already run Docker workloads, run Telegraf as a container with the host's /proc, /sys, and Docker socket mounted read-only:
docker run -d --name=telegraf --restart=always \
-v /:/host:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /sys:/host/sys:ro \
-v /proc:/host/proc:ro \
-v $PWD/telegraf.conf:/etc/telegraf/telegraf.conf:ro \
telegraf:1.27
telegraf.conf skeleton
[agent]
interval = "10s"
flush_interval = "10s"
hostname = "$HOSTNAME"
omit_hostname = false
[[inputs.cpu]]
percpu = true
totalcpu = true
[[inputs.mem]]
[[inputs.disk]]
mount_points = ["/"]
ignore_fs = ["tmpfs", "devtmpfs", "overlay"]
[[inputs.diskio]]
[[inputs.net]]
[[inputs.internal]]
[[inputs.system]]
[[inputs.docker]]
endpoint = "unix:///var/run/docker.sock"
container_names = []
source_tag = true
[[inputs.smart]]
path = "/usr/sbin/smartctl"
attributes = ["id", "name", "model"]
[[outputs.influxdb]]
urls = ["http://127.0.0.1:8086"]
database = "iot2050"
retention_policy = ""
timeout = "5s"
useragent = "telegraf"
Docker Integration on the IOT2050
The IOT2050 Basic (1 GB RAM) is tight for Docker. The Advanced (2 GB) is the recommended target. Use docker-ce from Docker's official apt repo, not the Debian-packaged docker.io package, to avoid the older containerd shipped with Debian 11.
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=arm64 signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu jammy stable" \
> /etc/apt/sources.list.d/docker.list
apt update
apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
overlay2 is supported, but on the Basic variant the eMMC endurance budget is small — prefer vfs for low-write workloads and configure log-driver=json-file with size limits (--log-opt max-size=5m --log-opt max-file=3) to avoid log-driven wear.
Building the Image
From the Isar build directory (after source setup-environment build-iot2050 and dropping your meta-mycustomer layer into conf/bblayers.conf):
bitbake iot2050-image-customer
Build artefacts land in build-iot2050/tmp/deploy/images/iot2050/:
| File | Purpose |
|---|---|
iot2050-image-customer-iot2050.wic |
Raw SD/eMMC image, write with dd or the Siemens DP tool |
iot2050-image-customer-iot2050.wic.bmap |
Bmap file for bmaptool copy (faster, validates during write) |
iot2050-image-customer-iot2050.tar.gz |
Rootfs tarball, useful for differential OTA updates |
Verification
- Write the image to an SD card or directly to eMMC:
bmaptool copy --bmap iot2050-image-customer-iot2050.wic.bmap \
iot2050-image-customer-iot2050.wic \
/dev/sdX
- Boot the IOT2050, log in over the serial console or SSH (default user
root, no password — set one immediately). - Confirm the resize completed:
df -h /
lsblk
- Confirm Docker:
docker version
docker run --rm hello-world
- Confirm Telegraf is emitting metrics to InfluxDB:
systemctl status telegraf
journalctl -u telegraf -n 50
influx -execute 'SHOW MEASUREMENTS' -database iot2050
- Confirm the first-boot service did not run twice:
ls -l /var/lib/iot2050-firstboot.lock
systemctl is-enabled iot2050-firstboot.service
Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
dpkg-prebuilt build fails with 404 |
SRC_URI URL or checksum drift | Re-host the deb on a stable URL, update SRC_URI[sha256sum], rebuild |
| Service enabled in image does not start | Isar chroot cannot talk to systemd | Use systemd-preset or write the symlink in do_install_append (Section 3.5) |
| First-boot service runs every boot | Missing ConditionPathExists or lock file |
Verify touch /var/lib/iot2050-firstboot.lock in the script |
| Telegraf inside Docker sees no host metrics |
/proc and /sys not mounted |
Add -v /proc:/host/proc:ro -v /sys:/host/sys:ro to the run command |
docker run fails with exec format error
|
Pulled an x86_64 image by accident | Use the ARM64 manifest, e.g. telegraf:1.27 pulls the linux/arm64 variant on the IOT2050 |
| InfluxDB receives no data | Telegraf points at 127.0.0.1 but InfluxDB runs on a different container network |
Use the container name as hostname, or join both containers to a user-defined bridge |
| Image build takes hours for a one-line change | No sstate cache reuse | Mount persistent sstate-cache and download directories; rebuild with bitbake iot2050-image-customer -c rootfs instead of full clean |
Reproducibility and Maintenance Trade-offs
| Method | Reproducibility | Build cycle per change | Operator skill | Best for |
|---|---|---|---|---|
| Golden Image | Low (manual steps) | N/A (live device) | Low | Single pilot, demos |
| Isar Layer | High (declarative) | ~5–15 min with sstate, ~hours clean | Medium | Production fleets > 5 units |
| First-Boot Service | Medium (depends on remote URL) | ~5 min (just edit script) | Low | Frequently changing config, pilot rollouts |
A common hybrid pattern: build the Isar image with a minimal set of packages and the first-boot service skeleton, and let the first-boot service install the application-specific debs. This keeps the image artifact small, the build deterministic, and the per-customer logic in a single versioned file on your artifact server.
Operational Notes
- Reserve at least 200 MB of free eMMC after image install — the IOT2050 firmware updater and apt's downloaded-package cache need headroom.
- Disable the
apt-daily.timerandapt-daily-upgrade.timerin production images; they will triggerdpkgruns at random intervals and can collide with your services. - For OTA updates, use
swupdate(already pulled in by meta-iot2050) with a signed CPIO image; do not reflash the full.wicin the field. - Watch the kernel
omap_wdtwatchdog — if Telegraf or Docker hangs for > 60 s, the IOT2050 will reboot, which is desirable in a brownout scenario but noisy in logs.
Which Telegraf Docker image should I use on the IOT2050?
Use the official telegraf:1.27 image from Docker Hub. It is a multi-arch manifest that resolves to the linux/arm64 variant on the IOT2050. The ubuntu/telegraf image is an alternative if you need to match the base distribution more tightly.
Why does my .deb's postinst fail during the Isar build?
Isar assembles the image inside a chroot without a running systemd instance. Any postinst step that calls systemctl enable or systemctl start will fail with Failed to connect to bus. Either move the enable step to a systemd preset file in /lib/systemd/system-preset/ or create the symlink manually in a do_install_append function.
How do I speed up incremental Isar builds?
Keep the sstate cache and the apt download cache on a persistent volume (for example, bind-mount build-iot2050/sstate-cache and build-iot2050/tmp/deploy/apt). After editing a recipe, run bitbake iot2050-image-customer -c rootfs to rebuild only the rootfs target, which reuses compiled artefacts and only re-runs the package install step.
Can I run the IOT2050 headless after flashing the custom image?
Yes. Disable the serial-getty and the getty on tty1 in your image recipe, ensure the SSH server is enabled (it is by default in the jan/ubuntu branch), and either bake a known SSH public key into /root/.ssh/authorized_keys or set a root password hash via passwd -u root in a post-install hook.
Does the first-boot filesystem resize run before my custom service?
Only if you order it explicitly. Add After=iot2050-resize-fs.service network-online.target and Wants=network-online.target to your unit file. The default iot2050-resize-fs.service runs resize2fs on the root partition and then touches /var/lib/iot2050-resize.done — gate your provisioning on that file's existence to be safe.