A reproducible, security-isolated Podman container build of Moxa MXview One 1.7.1 on Ubuntu 22.04.
What this is: a Containerfile, a set of host-side deployment scripts, a small handful of shell wrappers that work around vendor bugs, and a structured writeup of everything that's broken in the vendor's packaged Linux install and what we did about it.
What this is NOT: a redistribution of Moxa's software. The vendor zip
(with the two .deb packages inside) is downloaded fresh from Moxa's CDN
each time you build the image. The Containerfile never bakes the vendor
binaries into the source tree and the .gitignore blocks them from ever
being committed.
MXview One's documented Linux installation procedure does not produce
a working installation on a clean Ubuntu 22.04 system — the .deb
installs, but clicking Start in the control panel fails on a chain of
packaging and runtime issues spanning the install guide, the postinst
script, and the product runtime itself. We documented eleven distinct
issues that have to be worked around to get the stack running.
Each one is described in VENDOR_ISSUES.md with
symptoms, root cause, and a suggested upstream fix, in a form intended
to be useful to Moxa's engineering team.
The Containerfile comments reference those issue numbers at every
workaround (e.g. # Workaround for VENDOR_ISSUES.md Issue 7: ...) so that
a future reader can immediately understand the why behind any step
that looks unusual.
This writeup is being shared with Moxa technical support alongside this repository. It is not a complaint — it's a reproducible bug report with attached workarounds, and we hope Moxa will use it that way.
VENDOR_ISSUES.md Issue 10 describes a polling-engine
SEGV that triggers when MXview enumerates non-ethernet network
interfaces — Tailscale, WireGuard, Docker bridges, inactive WiFi, and
similar tunnel/bridge drivers. This affects bare-metal MXview One
installs on Linux systems with those interface types present at
startup, not just containerized deployments. Device Discovery silently
returns zero devices because the polling engine exits before sending
any probes, and there is no in-product error surface for the condition.
If you're evaluating MXview One on a workstation and Device Discovery
returns nothing, it's worth checking whether you have Tailscale,
Docker, WireGuard, or a similar service running. In our testing,
temporarily stopping those services (or deploying MXview in an
isolated netns, which is what this repository does) allowed the
polling engine to start cleanly. Running strace on the polling
engine was how we traced the root cause; an in-product diagnostic for
this condition would save future customers the same investigation.
MXview One will typically run co-located on a shared industrial PC that is also running other workloads (e.g. an EMS stack on a plant control PC). This is the single most important design constraint and it shapes every networking and security decision below:
- A bug in the vendor software must not be able to reach other workloads on the same host.
- The container must not have host root, even if something inside
escalates — hence
--userns=auto. - The container must not have unrestricted network access to the host — hence rootful Podman with a macvlan network giving the container its own L2-adjacent position on the LAN.
- Administrative access to the web UI is expected to happen via
ssh -Lport forwarding from an operator workstation; the host-side sibling macvlan interface (mxview-host0) exists specifically so thatsshdon the host can reach the container.
If you are deploying this on a dedicated single-purpose NMS box, the same setup still works — you're just spending isolation budget you don't need.
+---------------------------------- host OS ----------------------------------+
| |
| enp68s0 (parent NIC) |
| ├── (host main stack, whatever else runs on the EMS IPC) |
| │ |
| ├── mxview-host0 (macvlan sibling, e.g. 192.168.0.251) |
| │ └── /32 route to container IP — lets sshd port-forwards reach |
| │ the container, and lets the host browser test the UI |
| │ |
| └── podman macvlan network "mxview-lan" (rootful) |
| └── container netns |
| ├── lo |
| ├── eth0 (macvlan child, e.g. 192.168.0.250) |
| └── systemd pid 1 |
| ├── MXControlPanel on :7100 |
| ├── MXviewOneNext (Caddy, mochi-mqtt, ...) on :8443 |
| ├── MXviewPlatform (generic-processor) |
| ├── mx-gateway-ng |
| ├── polling engine (SNMP/ICMP scanner) |
| └── bundled PostgreSQL 15 on :5432 (loopback only) |
| |
+-----------------------------------------------------------------------------+
Containerfile — the image build. Heavily commented, every
vendor-bug workaround cross-referenced
to VENDOR_ISSUES.md.
VENDOR_ISSUES.md — the full bug report: 11 issues, symptoms,
root cause, suggested vendor fixes, our
workarounds. Also intended to be sent to
Moxa support.
README.md — this file.
LICENSE — MIT license for the original work in this
repository.
NOTICE — nominative-use note for the Moxa and
MXview One trademarks.
scripts/
env.example — site-specific deployment parameters. Copy to
scripts/.env and edit for your host.
lib.sh — shared env loader and helpers.
userns-setup.sh — one-time: create the `containers` user and
allocate subuid/subgid ranges for
--userns=auto.
host-network-setup.sh — one-time per boot: create the mxview-host0
macvlan sibling and /32 route.
host-network-teardown.sh — cleanup for the above.
podman-network-setup.sh — one-time: create the rootful podman
macvlan network.
build.sh — build the container image.
run.sh — start (or restart) the container.
stop.sh — stop and remove the container.
vendor-workarounds/
initdb-wrapper.sh — wraps bundled initdb to prepend narrowly
scoped loopback trust to pg_hba.conf
(see Issue 8).
mxviewonenext-wrapper.sh — wraps MXviewOneNext to ensure the stub
users table exists (see Issue 8).
The setup scripts (userns-setup.sh, host-network-setup.sh,
podman-network-setup.sh) are idempotent — safe to re-run.
run.sh is destructive. It stops and removes the existing container
before launching a new one. Until persistent volumes are wired up (see
Roadmap), this means every re-run loses the Postgres cluster, discovered
devices, the admin password change, license activation, and any other
runtime state. Plan accordingly. For day-to-day development sudo podman restart mxview is the non-destructive option; run.sh is for "rebuild
and start fresh."
You need rootful Podman installed. On Debian/Ubuntu:
sudo apt install -y podmanVerify it works for rootful:
sudo podman versioncd ~/code/mxview
cp scripts/env.example scripts/.env
$EDITOR scripts/.envFill in:
MXVIEW_PARENT_IF— the host NIC attached to the LAN you want to monitorMXVIEW_SUBNET,MXVIEW_GATEWAY— describing that LANMXVIEW_CONTAINER_IP— the IP the container will claim. Must be outside any DHCP pool on the LAN.MXVIEW_HOST_SIBLING_IP— the host's own macvlan sibling IP, used for SSH port forwarding and local browser access
sudo scripts/userns-setup.sh # creates 'containers' user + subuid/subgid
sudo scripts/host-network-setup.sh # creates mxview-host0 + route
sudo scripts/podman-network-setup.sh # creates the rootful podman networksudo scripts/build.shTakes ~3 minutes on a first build (the vendor zip is ~560 MB, downloaded fresh from Moxa's CDN). Subsequent builds use cached layers.
sudo scripts/run.shThe script prints the two URLs you want:
Control Panel: https://<MXVIEW_CONTAINER_IP>:7100 (MXview admin console)
MXview One UI: https://<MXVIEW_CONTAINER_IP>:8443 (the actual product)
Both use a self-signed cert from the .deb — your browser will complain
the first time. Default credentials on first login: admin / moxa.
MXview One forces a password change on first login.
The packaged .deb boots into a first-run flow:
- Accept the EULA
- Change the default password
- Activate the 90-day trial license (or load a purchased license)
- Start a Device Discovery scan against your LAN subnet
If device discovery returns devices that match what you can verify with
snmpget from the host, the containerization is working end-to-end.
- Full MXview One web UI on 8443
- Control Panel on 7100
- Bundled PostgreSQL 15, correctly initialized and migrated
- All MXview microservices (generic-processor, mx-gateway-ng, startMXsecurity, MXviewPlatform, mochi-mqtt, polling engine)
- SNMP v1/v2c unicast device discovery
- Generic SNMP polling (sysDescr, sysName, sysUpTime, sysObjectID, ifTable/ifXTable traffic counters, availability tracking)
- ICMP reachability scans for non-SNMP LAN hosts
- Moxa-specific device templates for Moxa hardware (not tested here because we don't have any)
- IP Conflict Detection (Moxa's
libpcap-based feature). Requires passive packet capture on the physical LAN, which a macvlan-isolated container cannot do. To enable it you'd give up isolation entirely (--network host,--privileged, promiscuous mode) — a non-starter on a shared EMS IPC. - Subnet-broadcast auto-discovery. Only scan-range / unicast discovery is supported. MXview One doesn't rely on broadcast for documented discovery modes, so this is not in practice a regression.
- SNMP traps on UDP 162 (receiving unsolicited device traps).
The container has
CAP_NET_BIND_SERVICEand its own LAN IP via macvlan, so it can in principle bind UDP 162 natively. What's missing is the MXview-side listener configuration — we haven't pointed MXview at UDP 162 on the container's IP yet. That's future work, not a fundamental blocker. See the "Roadmap" section below.
Documented in detail in VENDOR_ISSUES.md:
- The
mxview-core.migrate-mxview-user-logmigration is an upgrade-era step with no fresh-install branch. We suppress the error with a stub table, but a future vendor release could change what the migration expects and break our stub. If so, the error message will be loud and easy to re-diagnose. - The bundled
mosquittobroker may or may not be on any runtime- critical path; we haven't fully verified. We apply the cert-sync workaround defensively. - The polling engine is fragile to unusual interface types. Any
deployment that exposes more than
lo + one ethXto the container risks a SEGV. Macvlan happens to give us a minimal clean netns.
- Rootful Podman — required for macvlan on a physical NIC.
--userns=auto— container UID 0 is mapped to an unprivileged high UID on the host. A container escape does not immediately become host root.--security-opt=no-new-privileges— setuid binaries inside the image cannot escalate.--cap-add=NET_RAW— needed for ICMP ping sweeps.--cap-add=NET_BIND_SERVICE— needed for future SNMP trap listener on UDP 162.- No
--privileged. Ever, in production. - Macvlan network isolation — honestly described. The container has
its own network namespace. It cannot reach the host's
127.0.0.1, and it cannot reach the parent NIC's own IP (e.g.enp68s0at192.168.0.106) — the macvlan driver blocks parent/child self-loops, which is the same kernel behavior that forced us to create the sibling interface in the first place. However, the container can reach the host via the siblingmxview-host0interface (by design — this is what letssshdport-forwarding terminate into the container). That means any host service bound on0.0.0.0— the hostsshd, any loosely scoped management surface — is reachable from the container atmxview-host0's IP. To tighten further on a shared EMS IPC, either bind host services to specific non-mxview-host0interfaces, or add an nftables rule on the host's input hook that drops traffic from the container's IP to host services. This is documented as-is rather than glossed over; the boundary is real but not absolute. - Self-signed internal TLS certs as shipped by the vendor. Not suitable for operator-facing TLS — deployments should put a proper reverse proxy (nginx, Traefik, Caddy running on the host) in front of 8443 and terminate TLS there with a real certificate.
- Wire MXview's SNMP trap listener to bind UDP 162 on the container's
macvlan IP (in-container, using
CAP_NET_BIND_SERVICEwhich the container already has). - Publish UDP 514 for syslog reception.
- Persistent volumes for PostgreSQL data and MXview state so that
container recreation doesn't wipe the database. Use named volumes,
not bind mounts (named volumes remap correctly under
--userns=auto). - A Quadlet
.containerunit for host-systemd-managed container lifecycle on the EMS IPC. - A systemd-networkd
.netdev/.networkdrop-in to make themxview-host0sibling persistent across reboots without re-runninghost-network-setup.sh. - Automated upgrade testing: when MXview 1.7.2 ships, rebuild against the new zip and verify how many of the 11 documented vendor issues are fixed upstream.
If you are trying to run this and something doesn't work, please:
- Check that the version you're building against is still 1.7.1. The MXview zip URL embeds a specific product version and build date; if Moxa has cut a new release, some of the documented bugs may be gone and some may have moved.
- Read the relevant
VENDOR_ISSUES.mdentry — it describes both the symptom and the actual root cause, not just the workaround, so you can tell whether your problem is the same or something new. - If you hit a new vendor issue, open an issue on this repo with
logs, strace output if possible, and we'll add it to
VENDOR_ISSUES.md.
This repository contains only original work and references to publicly
available Moxa materials. No Moxa source code, no Moxa binaries, no
Moxa PDFs. The scripts and documentation here are released under the
MIT license — see LICENSE, with a nominative-use note
on Moxa trademarks in NOTICE.
You must obtain MXview One from Moxa under Moxa's own license terms;
the 90-day trial is downloadable from their website, and the
Containerfile fetches the .deb package directly from Moxa's public
CDN at build time.