Containers in RAM on hEX Refresh

My first post. Hi all.
I’m running containers in RAM, not to wear out FLASH, and it works well. I thought it would be neat to have a script pulling the container at boot.

  • That part I didn’t get working. /system/script. Container exits with code 255

Here is my instruction:

# :white_check_mark: Run iperf3 Container in RAM (No USB) on MikroTik hEX Refresh (RouterOS 7.20)

This guide explains how to run an **iperf3 container** on a MikroTik hEX Refresh router without using a USB stick, by leveraging **tmpfs** (RAM-based filesystem). This approach prevents flash wear and meets functional and resilience requirements.

  • Make an initial configuration, connected to port 2, and remove firewall blocking other than LAN
  • You must also get the container package i.e. container-7.20.arm. MikroTik · Downloads
  • After this, connect port 1 (Internet) to a network providing DHCP
  • Good practice is to update RouterOS & firmware. **RouterOS 7.20 or later**
  • The router needs Internet acces for updates and for pulling Docker image

## Requirements.

  • Functional Requirement: Run containers without adding USB memory
  • Cost requirement: Reducing Opex (less parts to maintain) & Capex (Cost of USB memory)
  • Resilience Requirement: Run 1M times without wearing out Flash Memory. (Using tmpfs)
  • Functional requirement: 100Mbps minimum
  • Security requirement: not covered here

## 1. SCP container package to the router & Enable Container Support

On your computer

scp container-7.20.5-arm.npk ``admin@192.168.5.210``:.

On router

/system reboot

After reboot:

/system device-mode update container=yes

Now power cycle the router

/system device-mode print

## 2. Create tmpfs for Container Root Directory

/disk add type=tmpfs tmpfs-max-size=64M
/file print where type=disk 
# Find the name (e.g., tmp1)
# tmp1 was created

This creates a RAM-backed filesystem. We’ll use `tmp1/iperf3` as the container root directory.

## 3. Networking Setup

/interface bridge add name=containers
/interface veth add name=veth1 address=172.17.0.3/24 gateway=172.17.0.1
/interface bridge port add bridge=containers interface=veth1
/ip address add address=172.17.0.1/24 interface=containers
/ip firewall nat add action=masquerade chain=srcnat out-interface-list=WAN
/ip firewall nat add action=dst-nat chain=dstnat dst-port=5201 protocol=tcp to-addresses=172.17.0.3 to-ports=5201
/ip firewall nat add action=dst-nat chain=dstnat dst-port=5201 protocol=udp to-addresses=172.17.0.3 to-ports=5201

### Firewall Best Practices

/ip firewall filter add action=accept chain=input comment="accept established,related,untracked" connection-state=established,related,untracked
/ip firewall filter add action=drop chain=input comment="drop invalid" connection-state=invalid
/ip firewall filter add action=accept chain=input comment="accept ICMP" protocol=icmp
/ip firewall filter add action=accept chain=input comment="allow iperf3 TCP" dst-port=5201 protocol=tcp
/ip firewall filter add action=accept chain=input comment="allow iperf3 UDP" dst-port=5201 protocol=udp

## 4. Configure Container Registry

/container config set registry-url=https://registry-1.docker.io tmpdir=tmp1

## 5. Startup Script for Resilience

Create a script to recreate the container at every boot:

/system script set [find where name=recreate-iperf3-on-boot] source={
# Wait for boot stability
:delay 20s;
# Clean existing container if any
:local id [/container find where name=iperf3];
:if ([:len $id] > 0) do={
/container stop $id;
/container remove $id;
    };
# Ensure clean root directory
:if ([:len [/file find where name="tmp1/iperf3"]] = 0) do={
/file add name=tmp1/iperf3 type=directory;
    };
# MY EXACT MANUAL COMMAND 1
/container add name=iperf3 remote-image="johneff/armv5-iperf3:latest" interface=veth1 root-dir=tmp1/iperf3 start-on-boot=no logging=yes hostname=iperf3;
# Wait for import (like I do manually)
:delay 15s;
# MY EXACT MANUAL COMMAND 2
/container start [find where name=iperf3];
:log info "iperf3 container started successfully";
}

Schedule it at boot:

/system scheduler add name=init-iperf3 interval=0s on-event=recreate-iperf3-on-boot start-time=startup
#/system scheduler remove [find where name=init-iperf3]

## 6. Pull and Start Container

/container add name=iperf3 remote-image="johneff/armv5-iperf3:latest" interface=veth1 root-dir=tmp1/iperf3 start-on-boot=no logging=yes hostname=iperf3
/container start [find where name=iperf3]

## 7. Test iperf3

From your PC:

iperf3 -c <router-LAN-IP> -t 10

Or check port:

nc -zv <router-LAN-IP> 5201

## Network Topology Diagram

```Mermaid

graph TD

subgraph Router

A[Bridge: containers
IP: 172.17.0.1/24]

B[veth1
IP: 172.17.0.3/24]

C[Container
iperf3]

end

LAN[LAN: 192.168.x.x/24] -->|dst-nat TCP/UDP port 5201| A

A --> B

B --> C

## :white_check_mark: Key Benefits

- No USB required → Lower cost and fewer parts

- RAM-based container → No flash wear

- Automatic recreation on boot → High resilience

As far as I remember, the main problem is that you can't add(/container/add ) and start(/container/start ) a container in the same script.

Thank you Chat!

This is one key finding!
Some progress: No pre‑creation of root-dir Previous, script did:
/file add name=tmp1/iperf3 type=directory

Pre-creating the root-dir can leave it with a layout/ownership RouterOS’s container engine doesn’t expect. Letting /container add create the root-dir ensures the correct structure and metadata. This is the single biggest factor aligning the scripted path with your manual success.