Introduction
A Pilbara iron ore site generates data from hundreds of instruments — pressure transmitters, vibration sensors, motor temperature probes, conveyor belt speed encoders. That data sits in a PI historian archive that nobody's looked at since 2019, behind a dashboard that only works on one Windows XP machine in the control room, and an OT network topology diagram that was last updated by someone who's no longer with the company.
The historian is chained to a physical server in the plant server room. The SCADA vendor locked the dashboards behind Internet Explorer. There's no disaster recovery for tag data — if the server dies, the last clean backup is three months old. And the remote site 400km south has a 4Mbps satellite link with 600ms latency and a FIFO maintenance window that comes around twice a year.
This is the reality for most brownfield mining operations in the Pilbara. It's not a technology gap — the technology exists. It's an architecture gap: the current stack was never designed to be distributed, containerised, or recoverable.
Docker at the edge is the fix.
The Problem: Why Historians Break Down at Scale
Single-Server Chaining
Most OSIsoft PI and Wonderware historians are installed on a single physical server, with the historian service, tag database, and front-end all co-located. This works fine until it doesn't — disk failure, power event, Windows update that required a restart at 3am. There's no redundancy, no failover, and no way to recover beyond the last backup.
The tag archive grows over years until the server runs out of disk. The vendor says migrate to new hardware. Migration means downtime, testing, and a 6-week project nobody wants to fund.
FIFO Satellite Windows
Remote Pilbara sites run on FIFO (Fly-In Fly-Out) rotations. The OT engineer on shift has a 12-hour day with 2 hours of travel and is responsible for everything from the crusher to the camp water treatment plant. There is no spare time to manage historian backups, tune retention policies, or troubleshoot a dashboard that stopped rendering.
Satellite connectivity at these sites means any centralised monitoring solution has to tolerate latency, packet loss, and extended disconnection windows. Anything that requires real-time bidirectional connectivity fails silently.
Vendor-Locked Dashboards
Ignition, Wonderware, and FactoryTalk View dashboards are typically bound to a specific server, a specific runtime version, and a specific set of tags configured by whoever installed the system. Updating a dashboard requires a vendor engagement. Changes to the underlying tag structure require an SCADA engineer. The dashboard becomes a static artifact rather than an operational tool.
The result is a dashboard that shows the wrong ranges, the wrong alarms, and the wrong equipment — but nobody changes it because changing it is a project.
No DR for Tag Data
Point-in-time recovery for a PI Server is a serious undertaking. The backup strategy for most sites is a nightly SQL backup to a local NAS. If the NAS fails, or the backup job silently stopped working, there is no DR. Tag data — months or years of operational history — is simply gone if the primary server fails.
For sites with compliance obligations (Mining Act, environmental monitoring, safety case documentation), losing historian data is a reportable incident.
Why Docker at the Edge
Stateless Services
A containerised service can be killed, restarted, and replaced without affecting the data it writes. The data lives in a volume (TimescaleDB, InfluxDB), not in the container layer. This means the OT engineer can pull a new version of Telegraf, restart the container, and the configuration and historical data are untouched.
Rollback is atomic: if the new version breaks, docker compose down && docker compose up -d restores the previous image in under 60 seconds.
Deterministic Deploys
Docker images are immutable. When you build an image, it includes the exact version of the OS, the exact version of the application, and the exact version of all dependencies. Two deployments of the same image on different hosts are identical. This matters in OT environments where consistency between sites is the difference between a working system and a field escalation.
With image signing (Docker Content Trust / Cosign), you can verify that the image running on the edge node is the image you built and tested — not a modified version from a compromised registry.
Footprint for Industrial PCs
Modern industrial PCs — Advantech UNO series, Beckhoff CX series, Axiomtek iNAV series — ship with dual-core Intel Atom or Celeron CPUs and 4–8GB RAM. Docker Engine runs fine on these. The reference stack described below runs comfortably under 2GB RAM at idle.
OT-Safe Rollback
The scenario: it's 6pm, you've just deployed a new Telegraf config to the satellite site, and the OT engineer reports that tag collection has stopped. With containers, you roll back in one command. Without containers, you're on the phone to the vendor.
Reference Stack
The following Docker Compose skeleton covers the edge-to-enterprise data flow for a Pilbara iron ore site. All components are open-source or commercially available with official Docker images.
version: "3.8" services: # ── Edge MQTT Broker ────────────────────────────────────────── emqx-edge: image: emqx/emqx:5.8 container_name: emqx-edge restart: unless-stopped ports: - "1883:1883" # MQTT plaintext - "8883:8883" # MQTT/TLS - "8083:8083" # MQTT over WebSocket - "18083:18083" # EMQX dashboard (LAN only) environment: EMQX__dashboard__default_username: "${EMQX_USER}" EMQX__dashboard__default_password: "${EMQX_PASS}" EMQX__zones__zone1__mqtt__max_packet_size: "32KB" EMQX__zones__zone1__mqtt__max_mqueue_len: "10000" volumes: - emqx-data:/opt/emqx/data - emqx-log:/opt/emqx/log network_mode: host # Use host networking; no Docker DNS on OT nets # ── Ignition Edge (SCADA gateway) ───────────────────────────── ignition-edge: image: inductiveautomation/ignition:8.1 container_name: ignition-edge restart: unless-stopped ports: - "8088:8088" # Ignition web gateway - "8443:8443" # HTTPS environment: - IGNITION_EDITION=edge volumes: - ignition-data:/var/lib/ignition/data - ignition-gateway:/usr/local/ignition/gateway depends_on: emqx-edge: condition: service_healthy # ── Telegraf: Sparkplug B → store-and-forward ───────────────── telegraf: image: telegraf:1.29 container_name: telegraf restart: unless-stopped user: "0:0" ports: - "8092:8092/udp" # StatsD input - "8094:8094" # HTTP input volumes: - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro - telegraf-buffer:/var/lib/telegraf environment: - TELEGRAF_BUFFER_DIR=/var/lib/telegraf network_mode: host # ── TimescaleDB: time-series historian ──────────────────────── timescaledb: image: timescale/timescaledb:2.15-pg16 container_name: timescaledb restart: unless-stopped environment: POSTGRES_DB: "pilbara_historian" POSTGRES_USER: "${TS_USER}" POSTGRES_PASSWORD: "${TS_PASS}" volumes: - ts-data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 30s timeout: 10s retries: 3 # ── Grafana: dashboards ───────────────────────────────────────── grafana: image: grafana/grafana:11.2 container_name: grafana restart: unless-stopped ports: - "3000:3000" environment: GF_SECURITY_ADMIN_USER: "${GF_USER}" GF_SECURITY_ADMIN_PASSWORD: "${GF_PASS}" GF_SERVER_ROOT_URL: "%(base)s/grafana" volumes: - grafana-data:/var/lib/grafana - ./dashboards:/etc/grafana/provisioning/dashboards - ./datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:ro depends_on: timescaledb: condition: service_healthy # ── NGINX: reverse proxy for LAN access ─────────────────────── nginx: image: nginx:1.27-alpine container_name: nginx-edge restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - emqx-edge - grafana volumes: emqx-data: emqx-log: ignition-data: ignition-gateway: telegraf-buffer: ts-data: grafana-data:
Dependencies to define in .env:
EMQX_USER=admin EMQX_PASS=<strong-local-password> TS_USER=pilbara TS_PASS=<strong-local-password> GF_USER=admin GF_PASS=<strong-local-password>
Edge → Enterprise Data Flow
The data path from PLC register to enterprise historian:
Store-and-forward is the key property. When the satellite link drops (which happens regularly in the Pilbara during wet season electrical storms), Telegraf queues MQTT messages in local storage. When connectivity restores, the queue drains in order. No data loss. The enterprise historian receives the complete sequence with correct timestamps.
The EMQX edge broker handles the MQTT session state. When the edge node reconnects after 48 hours offline, it sends a NBIRTH message, the enterprise broker re-registers the edge node, and the queue drains. The operator sees all data with correct timestamps — no gaps, no re-sampling.
Hardening for OT
A Docker stack on a plant floor needs to survive in an environment where:
- The IT department has no visibility into OT assets
- The IT department does not approve software installations on OT networks
- The site is subject to NATA/SOC audits and the system needs to be in scope
- There is no internet connectivity at the edge node (air-gapped or satellite-only)
Network Segmentation (Purdue Model Alignment)
The edge Docker host sits at Level 2 (Area Supervisory) per the Purdue Enterprise Reference Architecture. It communicates upward (to the enterprise MQTT broker) over a demilitarised zone. It does not communicate with Level 1 PLCs over any routed path — EtherNet/IP traffic stays on the flat OT subnet.
On the firewall:
- Outbound (from edge node): 443/TCP to cloud MQTT endpoint only. All other outbound blocked.
- Inbound (to edge node): Nothing. The edge node does not accept inbound connections from the corporate network.
- Lateral (within OT subnet): EtherNet/IP on port 44818. No restrictions on OT-switched traffic.
The Docker host runs with a static IP on the OT subnet. No DHCP. No zerotrust VLAN tagging on the switch port (IEEE 802.1X preferred; MAC whitelisting acceptable).
Read-Only Volumes for OS Layers
Mount /var/log, /tmp, and system directories as read-only or tmpfs volumes. The Docker daemon and container runtimes should be the only write paths to persistent storage.
# Example: run a container with read-only root filesystem services: telegraf: read_only: true tmpfs: - /tmp:size=100M,mode=1777 - /run:size=50M,mode=1777 volumes: - telegraf-buffer:/var/lib/telegraf - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
Image Signing and Registry Hardening
Use Cosign (Sigstore) to sign images at build time and verify at deployment:
# Build and sign cosign sign --key cosign.key emqx/emqx:5.8 # Verify at deploy cosign verify --key cosign.pub docker.io/emqx/emqx:5.8
Pull images from an air-gapped registry mirror (Harbor or Quay) rather than Docker Hub. Set imagePullPolicy: Always with an explicit registry URL to prevent accidental pulls from the internet.
Keep images updated. Subscribe to CVE feeds for EMQX, Telegraf, and NGINX. Patch monthly, test in staging, deploy during FIFO windows.
No-Internet Edge Nodes
For truly air-gapped sites, run a local Harbor registry on the same Docker host or a co-located server. Sync images from Docker Hub on a monthly schedule during office-hours connectivity windows.
# Harbor air-gap sync (run monthly from office network) harbor sync --source docker.io --image emqx/emqx --tag 5.8
The edge node should have no default route to the internet. Check with ip route and verify that 0.0.0.0/0 is not pointing at a WAN interface.
Vendor Tunnel Discipline
If a vendor (Inductive Automation, OSIsoft, AVEVA) has a remote support tunnel (TeamViewer, Splashtop, vendor VPN), enforce:
- Tunnel-only VLAN: Vendor access is isolated to a dedicated VLAN with no access to the OT subnet.
- Time-bounded: Tunnels activate only during agreed maintenance windows with a maximum session duration.
- Audit log: All vendor tunnel activity logged to a SIEM or syslog sink.
- No persistent credentials: Vendor accounts with MFA, removed after each session.
What We'd Assess in a Free RATECH Pilot
The RATECH pilot is a free on-site OT assessment that maps your current historian and dashboard infrastructure to a Docker-based architecture. On-site means on-site — Dampier, Cape Lambert, Newman, or any Pilbara location.
Here's what we'd actually evaluate during the 2-day visit:
1. Network Readiness
We'd run a full OT network topology scan (passive — no packets sent to PLCs) to map:
- IP subnets and VLAN structure
- Firewall rules governing edge node egress
- Satellite link capacity and latency profile
- PI server and SCADA server addresses, patch levels, and backup status
This gives us a real map of the current state. Not assumptions — actual scans.
2. Container Host Candidates
We'd identify candidate industrial PCs for Docker hosting — Advantech, Beckhoff, or existing SCADA server hardware that can run Docker. We check:
- CPU architecture and available RAM
- Disk health and available space
- OS version and patch level (Windows 10 IoT LTSC or Ubuntu 22.04 LTS preferred)
- Existing services that would conflict with Docker networking
If no candidates exist, we scope a hardware recommendation as part of the pilot output.
3. Data Contract for Historian Write-Back
We'd identify the target write-back pathway — how does the Docker stack deliver data to PI or the existing historian? This means:
- Mapping existing PI tags that need to be extended with new real-time streams
- Identifying the PI Connector for MQTT version and licensing status
- Defining the data contract: tag naming, scan rate, units, alarm boundaries
- Documenting any existing custom tag providers or data bridges we need to work around
The output is a one-page data contract that the OT team and IT team both sign off on before any implementation work begins.