routeros-container — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited routeros-container (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
RouterOS 7.x includes a container subsystem (/container) that runs OCI-compatible container images directly on MikroTik hardware. It is NOT Docker — it's MikroTik's own implementation with significant differences.
Requirements:
container extra package installedContainer support is gated behind device-mode, which requires physical confirmation (reset button press or power cycle) to enable:
# Enable container mode
/system/device-mode/update mode=advanced container=yes
# After executing: physically confirm within activation-timeout
# - Press reset button, OR
# - Power cycle the deviceDevice-mode is a general RouterOS security feature — not container-specific. It gates many features (scheduler, fetch, sniffer, etc.) across four modes (home, basic, advanced, rose) with device-dependent factory defaults.
For the full feature matrix, modes, update properties, and physical confirmation details: see the Device-mode reference in the routeros-fundamentals skill.
Mode script bypass (7.22+): During netinstall, a mode script (-sm) can set device-mode on first boot, automatically triggering a reboot. See the routeros-netinstall skill.
# Check if container package is already installed
/system/package/print where name=containerMethod 1: Upload .npk file + apply-changes (offline)
# Upload via SCP (or Winbox drag-and-drop, or WebFig file upload)
scp container-7.22-arm64.npk admin@router:/# Apply changes (triggers reboot AND activates — /system/reboot does NOT work!)
/system/package/apply-changes⚠️ Critical: `/system/package/apply-changes` was added in RouterOS 7.18. On 7.18+, always use it — a plain /system/reboot discards uploaded packages. On versions <7.18, /system/reboot IS the correct (and only) method. (Lab-verified: 7.22.1 uses apply-changes, 7.10 requires reboot. Version check via rosetta command tree.)
Method 2: Online package update (requires internet)
/system/package/update check-for-updates
/system/package/update installThis downloads and installs all available updates including extra packages. To enable a specific package already uploaded but not active, use /system/package/enable container then /system/package/apply-changes.
Containers connect to RouterOS networking via VETH interfaces:
# Create VETH pair
/interface/veth/add name=veth-myapp address=172.17.0.2/24 gateway=172.17.0.1
# The VETH name IS the container's interface name (RouterOS 7.21+)# Create a bridge for containers
/interface/bridge/add name=containers
# Add VETH to the bridge
/interface/bridge/port/add bridge=containers interface=veth-myapp
# Assign IP to bridge (acts as gateway for containers)
/ip/address/add address=172.17.0.1/24 interface=containers# Masquerade container traffic for internet access
/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.17.0.0/24
# Port forwarding from host to container
/ip/firewall/nat/add chain=dstnat action=dst-nat \
dst-port=8080 protocol=tcp to-addresses=172.17.0.2 to-ports=80
# Allow container bridge in interface list (if firewall restricts)
/interface/list/member/add list=LAN interface=containersFor containers that need to be on the same L2 network as physical interfaces (e.g., netinstall):
# Add both physical port and VETH to the same bridge
/interface/bridge/port/add bridge=mybridge interface=ether5
/interface/bridge/port/add bridge=mybridge interface=veth-netinstallThis gives the container direct L2 access to devices on ether5.
There are two ways to attach env vars and mounts to a container (from 7.21+):
Set env= and mount= directly on /container/add — keeps the container self-contained:
# Inline env vars and mount (7.21+)
/container/add remote-image=pihole/pihole:latest interface=veth1 \
env="TZ=Europe/Riga,WEBPASSWORD=secret" \
mount="src=disk1/pihole,dst=/etc/pihole" \
root-dir=disk1/images/pihole logging=yesThis is also how /app YAML works under the hood — inline is the modern pattern and easier for automation (no separate linked objects to manage).
Create env vars and mounts as separate objects, then reference by name:
# Create named env list (7.20+ — the 'list=' property groups envs together)
/container/envs/add list=MYAPP key=TZ value="Europe/Riga"
/container/envs/add list=MYAPP key=WEBPASSWORD value="secret"
# Create named mount
/container/mounts/add name=appdata src=disk1/appdata dst=/data
# Reference from container (7.20+ uses 'envlists=', pre-7.20 used 'envlist=')
/container/add file=myimage.tar interface=veth1 \
envlists=MYAPP mountlists=appdata root-dir=disk1/myappBest practice: Always place container volumes on external disk (disk1/), never on internal flash storage.
The naming of env/mount reference properties changed at version boundaries:
| Version | Env list grouping (/container/envs/add) | Container env reference (/container/add) | Container mount reference |
|---|---|---|---|
| Pre-7.20 | key=, value= only (no grouping property) | (no env reference property) | (not available) |
| 7.20 | list= added | envlists= (plural) added | (not available) |
| 7.21+ | list= | envlists= + inline env= | mountlists= + inline mount= |
Version note: Property names for 7.20+ are confirmed against/console/inspectcommand tree data. Pre-7.20,/container/envs/addhad onlykeyandvaluewith no grouping mechanism;/container/addhad no env reference property. Inlineenv=andmount=were added at 7.21.
RouterOS accepts container images in these formats:
/container/config/set registry-url=https://registry-1.docker.io tmpdir=disk1/pull
/container/add remote-image=library/alpine:latest interface=veth-myappUpload a Docker v1 tar to the router, then:
/container/add file=myimage.tar interface=veth-myappRouterOS's container loader has specific requirements for local tar files:
manifest.json + config.json + layer.tarmyimage.tar
├── manifest.json # [{"Config":"config.json","RepoTags":["name:tag"],"Layers":["layer.tar"]}]
├── config.json # {"architecture":"arm64","os":"linux","config":{...},"rootfs":{...}}
└── layer.tar # Uncompressed tar of the full filesystemThese constraints are the key difference from standard OCI images — most base images from public registries already meet requirement 1 and 2 via registry pull; local tar builds must satisfy all three.
# Create container (7.21+ inline syntax)
/container/add file=myimage.tar interface=veth-myapp \
env="MY_VAR=hello" mount="src=disk1/appdata,dst=/data" \
root-dir=disk1/myapp logging=yes
# Start
/container/start [find tag~"myapp"]
# Stop
/container/stop [find tag~"myapp"]
# View status
/container/print
# View logs (if logging=yes)
/log/print where topics~"container"
# Remove (must be fully stopped first)
/container/remove [find tag~"myapp"]const base = "http://192.168.1.1/rest";
const auth = { headers: { Authorization: `Basic ${btoa("admin:")}` } };
// List containers
const containers = await fetch(`${base}/container`, auth).then(r => r.json());
// Start container by ID
await fetch(`${base}/container/start`, {
method: "POST", ...auth,
headers: { ...auth.headers, "Content-Type": "application/json" },
body: JSON.stringify({ ".id": "*1" }),
});
// Check status — .running field is "true"/"false" (strings!)
const status = await fetch(`${base}/container/*1`, auth).then(r => r.json());
if (status.running === "true") { /* container is running */ }
// Stop container
await fetch(`${base}/container/stop`, {
method: "POST", ...auth,
body: JSON.stringify({ ".id": "*1" }),
});
// Delete — must be fully stopped. Poll .running and retry.
async function deleteContainer(id) {
for (let i = 0; i < 5; i++) {
const c = await fetch(`${base}/container/${id}`, auth).then(r => r.json());
if (c.running === "false") {
await fetch(`${base}/container/${id}`, { method: "DELETE", ...auth });
return;
}
await new Promise(r => setTimeout(r, 3000));
}
throw new Error("Container did not stop in time");
}"true" / "false", not booleans.running.running until "false" before DELETEremote-image= for registry pullenvlist= (singular). See env/mount version history above.Selected properties from /container/add. This is not exhaustive — use rosetta MCP tools (routeros_command_tree at /container/add) for the full list on a specific version.
| Property | Description |
|---|---|
interface | VETH interface |
env | Inline environment variables (7.21+). Comma-separated KEY=value pairs |
envlists | Named env list reference (7.20+). See env/mount section above |
mount | Inline volume mount (7.21+). src=host/path,dst=/container/path |
mountlists | Named mount list reference (7.21+). See env/mount section above |
root-dir | Storage location for container filesystem |
file | Container tar file (local import) |
remote-image | Container image name (registry pull) |
cmd | Override container CMD |
entrypoint | Override container ENTRYPOINT |
hostname | Container hostname |
dns | DNS server for container |
logging | Enable container stdout/stderr to RouterOS log (yes/no) |
start-on-boot | Auto-start container on device boot (yes/no) |
workdir | Override working directory |
name | Container name (for [find where name=...]) |
devices | Pass through physical devices (7.20+) |
cpu-list | CPU core affinity |
memory-high | RAM usage limit in bytes |
When pulling from registries or building images, map RouterOS architecture to Docker platform:
RouterOS architecture-name | Docker Platform |
|---|---|
arm | linux/arm/v7 |
arm64 | linux/arm64 |
x86 | linux/amd64 |
Query the router's architecture:
const resource = await fetch(`${base}/system/resource`, auth).then(r => r.json());
const arch = resource["architecture-name"]; // "arm64", "arm", "x86"RouterOS 7.21 introduced the /app path (built-in app listing). Full YAML app creation (/app/add) was added in 7.22. See the routeros-app-yaml skill for the full YAML specification.
Use manual /container setup when you need full VETH/bridge/L2 control, such as netinstall, DHCP relay, or other raw L2 workflows. Use /app YAML for standard multi-container applications that fit RouterOS app networking and port-forwarding conventions.
Related skills:
routeros-netinstall skillrouteros-app-yaml skillrouteros-fundamentals skillMCP tools:
rosetta MCP server tools (routeros_search, routeros_get_page, routeros_search_properties)External docs:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.