Installation & Deployment

Overview

This guide walks you through installing and running the IoT Emulator — a Spring Boot application (Java 25) that simulates IoT devices across multiple protocols. Docker is the recommended deployment method: it ships a self-contained image that runs the emulator in headless web mode and exposes the browser-based control panel and REST API on a single port.

The deployment consists of:

  • IoT Emulator — Spring Boot service started with --web-mode (no desktop/JavaFX GUI), serving the Web UI, the REST API and Swagger UI.

  • Docker container — pre-built image from the Harbor registry or loaded from an archive.

  • Configuration directory — /app/configuration inside the container, holding protocol resource files (*_resources.xml), GUI/property files, rule scripts and snapshots. It is baked into the image and can optionally be mounted as a volume for persistence and customization.

The emulator runs as a single container. It does not require its own database to start. A database connection is only needed for the optional Extract CPE from DB feature (see Extract CPE from DB), and network access to a target server (ACS, LWM2M server, MQTT broker, …​) is needed only when you actually start a simulation against it.

Prerequisites

System Requirements

  • Docker Engine 20.10+

  • Docker Compose 2.0+ (optional — only for the Compose deployment option)

  • Minimum 1 GB RAM (2+ GB recommended; more for large device counts)

  • 500 MB free disk space for the image and logs

JDK 25 is only required for local Maven builds and for running the desktop GUI without Docker. Docker deployment does not need a JDK on the host machine.

Supported Operating Systems

  • Linux (recommended)

  • macOS

  • Windows (Docker Desktop, or native Java for the desktop GUI)

Before starting, make sure:

  • Docker Engine 20.10+ is installed. If Docker is not installed, follow the Docker Installation Guide.

  • You have network access to any target server you intend to emulate against (ACS / LWM2M server / MQTT broker / STOMP broker).

  • If you plan to use Extract CPE from DB, you have network access to the ACS database (MySQL).

Quick Start

For experienced users who already have Docker installed. The image ships with a complete default configuration directory, so no extra files are required to start.

# 1. Authenticate with the registry (first time only)
docker login hub.friendly-tech.com

# 2. Run the emulator (the image is pulled automatically if missing)
docker run -d \
  --name iot-emulator \
  -p 8556:8556 \
  hub.friendly-tech.com/emulator/iot:latest

# 3. Verify
curl -s http://localhost:8556/api/status

After startup the Web UI is available at http://localhost:8556.

For repeatable deployments and upgrades that preserve your settings, snapshots and rule scripts, use the bundled run-docker.sh bootstrap script instead of a raw docker run — see Deployment.

Getting the Docker Image

Option A: Pull from Harbor Registry (recommended)

# Authenticate with the registry
docker login hub.friendly-tech.com

# Pull the latest image (optional -- "docker run" / "docker compose up"
# will pull it automatically on first use)
docker pull hub.friendly-tech.com/emulator/iot:latest

To pin to a specific release tag instead of latest:

docker pull hub.friendly-tech.com/emulator/iot:<version>

<version> is an example placeholder. Always use the tag that corresponds to your deployment. To request Harbor access, contact the DevOps team for a user account or robot token.

Option B: Transfer Image to an Offline Server

Use this option when the target server has no internet access and cannot pull images from Harbor directly. The image is pulled on a machine that does have Harbor access, exported to a tar archive, transferred to the offline server, and loaded there.

  1. On a machine with Harbor access, log in and pull the image for the target server’s architecture:

    docker login hub.friendly-tech.com
    docker pull --platform linux/amd64 \
      hub.friendly-tech.com/emulator/iot:latest

    An explicit --platform matching the offline target server’s architecture is required if the image is multi-arch. Without --platform, docker pull selects the host architecture, which may not match the target (for example, an Apple Silicon arm64 Mac would produce an arm64 archive that fails on amd64 servers). Replace linux/amd64 with the platform of your offline target (linux/arm64, etc.).

  2. Save the pulled image to a tar archive and compress it:

    docker save hub.friendly-tech.com/emulator/iot:latest \
      -o iot-emulator.tar
    gzip iot-emulator.tar
  3. Transfer iot-emulator.tar.gz to the offline server (e.g., via scp or removable media).

  4. On the offline server, load the image:

    gzip -dc iot-emulator.tar.gz | docker load

Option C: Build from Source (developers only)

# Build the application JAR (Java 25 + Maven)
mvn clean package -DskipTests

# Build the Docker image (uses the bundled Dockerfile)
docker build -t iot-emulator:latest .

The Dockerfile copies gui/target/.jar and the gui/configuration directory into the image, so mvn package must run *before docker build.

After loading, pulling or building, verify the image is available:

docker images | grep iot

Configuration Directory

The configuration directory contains every file the emulator needs at runtime. It is baked into the image at /app/configuration/; when building from source the same files live in gui/configuration/.

configuration/
├── gui_connection_options.properties
├── lwm2m_gui.properties
├── lwm2m_controls_names.properties
├── mqtt_gui.properties
├── mqtt_controls_names.properties
├── usp_gui.properties
├── usp_controls_names.properties
├── fsh_hub_gui.properties
├── fsh_hub_controls_names.properties
├── profiles.properties
├── sensor_info.properties
├── metering_plugin_db_connection.properties
├── rules.js
├── snapshot.json
├── lwm2m_resources.xml
├── mqtt_resources.xml
├── usp_resources.xml
└── fsh_hub_resources.xml
File Description

gui_connection_options.properties

Main connection settings: server/client IP, port, security (PSK/X.509), protocol selection, GUI runtime flags (gui_rendering_type, client.logging, client.update_param, fail.response), and ACS DB credentials used by Extract CPE from DB (acs.ip, acs.port, acs.login, acs.password, acs.schema, acs.serial). Holds persisted user state.

*_gui.properties

Protocol-specific settings (LWM2M, MQTT, USP, FSH Hub)

*_controls_names.properties

GUI control labels for each protocol

profiles.properties

Active plugin/profile configuration

sensor_info.properties

Device sensor definitions

metering_plugin_db_connection.properties

Database connection for the metering plugin

rules.js

Rule engine script — body of onParamUpdated(param, device, ctx) (user state)

snapshot.json

Saved device list + parameter tree (user state)

*_resources.xml

Device data model definitions (LWM2M, MQTT, USP, FSH Hub). The active file is selected from the Config Files dropdown in the Web UI.

A bind mount on /app/configuration replaces the entire built-in directory. If you mount a partial directory (for example only the .xml files), the emulator will fail to start because the required .properties files are missing. To customize safely, first extract the full default directory from the image, edit it, then mount your complete copy — or use run-docker.sh, which does this overlay for you (see Deployment).

Deployment

The repository ships an idempotent bootstrap script, run-docker.sh. It is safe to re-run on every release: it pulls the latest image, overlays new/renamed template files (protocol options, resource files, control names) into ./configuration, and preserves user state — persisted settings, snapshots, rule scripts and any user-uploaded files such as TLS keystores — then recreates the container.

# From the directory where you want ./configuration to live
./run-docker.sh

What it does, in order:

  1. Pulls hub.friendly-tech.com/emulator/iot:latest.

  2. Extracts the image’s default configuration into a staging directory.

  3. Overlays template files (*_gui.properties, *_resources.xml, *_controls_names.properties, profiles.properties, sensor_info.properties, metering_plugin_db_connection.properties) onto ./configuration.

  4. Seeds user-state files (gui_connection_options.properties, snapshot.json, rules.js) only on first run — never overwriting them afterwards.

  5. Recreates the iot-emulator container.

By default run-docker.sh binds the UI to 127.0.0.1:8556 (loopback only), sets FT_TLS_AUTH=0 (server-only TLS for the USP MQTT MTP) and raises the JVM heap (-Xms1024m -Xmx16384m). Edit the variables at the top of the script (IMAGE, NAME, DATA) and the final docker run block to suit your environment — for example, change the port binding to -p 8556:8556 to expose the UI on all interfaces.

Option 2: docker run

Run the image directly. The built-in configuration is used unless you mount your own.

docker run -d \
  --name iot-emulator \
  -p 8556:8556 \
  --restart unless-stopped \
  hub.friendly-tech.com/emulator/iot:latest

To customize configuration, extract the defaults first and mount your complete copy:

# 1. Extract the default configuration from the image
docker create --name tmp-emulator hub.friendly-tech.com/emulator/iot:latest
docker cp tmp-emulator:/app/configuration ./configuration
docker rm tmp-emulator

# 2. Edit files in ./configuration as needed, then run with the volume mounted
docker run -d \
  --name iot-emulator \
  -p 8556:8556 \
  -v "$(pwd)/configuration:/app/configuration" \
  --restart unless-stopped \
  hub.friendly-tech.com/emulator/iot:latest

To override runtime behaviour, pass environment variables with -e (see Environment Variables):

docker run -d \
  --name iot-emulator \
  -p 8556:8556 \
  -e JAVA_OPTS="-Xms1g -Xmx4g" \
  -e FT_TLS_AUTH=0 \
  hub.friendly-tech.com/emulator/iot:latest

Option 3: Docker Compose

Create a docker-compose.yml file:

services:
  iot-emulator:
    image: hub.friendly-tech.com/emulator/iot:latest
    container_name: iot-emulator
    ports:
      - "8556:8556"          # <host-port>:<container-port> -- change the host
                              # port if 8556 is in use (e.g. "9556:8556")
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - JAVA_OPTS=-Xms512m -Xmx1g
      # USP MQTTv5 MTP TLS mode: 1 = mutual auth (default), 0 = server auth only
      - FT_TLS_AUTH=1
    # Uncomment to customize configuration. The mounted directory REPLACES the
    # built-in one, so it must contain ALL required files (see warning above).
    # volumes:
    #   - ./configuration:/app/configuration
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8556/api/status"]
      interval: 30s
      timeout: 3s
      start_period: 30s
      retries: 3

Start with:

docker compose up -d
docker compose logs -f iot-emulator

Verify Installation

After starting the container, run the following checks:

# 1. Check container status
docker ps -f name=iot-emulator
# Expected: container "Up" with port 0.0.0.0:8556->8556/tcp

# 2. Status endpoint
curl -s http://localhost:8556/api/status
# Expected JSON, e.g.:
# {"protocol":"LWM2M","initialized":false,"started":false,"deviceCount":0}

# 3. Application logs
docker logs iot-emulator --tail 50
# Look for the Spring Boot "Started ..." line

# 4. Web UI (open in a browser)
# http://localhost:8556

# 5. Swagger UI (open in a browser)
# http://localhost:8556/swagger-ui.html

Environment Variables

These variables are baked into the image with sensible defaults and can be overridden at runtime with -e / environment:.

Variable Description Default Required

SERVER_PORT

HTTP port the emulator listens on inside the container

8556

No

SPRING_PROFILES_ACTIVE

Active Spring profile

prod

No

JAVA_OPTS

JVM options (heap sizing, GC, heap-dump on OOM). Increase the heap for large device counts.

-Xms512m -Xmx1g -XX:+UseG1GC …​

No

FT_TLS_AUTH

USP MQTTv5 MTP TLS mode: 1 = mutual auth (client presents its certificate), 0 = server-only auth. See TLS Authentication Mode.

1

No

TZ

Container timezone

UTC

No

JDK_JAVA_OPTIONS

JVM options auto-prepended by the launcher (opens sun.security.ssl for reflective TLS code paths). Normally leave as-is.

--add-opens=java.base/sun.security.ssl=ALL-UNNAMED

No

SERVER_IP

Headless mode only. Overrides the target server IP at startup, without editing gui_connection_options.properties. Useful for Kubernetes/CI where the target is injected.

(unset)

No

SERVER_TARGET_PORT

Headless mode only. Overrides the target server port at startup. Applied together with SERVER_IP.

(unset)

No

SERVER_IP and SERVER_TARGET_PORT are read only when the emulator starts in headless web mode (--web-mode, which is how the Docker image runs). When set, they pre-populate the target connection so the container can connect to a server supplied at deploy time without a mounted configuration file.

SERVER_PORT changes the port inside the container. To reach it on a different host port, change only the left side of the Docker port mapping (e.g. -p 9556:8556); the container port stays 8556 unless you also change SERVER_PORT.

Application Properties

Beyond environment variables, runtime behaviour is driven by the files in the configuration directory. Most of these are managed for you through the Web UI and persisted automatically (see User Guide); you rarely need to edit them by hand.

TLS Authentication Mode

The emulator can switch between TLS mutual authentication and server-only authentication for the USP MQTTv5 MTP. It is controlled by the FT_TLS_AUTH environment variable (mapped to the JVM system property ft.tls.auth):

Value Behaviour

1 (default)

Mutual TLS authentication. The emulator presents its client certificate to the MQTT broker in addition to validating the server certificate.

0

Server-only authentication. The emulator validates the broker certificate but does not send a client certificate.

This setting currently affects only the USP MQTTv5 MTP. Other protocols ignore it and may use their own TLS configuration.

Docker:

docker run -d \
  --name iot-emulator \
  -p 8556:8556 \
  -e FT_TLS_AUTH=0 \
  hub.friendly-tech.com/emulator/iot:latest

Manual / run.sh / run.bat: export FT_TLS_AUTH before launching, or pass -Dft.tls.auth=0 directly to the JVM:

FT_TLS_AUTH=0 ./run.sh
# or
java -Dft.tls.auth=0 -jar gui/target/iot-emulator.jar --web-mode

XML Resource Files

Resource files define the device data model. The active file is chosen from the Config Files dropdown in the Web UI; new ones can be uploaded there. Example structure:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <resource>
        <path>Device.DeviceInfo.Manufacturer</path>
        <value>MyCompany</value>
        <type>string</type>
        <writable>false</writable>
    </resource>
    <!-- More resources... -->
</resources>

Manual Installation (without Docker)

Use this when you want to run the emulator directly on a host with Java 25 installed — either in headless web mode or as the desktop GUI.

Build from Source

  1. Clone the repository:

    git clone https://github.com/friendly-tech/iot-emulator.git
    cd iot-emulator
  2. Build with Maven:

    mvn clean package -DskipTests

Run in Web Mode (headless)

Web mode serves the same browser UI and REST API as the Docker container — ideal for servers without a display.

java -jar gui/target/iot-emulator.jar --web-mode

Run the Desktop GUI (JavaFX)

The assembled distribution includes launch scripts that start the native JavaFX desktop application:

# Linux / macOS
./run.sh

# Windows
run.bat

The desktop GUI requires JavaFX modules, which are bundled in the assembly under bin/. Set FT_TLS_AUTH before launching to control the USP MQTT TLS mode (see TLS Authentication Mode).

Container Management

Command Description

docker run -d --name iot-emulator -p 8556:8556 <image>

Start the emulator in the background

docker stop iot-emulator / docker start iot-emulator

Stop / start the container

docker restart iot-emulator

Restart the application

docker logs -f iot-emulator

Follow application logs

docker logs --tail 100 iot-emulator

View the last 100 log lines

docker exec -it iot-emulator sh

Open a shell inside the container

docker exec iot-emulator ls -la /app/configuration

List the active configuration files

docker stats iot-emulator --no-stream

View CPU / memory usage

docker inspect iot-emulator --format '{{json .Config.Env}}'

Show the container’s environment variables

Updating

Re-running the bootstrap script is the simplest upgrade path. It pulls the new image, propagates new template files, keeps your settings/snapshots/rules, and recreates the container.

# (Optional) back up your configuration first
tar -czf emulator-config-$(date +%Y%m%d).tar.gz configuration/

./run-docker.sh

Via plain Docker

# 1. Pull the new image
docker pull hub.friendly-tech.com/emulator/iot:latest

# 2. Recreate the container
docker rm -f iot-emulator
docker run -d \
  --name iot-emulator \
  -p 8556:8556 \
  -v "$(pwd)/configuration:/app/configuration" \
  --restart unless-stopped \
  hub.friendly-tech.com/emulator/iot:latest

# 3. Verify
curl -s http://localhost:8556/api/status

If you are not mounting a configuration volume, recreating the container resets the configuration to the image defaults (snapshots and rule edits made through the UI are lost). Mount a volume, or use run-docker.sh, to keep user state across updates.

Rollback

docker rm -f iot-emulator

# Re-run with the previous image tag
docker run -d \
  --name iot-emulator \
  -p 8556:8556 \
  -v "$(pwd)/configuration:/app/configuration" \
  --restart unless-stopped \
  hub.friendly-tech.com/emulator/iot:<previous-version-tag>

Production / QA Checklist

# Item Notes

1

Restart policy

Use --restart unless-stopped (or restart: unless-stopped in Compose)

2

Configuration persistence

Mount a configuration volume, or deploy via run-docker.sh, so snapshots and rules survive restarts/updates

3

Heap sizing

Raise JAVA_OPTS (-Xmx) when simulating large device counts

4

Network exposure

Bind to loopback (-p 127.0.0.1:8556:8556) on shared hosts; expose on all interfaces only when needed

5

Health check

Configure a healthcheck against /api/status (already in the Compose example)

6

Timezone

Set TZ if logs must use a specific timezone (defaults to UTC)

7

Log management

Application logs go to /app/logs inside the container; mount it as a volume if you need them on the host

Troubleshooting

Start with quick diagnostics:

docker logs iot-emulator --tail 100
docker inspect iot-emulator --format '{{json .Config.Env}}'
docker exec iot-emulator ls -la /app/configuration

Common Issues

Container exits immediately / fails to start

Symptom: The container is not in the Up state, and logs mention a missing file under /app/configuration.

Check:

docker exec iot-emulator ls /app/configuration

Solution:

  • If you mounted a configuration volume, ensure it contains all required files (both .properties and .xml) — a bind mount replaces the entire built-in directory.

  • Re-extract the defaults (see Deployment, Option 2) or deploy via run-docker.sh.

Web UI not reachable on port 8556

Symptom: The browser cannot connect to http://localhost:8556.

Check:

docker ps -f name=iot-emulator      # is the port mapped?
curl -s http://localhost:8556/api/status

Solution:

  • Confirm the port mapping (-p 8556:8556).

  • If you deployed with run-docker.sh, the UI is bound to 127.0.0.1 only — access it from the host itself or change the binding to -p 8556:8556.

Port conflict (address already in use)

Symptom: The container fails to start because port 8556 is taken.

Check:

docker ps
lsof -i :8556     # Linux/macOS

Solution:

  • Stop the conflicting service, or publish a different host port: -p 9556:8556.

OutOfMemoryError with many devices

Symptom: A heap dump appears at /app/logs/heapdump.hprof; logs show OutOfMemoryError.

Solution:

  • Increase the JVM heap via JAVA_OPTS, e.g. -e JAVA_OPTS="-Xms1g -Xmx4g".

USP/MQTT TLS handshake fails

Symptom: The USP agent cannot connect to the MQTT broker over TLS.

Solution:

  • If the broker does not require client certificates, switch to server-only auth with -e FT_TLS_AUTH=0 (see TLS Authentication Mode).

  • Verify the keystore/truststore paths and the broker certificate chain.

Port Reference

Port Protocol Description

8556

HTTP

Web UI, REST API, and Swagger UI

Protocol traffic (CoAP/LWM2M 5683/5684, MQTT 1883/8883, USP MTP ports, …​) is outbound from the emulator to the target server, so it does not need to be published from the container. See Supported Protocols for protocol-specific defaults.