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:

EDGE SITE (Pilbara) L0 — FIELD Sensors / VFDs L1 — BASIC CONTROL Allen-Bradley ControlLogix PLCs L2 — AREA SUPERVISORY Inductive Automation Ignition Edge EMQX Edge Broker Store-and-forward queue Sparkplug B / MQTT Telegraf (store-and-forward) Local buffer · resumes on reconnect TimescaleDB (local historian) Tag archive · compressed chunks 4Mbps / 600ms Satellite ENTERPRISE (Cloud / DC) EMQX Cloud or AWS IoT Core TLS · MQTT bridge OSIsoft PI Server PI Connector for MQTT or PI Web API bridge TimescaleDB (parallel) Native Sparkplug B ingest PI Asset Framework Existing tag namespace Grafana (enterprise) PI SQL Builder + direct TS Grafana (edge site) Grafana Mobile · read-only NGINX (edge LAN) Reverse proxy · TLS termination Remote site dashboards Read-only · satellite-tolerant Telemetry PI write-back EtherNet/IP Store-and-forward is the key property Queues on disconnect · drains on reconnect No data loss · correct timestamps Edge-to-Enterprise Data Flow — Pilbara Iron Ore — Docker Stack / MQTT / TimescaleDB
Figure 1: Data path from PLC register to enterprise historian. Telegraf queues on satellite disconnect; EMQX edge broker re-registers on reconnect with full queue drain.

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:

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:

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:

  1. Tunnel-only VLAN: Vendor access is isolated to a dedicated VLAN with no access to the OT subnet.
  2. Time-bounded: Tunnels activate only during agreed maintenance windows with a maximum session duration.
  3. Audit log: All vendor tunnel activity logged to a SIEM or syslog sink.
  4. 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:

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:

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:

The output is a one-page data contract that the OT team and IT team both sign off on before any implementation work begins.