← Back to Home

The headless edge computing platform for FactoryTalk Optix — documentation, presentations, and container support.

Technical Presentation

Docker Integration

Deploy your first container on your OptixEdge

Follow this tutorial to deploy a MySQL database with phpMyAdmin on OptixEdge using Docker. The procedure is based on the OptixEdge & Docker Containers presentation (page 42 onward).

Before loading any container:
  1. Install Docker (or Docker Desktop) on your PC or virtual machine.
  2. Plug your OptixEdge, open https://optix_edge_ip_address, sign in, go to Docker, enable Docker Engine under Services, then reboot OptixEdge.
  3. Storage choice: containers can run on the internal eMMC or on an external Micro SD. To change, go to Docker > Container Storage and set Data Root from Internal to Micro SD.
Tip: Portainer doesn't have to run on the OptixEdge itself. You can install Portainer on any machine and add one or more OptixEdge devices as remote environments. In Portainer, an environment is a Docker endpoint you manage — local or remote — so a single Portainer instance can control multiple OptixEdge units from one dashboard.

Deploy with Portainer

  1. In System Manager, go to Docker and enable Embedded Portainer CE under Services.
  2. Reboot OptixEdge.
  3. Open https://optix_edge_ip_address:9443 and trust the self-signed certificate.
  4. Set the admin password at first login.
  5. Go to "Home" and select the local environment, open Stacks, then click Add stack.
  6. Name the stack (e.g., my_sql_stack).
  7. Paste the following content into the web editor and click Deploy the stack:
    services:
      mysql:
        image: mysql:8
        container_name: mysql-retention
        environment:
          MYSQL_ROOT_PASSWORD: Password01 # Change to a strong password
          MYSQL_DATABASE: example_db
          MYSQL_USER: user
          MYSQL_PASSWORD: Password01 # Change to a strong password
        ports:
          - "3306:3306"
        volumes:
          - mysql_data:/var/lib/mysql
        restart: unless-stopped
    
      phpmyadmin:
        image: phpmyadmin:latest
        container_name: phpmyadmin
        depends_on:
          - mysql
        environment:
          PMA_HOST: mysql
          PMA_PORT: 3306
          MYSQL_ROOT_PASSWORD: Password01 # Keep in sync with MYSQL_ROOT_PASSWORD
        ports:
          - "8090:80"
        restart: unless-stopped
    
    volumes:
      mysql_data:
  8. Wait for both containers to show as running in the Portainer dashboard.
  9. Open http://optix_edge_ip_address:8090 in your browser.
  10. Log in with user root and password Password01 (or with user user and the same password).
  11. Verify that the example_db database is listed in the left panel.

Load from USB key

Note: USB import accepts one .tar file and one docker-compose.yaml. For multi-container deployments, include all required images in the same .tar bundle.

MySQL + phpMyAdmin: deploy a MySQL database with a phpMyAdmin web front-end.

  1. Use a USB key formatted as FAT32, exFAT, or ext4.
  2. On your PC, open a terminal and pull both images for linux/arm64:
    docker pull --platform linux/arm64 mysql:8
    docker pull --platform linux/arm64 phpmyadmin:latest
  3. Export both images into a single .tar file:
    docker save -o mysql_phpmyadmin.tar mysql:8 phpmyadmin:latest
  4. Create a file named docker-compose.yaml with this content:
    services:
      mysql:
        image: mysql:8
        container_name: mysql-retention
        environment:
          MYSQL_ROOT_PASSWORD: Password01 # Change to a strong password
          MYSQL_DATABASE: example_db
          MYSQL_USER: user
          MYSQL_PASSWORD: Password01 # Change to a strong password
        ports:
          - "3306:3306"
        volumes:
          - mysql_data:/var/lib/mysql
        restart: unless-stopped
    
      phpmyadmin:
        image: phpmyadmin:latest
        container_name: phpmyadmin
        depends_on:
          - mysql
        environment:
          PMA_HOST: mysql
          PMA_PORT: 3306
          MYSQL_ROOT_PASSWORD: Password01 # Keep in sync with MYSQL_ROOT_PASSWORD
        ports:
          - "8090:80"
        restart: unless-stopped
    
    volumes:
      mysql_data:
  5. Copy the two files (mysql_phpmyadmin.tar, docker-compose.yaml) to the USB key root directory.
  6. Plug the USB key into OptixEdge.
  7. In System Manager, open Docker and click Import docker image via USB in the Containers area.
  8. Verify that both containers are running, MySQL is listening on port 3306, and phpMyAdmin is reachable at http://optix_edge_ip_address:8090.

Mosquitto: deploy an Eclipse Mosquitto MQTT broker for lightweight messaging between devices and services.

  1. Use a USB key formatted as FAT32, exFAT, or ext4.
  2. On your PC, open a terminal and pull the Mosquitto image for linux/arm64:
    docker pull --platform linux/arm64 eclipse-mosquitto:2
  3. Export the image into a .tar file (remember where you save it, as you'll need to copy it to the USB key):
    docker save -o mosquitto.tar eclipse-mosquitto:2
  4. Create a file named docker-compose.yaml with this content:
    services:
      mosquitto:
        image: eclipse-mosquitto:2
        container_name: mosquitto
        ports:
          - "1883:1883"
          - "9001:9001"
        volumes:
          - mosquitto_config:/mosquitto/config
          - mosquitto_data:/mosquitto/data
          - mosquitto_log:/mosquitto/log
        restart: unless-stopped
    
    volumes:
      mosquitto_config:
      mosquitto_data:
      mosquitto_log:
  5. Copy the two files (mosquitto.tar, docker-compose.yaml) to the USB key root directory.
  6. Plug the USB key into OptixEdge.
  7. In System Manager, open Docker and click Import docker image via USB in the Containers area.
  8. Verify that the container is running and Mosquitto is listening on port 1883 (MQTT) and port 9001 (WebSockets).

Deploy with Docker CLI remote management

  1. In System Manager, under Docker > Remote Management, choose the connection type and allowed network interfaces.
  2. On your local machine, point the Docker CLI at OptixEdge. Linux / macOS
    export DOCKER_HOST=tcp://optix_edge_ip:2375
    Windows PowerShell
    $env:DOCKER_HOST = "tcp://optix_edge_ip:2375"
  3. Verify the connection by listing running containers:
    docker ps
  4. Run any Docker command as if you were on the OptixEdge device (e.g., docker compose up -d).
  5. When finished, restore your local Docker context: Linux / macOS
    unset DOCKER_HOST
    Windows PowerShell
    Remove-Item Env:DOCKER_HOST

Go Beyond: more exercise ideas

Kuma container: run Uptime Kuma to monitor service uptime with a simple web dashboard.

Portainer stack
version: '3.8'

services:
  kuma:
    image: docker.io/louislam/uptime-kuma:1
    container_name: kuma
    ports:
      - "3001:3001"
    volumes:
      - kuma_data:/app/data
    restart: unless-stopped

volumes:
  kuma_data:

Mosquitto: deploy an MQTT broker for lightweight messaging between devices and services.

Portainer stack
version: '3.8'

services:
  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mosquitto
    ports:
      - "1883:1883"
      - "9001:9001"
    restart: unless-stopped

Prometheus + Node Exporter: monitor CPU and RAM usage of the OptixEdge device.

Portainer stack
version: '3.8'

services:

  # --- INIT CONTAINER ---
  # This tiny container runs once at startup to create the Prometheus
  # configuration file. It writes the config into a shared volume,
  # then exits. This is needed because Prometheus needs a config file
  # to know which services to monitor (called "scrape targets").
  prometheus-init:
    image: busybox:latest              # Minimal Linux image (~1MB), used only to write a file
    container_name: prometheus-init
    volumes:
      - prometheus_config:/config      # Shared volume where the config file will be written
    entrypoint: ["/bin/sh", "-c"]      # Run a shell command
    command:
      - |
        cat > /config/prometheus.yml << 'EOF'
        global:
          scrape_interval: 5s          # How often Prometheus collects metrics (every 5 seconds)
        scrape_configs:
          - job_name: prometheus        # Prometheus monitors itself
            static_configs:
              - targets: ['localhost:9090']
          - job_name: node_exporter     # Node Exporter provides device metrics (CPU, RAM, disk, etc.)
            static_configs:
              - targets: ['node-exporter:9100']
        EOF
    restart: "no"                      # Run only once, do not restart after exiting

  # --- PROMETHEUS ---
  # The metrics database. It periodically collects (scrapes) metrics
  # from the targets defined in the config file, stores them, and
  # provides a web UI to query and graph them.
  # Web UI available at: http://<device-ip>:9090
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    depends_on:
      prometheus-init:
        condition: service_completed_successfully  # Wait for the config file to be created first
    ports:
      - "9090:9090"                    # Expose Prometheus web UI on port 9090
    command:
      - --config.file=/config/prometheus.yml       # Path to the config created by the init container
      - --storage.tsdb.path=/prometheus            # Where to store collected metrics data
      - --web.listen-address=0.0.0.0:9090          # Listen on all network interfaces
    volumes:
      - prometheus_data:/prometheus                 # Persistent storage for metrics history
      - prometheus_config:/config:ro                # Read the config file (read-only)
    restart: unless-stopped            # Auto-restart unless manually stopped

  # --- NODE EXPORTER ---
  # A lightweight agent that reads system metrics from the device
  # (CPU usage, RAM, disk space, network traffic, etc.) and exposes
  # them on port 9100 for Prometheus to collect.
  # Raw metrics visible at: http://<device-ip>:9100/metrics
  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - "9100:9100"                    # Expose metrics endpoint on port 9100
    restart: unless-stopped

# --- VOLUMES ---
# Named volumes persist data across container restarts and recreations.
volumes:
  prometheus_data:                     # Stores Prometheus metrics history
  prometheus_config:                   # Stores the generated config file

Check RAM and CPU in Prometheus

  1. Open http://optix_edge_ip_address:9090.
  2. In the Expression field, paste one query and click Execute.
  3. Use Graph view to see trends over time.
CPU usage (%)
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
RAM usage (%)
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100

Apache Guacamole: deploy a clientless remote desktop gateway that supports VNC, RDP, and SSH protocols. Access remote desktops from any device using only a web browser — no client software required.

Portainer stack
services:
  postgres:
    image: docker.io/postgres:16-alpine
    container_name: guac-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10

  guacd:
    image: docker.io/guacamole/guacd:1.6.0
    container_name: guac-guacd
    restart: unless-stopped

  guacamole:
    image: docker.io/guacamole/guacamole:1.6.0
    container_name: guac-web
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
      guacd:
        condition: service_started
    environment:
      GUACD_HOSTNAME: guacd
      POSTGRESQL_HOSTNAME: postgres
      POSTGRESQL_PORT: 5432
      POSTGRESQL_DATABASE: ${POSTGRES_DB}
      POSTGRESQL_USER: ${POSTGRES_USER}
      POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRESQL_AUTO_CREATE_ACCOUNTS: "true"
    ports:
      - "8080:8080"

volumes:
  postgres_data:

Environment Variables

In Portainer, add these environment variables when deploying the stack:

POSTGRES_DB=guacamole_db
POSTGRES_USER=guacamole_user
POSTGRES_PASSWORD=YourSecurePassword

Access the Web UI

  1. Open http://optix_edge_ip_address:8080/guacamole in your browser.
  2. Log in with the default credentials: guacadmin / guacadmin.
  3. Change the admin password immediately after first login.
  4. Add connections to your VNC, RDP, or SSH targets via Settings > Connections.

Tips for production-ready compose files

CPU and memory limits

Add a deploy.resources.limits block to each service to prevent a container from consuming all device resources and affecting FTOptix performance.

Example: MySQL service with resource limits
services:
  mysql:
    image: mysql:8
    container_name: mysql-retention
    ports:
      - "3306:3306"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "0.50"
          memory: "256M" // RAM

Log rotation

Add a logging block to each service to cap log file size and count, preventing local storage from filling up.

Example: MySQL service with log rotation
services:
  mysql:
    image: mysql:8
    container_name: mysql-retention
    ports:
      - "3306:3306"
    restart: unless-stopped
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Both combined

A complete service definition with both resource limits and log rotation applied.

Full example
services:
  mysql:
    image: mysql:8
    container_name: mysql-retention
    ports:
      - "3306:3306"
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "0.50"
          memory: "256M"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"