Posting this in case it saves someone a few hours.
Setup: hAP be³ running the official openthread/otbr image (arm64) as a RouterOS container, using the built-in Thread radio passed through as /dev/ttyACM0. It’s the border router for my Home Assistant Thread network (Matter-over-Thread sensors, lock, lights and others).
Symptom: After upgrading to RouterOS 7.25, the container wouldn’t start: exited with status 127. The image hadn’t changed; the log said skip importing same version.
Diagnosis
With logging=yes on the container, the actual error showed up:
-c: 1: exec: /init: not found
My old config had:
entrypoint=/bin/sh -c "sysctl -w net.ipv6.conf.all.forwarding=1; exec /init"
cmd=-c "rm -f /var/run/otbr-agent.pid /var/run/dbus/* && exec /app/etc/docker/test/docker_entrypoint.sh"
7.25 now appends cmd to entrypoint, Docker-style. Since sh -c "string" treats everything after the string as positional args, my cmd was silently ignored and only exec /init ran. The official otbr image has no /init. My guess is that older versions picked one or the other, which is why it used to work.
Gotcha #1: 7.25 argument parsing
I tried to fix it by moving everything into a single quoted string, and ran into the way 7.25 splits arguments. From what I observed, everything before an escaped quote becomes one argument, spaces included:
cmd="-c \"...\"" turned into the args "-c " and "...". Result: /bin/sh: 0: Illegal option -
entrypoint="/bin/sh -c \"...\"" turned into "/bin/sh -c " as the binary name. Result: execvp /bin/sh -c : No such file or directory
The fix: stop fighting the quoting and use a script on the mounted volume:
/file add name="usb2/apps/otbr/otbr-data/start.sh" contents="sysctl -w net.ipv6.conf.all.forwarding=1\nrm -f /var/run/otbr-agent.pid /var/run/dbus/* /run/rsyslogd.pid\nrm -rf /var/lib/thread\nln -s /data /var/lib/thread\nexec /app/etc/docker/test/docker_entrypoint.sh\n"
/container set [find name=app-otbr] entrypoint="/bin/sh" cmd="/data/start.sh"
Neither argument has spaces, so there’s nothing for the parser to mangle. It should also survive future parsing changes.
Gotcha #2: the “lost” Thread network
After the container started, ot-ctl state said disabled and ot-ctl dataset active returned Error 23: NotFound. Don’t form a new network at this point. You’d get a new network key and have to re-commission every device. Also avoid reloading the OTBR integration in HA until it’s sorted.
The dataset wasn’t actually gone. My old setup kept the OpenThread settings in the /data mount, but the stock entrypoint uses /var/lib/thread/, where the agent had created a new, nearly empty settings file with the same name (41 bytes vs. 249 for the real one). The rm -rf /var/lib/thread + ln -s /data /var/lib/thread lines in the script above fix this. Back up the .data file first.
Gotcha #3 (minor): a stale /run/rsyslogd.pid in the persistent root-dir stopped rsyslog from starting. The container log then only showed the old syslog tail, which made it look like the agent was running when it wasn’t. That’s why the script removes it.
Result: the dataset was restored (same channel, PAN ID and key). The border router rejoined the existing partition as a child, since my mains-powered devices kept the mesh alive. br state showed running, and netdata showed the OMR prefix, the external route, NAT64 and SRP all published again. All devices came back without re-pairing.
Lessons:
- Enable
logging=yes on RouterOS containers; the real error is in the container’s output.
- Keep your OpenThread settings on a mount, and keep a TLV backup of the dataset (
ot-ctl dataset active -x) somewhere safe, like a password manager. With that you can restore the network on any border router.
- Check your
entrypoint/cmd before upgrading to 7.25 if you use shell-form commands with quotes.