TL;DR
Starting in RouterOS 7.23 (not present in 7.22.1/7.22.2), a container's cmd value that contains
a comma -- e.g. -forwarder com=1.1.1.1,8.8.8.8 for multiple DNS servers -- gets silently cut in
the wrong place, breaking the container. Escaping the comma (\\,) fixes it live, but the moment
you /container export and /import that same config back (backup/restore, migrating to a new
router), the escaping is lost and it breaks again -- with no error either time. Confirmed on every
7.23.x release; confirmed absent on 7.22.1/7.22.2. Two independent repros below (manual/GUI and
scripted).
Environment
- RouterOS: reproduced on every 7.23.x release (7.23, 7.23.1, 7.23.2, 7.23.3); confirmed absent
on 7.22.1 and 7.22.2 -- see the version table below. Primary report is against 7.23.3 (stable). - Container package: matching version in each case
- Affected property: /container add cmd=... / /container set cmd=...
Summary
This is a regression introduced in RouterOS 7.23 (present in every 7.23.x release tested,
absent in 7.22.1/7.22.2): /container's cmd property changed from a plain string type to an
array type. On the array-typed versions (7.23+), assigning cmd a string containing a literal
comma (as part of one logical argument, not as an intentional array separator) causes RouterOS to
split that string into extra array elements at the comma -- and this splitting is not
reversible: /container export serializes the resulting array back out as one comma-joined
string with no escaping, so re-/importing the exported config reproduces the split, even
starting from a container whose cmd was correctly configured.
This breaks any use case that legitimately needs a comma inside one cmd argument -- our
specific case is the official nextdns/nextdns container image, whose -forwarder flag
documents [DOMAIN=]SERVER_ADDR[,SERVER_ADDR...] as its supported syntax for multiple upstream
DNS servers per domain (comma-separated, for failover). Worth noting this isn't just "annoying" --
it's the kind of regression that can pass a config's own validation/testing at deploy time and
then silently break later, purely from an OS upgrade (7.22.x -> 7.23.x) with no config change at
all, since a cmd value that worked fine as a plain string suddenly starts splitting once the
underlying property type changes.
Version bisection (all versions from 7.22.1 through 7.23.3 tested)
| RouterOS | cmd type | Comma in a value splits it? | |
|---|---|---|---|
| 7.22.1 | str | No | |
| 7.22.2 | str | No | |
| 7.23 | array | Yes | |
| 7.23.1 | array | Yes | |
| 7.23.2 | array | Yes | |
| 7.23.3 | array | Yes |
Tested via [:typeof [/container get [find name=...] value-name=cmd]] and
[:len [...]] immediately after /container add ... cmd=, on the same
physical CHR instance downgraded/upgraded between each version via /system/package/downgrade
(works bidirectionally) -- ruling out any hardware/environment difference between test runs.
Repro 1 -- manual/GUI, export/import round-trip
- Add a container with a cmd argument that needs a literal comma in one value, e.g.:
/container add ``remote-image=registry-1.docker.io/nextdns/nextdns:latest`` interface=<veth> \ cmd="run -forwarder com=192.168.255.55,192.168.255.56" root-dir=<dir> layer-dir=<dir> \ name=test-cmd-comma
- /container print detail where name=test-cmd-comma -- note cmd is already split into two
array elements (com=192.168.255.55 and 192.168.255.56), not the intended one. - Work around it once by escaping the comma: /container set [find name=test-cmd-comma] cmd="run -forwarder com=192.168.255.55\,192.168.255.56" (note: this requires two literal
backslash characters typed in the CLI/GUI, i.e. \\, in scripted contexts, since a single \,
is not a recognized escape sequence and is a syntax error on its own -- confirmed). - /container print detail again -- cmd is now correctly a single element,
run -forwarder com=192.168.255.55,192.168.255.56, comma intact, no visible backslash. - /container export file=test-export -- inspect the resulting file. It contains
cmd="run -forwarder com=192.168.255.55,192.168.255.56" -- correct-looking, but the escaping
is gone (nothing needed it while cmd was a single element). - /container remove [find name=test-cmd-comma], then /import file-name=test-export.rsc (or
just copy-paste the add cmd="..." line from the export back into the terminal). - /container print detail once more -- cmd is split into two elements again, identical to
step 2. The exported config does not reproduce the state it was exported from.
Repro 2 -- scripted, no GUI needed
:local cmdVal ("run -forwarder com=192.168.255.55\\,192.168.255.56")
/container add remote-image=registry-1.docker.io/nextdns/nextdns:latest interface=<veth> \
cmd=$cmdVal root-dir=<dir> layer-dir=<dir> name=test-cmd-comma
:put ("typeof: " . [:typeof [/container get [find name=test-cmd-comma] value-name=cmd]])
:put ("len: " . [:len [/container get [find name=test-cmd-comma] value-name=cmd]])
# -> typeof: array, len: 1 (correct)
/container export file=test-export
/container remove [find name=test-cmd-comma]
/import file-name=test-export.rsc
:put ("typeof: " . [:typeof [/container get [find name=test-cmd-comma] value-name=cmd]])
:put ("len: " . [:len [/container get [find name=test-cmd-comma] value-name=cmd]])
# -> typeof: array, len: 2 (WRONG -- was 1 before the export/import round-trip)
Expected behavior
Either:
- /container export should escape (or otherwise encode) a comma that's part of a single array
element's value, so /importing the exported config reproduces the identical cmd array; or - if the intent is genuinely that cmd values can never contain a literal comma, /container add/set cmd= should reject (or clearly document) that a comma always means "start a new
argument," so \,escaping isn't silently accepted as a working (but non-persistent) escape
hatch.
Actual behavior
A cmd value containing an escaped comma is accepted, stored, and functions correctly at
runtime -- but /container export discards the escaping, so the very next /import (or a
full router backup/restore) silently corrupts it back to the split, broken state.
Impact
Any container whose cmd needs a literal comma in one argument (multiple space-separated CLI
args, at least one of which itself needs a comma-separated sub-list -- e.g. the official
nextdns/nextdns image's -forwarder DOMAIN=IP1,IP2 syntax) will break the first time its
config is exported and re-imported, e.g. during a router backup/restore or migration to a new
device -- with no warning, no error, just a silently different (and non-functional) cmd.
Separately, and worse: because this is a genuine type regression (not something that always
existed), a cmd value that worked correctly for a long time can start failing purely from an
in-place RouterOS upgrade from 7.22.x to 7.23.x, with zero configuration change on our end. And
the two "fixes" are mutually exclusive across that boundary -- escaping the comma (\,) is
required to keep it working on 7.23+, but doing so on 7.22.x actively breaks it a different way
(introduces a literal, unwanted \ character into the value -- confirmed, not theoretical). A
config/script that defensively escapes "just in case" is not safe across both version families.
Additional context
Two separate things changed, worth not conflating:
- The type change we're reporting here (cmd str -> array, comma-splitting) -- pinned
down precisely to RouterOS 7.23 via the version bisection above. Not documented anywhere we
could find, official or community. - A separate, earlier change: "basic string splitting" for the cmd argument (tokenizing
on whitespace when actually launching the container) was added in RouterOS 7.20, per
community documentation
(MikroTik Solutions: Container Limitations),
which itself notes uncertainty about exactly where in the changelogs that was introduced.
This is a different mechanism at a different layer (container-launch-time argv construction,
not the property's storage type) and a different version (7.20, not 7.23) -- it's what turns a
single cmd string/element into individual argv tokens for the container process, and doesn't
itself involve commas at all.
Official docs at Container - RouterOS - MikroTik Documentation list cmd
only as a "string type" with no version-specific note that this changed in 7.23, no description of
the array behavior on newer versions, and no mention of comma/escaping semantics at all -- happy
to help narrow this down further if useful (e.g. exact changelog entry, if MikroTik can identify
it internally), but from our side this entire type-and-escaping behavior isn't documented
anywhere we could find, on any version.