Installation & Deployment
Overview
This guide walks you through installing and running the IoT Emulator — a Spring Boot application (Java 25 bytecode, runs on JDK 27 in the image) 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/configurationinside 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.
The Docker image runs on JDK 27 ( |
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).
Third-Party Components
Every third-party product the emulator is built or run against. Values are read off this
repository — the root pom.xml, the per-protocol module poms, and Dockerfile.
The table below is this emulator’s own view. For the platform-wide picture — which third-party product version each published release and hot-fix was built against, across every FT service — see FT Version Control.
| Component | Technology / Version | Purpose |
|---|---|---|
Container runtime |
Docker Engine 20.10+, Docker Compose 2.0+ |
Recommended deployment method |
Base image |
|
Runtime image the emulator is built from ( |
Java |
25 |
Language level and runtime ( |
Application framework |
Spring Boot 4.0.0-SNAPSHOT |
Web UI and REST API ( |
springdoc-openapi |
|
Serves Swagger UI and the OpenAPI spec ( |
Apache Commons Lang |
3.17.0 |
Shared utility layer ( |
Apache Commons Collections |
4.5.0-M2 |
LRU-bounded session cache for the USP MTP ( |
Eclipse Californium |
2.0.0-M16 |
CoAP and DTLS transport, used by the CoAP, LwM2M and USP emulators |
Eclipse Leshan |
1.0.0-M12 |
LwM2M client implementation ( |
Eclipse Paho MQTT v3 |
1.2.0 |
MQTT transport ( |
Eclipse Paho MQTT v5 |
1.2.5 |
USP MQTTv5 MTP ( |
protobuf-java |
4.29.2 |
USP record and message encoding ( |
BouncyCastle |
1.81 ( |
TLS for the USP MQTTv5 MTP ( |
Relational database |
MySQL, used only by the optional Extract CPE from DB feature |
Read through |
MySQL driver |
|
JDBC access for Extract CPE from DB ( |
|
Four of these are not release versions: |
Beyond the optional MySQL connection above, the emulator needs no cache and no message broker of its own; the MQTT broker it talks to is part of the platform under test, not of this deployment.
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. The root URL serves a landing page where you choose Load Test Mode (/load) or Single Emulator (/single) — see Operating Modes.
|
For repeatable deployments and upgrades that preserve your settings, snapshots and rule scripts, use the |
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>
|
|
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.
-
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:latestAn explicit
--platformmatching the offline target server’s architecture is required if the image is multi-arch. Without--platform,docker pullselects 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). Replacelinux/amd64with the platform of your offline target (linux/arm64, etc.). -
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 -
Transfer
iot-emulator.tar.gzto the offline server (e.g., viascpor removable media). -
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 Dockerfile in the repository root)
docker build -t iot-emulator:latest .
|
The |
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 |
|---|---|
|
Main connection settings: server/client IP, port, security (PSK/X.509), protocol selection, GUI runtime flags ( |
|
Protocol-specific settings (LWM2M, MQTT, USP, FSH Hub) |
|
GUI control labels for each protocol |
|
Active plugin/profile configuration |
|
Device sensor definitions |
|
Database connection for the metering plugin |
|
Rule engine script — body of |
|
Saved device list + parameter tree (user state) |
|
Device data model definitions (LWM2M, MQTT, USP, FSH Hub). The active file is selected from the Device tree (XML) dropdown in the Web UI. |
|
A bind mount on |
Deployment
Docker Compose with a per-service environment file is the supported form, and it is what the
iot-emulator folder on FT_DISK ships: a single-service compose.yml in the platform layout and
iot-emulator/.env. run-docker.sh remains the fastest path for a developer machine, and plain
docker run the minimum.
FT_DISK Downloads
Download the following from
FT_DISK — iot-emulator (FT_DISK → RD → prerelease → iot-emulator):
| Component | Target | Files |
|---|---|---|
Stack definition |
|
|
IoT Emulator environment |
|
Only one file: the |
The emulator has no shared infrastructure of its own, so there is no root .env layer in this
deployment — the single iot-emulator/.env carries everything the container reads
(Environment Variables).
Option 1: Docker Compose (recommended)
Lay out the deployment directory. The platform layout puts it under /usr/local/ft-system/, the
same root every FT service uses; on a host of its own any directory works. The mounted
configuration directory replaces the built-in one, so populate it from the image before the
first start:
mkdir -p /usr/local/ft-system/iot-emulator/logs
cd /usr/local/ft-system
docker create --name tmp-emulator hub.friendly-tech.com/emulator/iot:latest
docker cp tmp-emulator:/app/configuration ./iot-emulator/configuration
docker rm tmp-emulator
The result:
/usr/local/ft-system/
├── compose.yml # from FT_DISK -- iot-emulator (or the FT System <vendor> Stack)
└── iot-emulator/
├── .env # per-service environment, from FT_DISK
├── configuration/ # -> /app/configuration
└── logs/ # -> /app/logs
A new image does not change the files in configuration/. After each update, refresh the template
files. Updating gives the steps.
compose.yml:
name: ft-iot-emulator
services:
iot-emulator:
image: hub.friendly-tech.com/emulator/iot:latest
container_name: iot-emulator
env_file:
- ./iot-emulator/.env
ports:
# Host port is literal on purpose. The emulator does not read it, and
# Compose interpolates ${...} only from the shell environment and from a
# .env in this directory -- never from the env_file above.
- "8556:8556"
volumes:
# The mounted directory REPLACES the built-in one, so it must contain
# every required file. Populate it from the image before the first start.
- ./iot-emulator/configuration:/app/configuration
- ./iot-emulator/logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8556/api/status"]
interval: 30s
timeout: 3s
start_period: 30s
retries: 3
restart: unless-stopped
iot-emulator/.env:
# ==========================================================
# IoT Emulator - per-service environment
# ==========================================================
# The only environment file this deployment uses. The emulator has no shared
# infrastructure of its own, so there is no root .env layer.
#
# The published host port is NOT here: it is a compose concern, not an
# application setting, and Compose does not interpolate env_file values anyway.
# Change it in compose.yml.
# HTTP port the emulator listens on inside the container
SERVER_PORT=8556
# Active Spring profile
SPRING_PROFILES_ACTIVE=prod
# Container timezone
TZ=UTC
# JVM options. Raise the heap for large device counts.
JAVA_OPTS=-Xms512m -Xmx1g -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/heapdump.hprof
# Auto-prepended by the launcher; opens sun.security.ssl for reflective TLS
# code paths. Normally leave as-is.
JDK_JAVA_OPTIONS=--add-opens=java.base/sun.security.ssl=ALL-UNNAMED
# USP MQTTv5 MTP TLS mode: 1 = mutual auth, 0 = server auth only
FT_TLS_AUTH=1
# Application log file inside the container
LOG_FILE=/app/logs/iot-emulator.log
# Headless mode only: override the target server at startup. Each is applied
# independently -- either can be set alone.
# SERVER_IP=
# SERVER_TARGET_PORT=
Start it:
docker compose up -d
docker compose logs -f iot-emulator
|
The published host port is not a To publish on a different host port, change the left-hand side in |
Adding the emulator to an existing FT stack
On a host that already runs the FT System stack, do not use the shipped compose.yml: it
declares its own Compose project (name: ft-iot-emulator) with its own network, so the stack
services are not reachable from it by name. The stack compose.yml from the FT System MySQL
Stack / FT System Oracle Stack folder already carries the iot-emulator block, on the shared
ft-network bridge:
iot-emulator:
image: hub.friendly-tech.com/emulator/iot:latest
container_name: iot-emulator
depends_on:
ftacs:
condition: service_healthy
env_file:
- ./iot-emulator/.env
environment:
TZ: ${TZ:-Europe/Kyiv} # the host-wide zone from the root .env; overrides TZ in iot-emulator/.env
JAVA_OPTS: -Xms512m -Xmx1g -XX:+UseG1GC
ports:
- "${IOT_EMUL_PORT:-8556}:8556"
restart: unless-stopped
profiles:
- testing
networks:
- ft-network
Only iot-emulator/.env from FT_DISK is needed there. The block is behind the testing
profile, so start it explicitly:
cd /usr/local/ft-system
docker compose --profile testing up -d iot-emulator
Note the differences from the single-service file: the published port comes from
IOT_EMUL_PORT in the root .env; TZ is taken from the root .env through the environment:
block (an env_file entry alone could not do that — iot-emulator/.env is loaded last and would
win with its own TZ=UTC); JAVA_OPTS is pinned in environment: as well and therefore
replaces the value in iot-emulator/.env, without the heap-dump flags; and neither the
configuration directory nor the logs are mounted — settings, snapshots and rule scripts live inside
the container and do not survive a recreation.
Option 2: run-docker.sh
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:
-
Pulls
hub.friendly-tech.com/emulator/iot:latest. -
Extracts the image’s default configuration into a staging directory.
-
Overlays template files (
*_gui.properties,*_resources.xml,*_controls_names.properties,profiles.properties,sensor_info.properties,metering_plugin_db_connection.properties) onto./configuration. -
Seeds user-state files (
gui_connection_options.properties,snapshot.json,rules.js) only on first run — never overwriting them afterwards. -
Recreates the
iot-emulatorcontainer.
|
The script deliberately differs from the Compose defaults above. It binds the UI to
|
Option 3: docker run
For a throwaway container. 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
The same environment file works here too, which keeps the forms in sync:
docker run -d \
--name iot-emulator \
-p 8556:8556 \
--env-file ./iot-emulator/.env \
-v "$(pwd)/iot-emulator/configuration:/app/configuration" \
--restart unless-stopped \
hub.friendly-tech.com/emulator/iot:latest
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 -> landing page: pick a mode
# http://localhost:8556/load -> Load Test mode
# http://localhost:8556/single -> Single Emulator mode
# 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. In the Compose deployment they
live in iot-emulator/.env (see Option 1: Docker Compose (recommended)); with docker run they are passed as
-e or through --env-file.
The published host port is deliberately absent from the table — it is a Compose concern and the application never reads it.
| Variable | Description | Default | Required |
|---|---|---|---|
|
HTTP port the emulator listens on inside the container |
|
No |
|
Active Spring profile |
|
No |
|
JVM options (heap sizing, GC, heap-dump on OOM). Increase the heap for large device counts. Carries |
|
No |
|
LwM2M scale mode for many devices per container (virtual threads). Same as |
|
No |
|
Path of a file holding the CA keystore password, for per-device X.509 minting. The only password source. See per-device credentials. |
(unset) |
No |
|
USP MQTTv5 MTP TLS mode: |
|
No |
|
Container timezone |
|
No |
|
JVM options auto-prepended by the launcher (opens |
|
No |
|
Headless mode only. Overrides the target server IP at startup, without editing |
(unset) |
No |
|
Headless mode only. Overrides the target server port at startup. Applied independently of |
(unset) |
No |
|
|
|
|
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 |
|---|---|
|
Mutual TLS authentication. The emulator presents its client certificate to the MQTT broker in addition to validating the server certificate. |
|
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
USP Diagnostics Simulation
Two JVM system properties tune the synthesised USP diagnostics described in Supported Protocols — USP Diagnostics. The Ookla speed test itself is a per-device Protocol Options switch, not a system property: a device offers it only when that switch is on or when its device tree declares the command. If the switch is missing from the USP Protocol Options after an update, see An option is missing from Protocol Options after an update.
| Property | Default | Effect |
|---|---|---|
|
|
How long the Ookla speed test ( |
|
(unset) |
Share of diagnostic runs that fail, from |
Pass them through JAVA_OPTS, keeping the options that are already there: in iot-emulator/.env for the
Compose deployment, or in the environment: block where a stack pins JAVA_OPTS (see
Option 1: Docker Compose (recommended)). With docker run the variable replaces the image default, so repeat the heap
options:
docker run -d \
--name iot-emulator \
-p 8556:8556 \
-e JAVA_OPTS="-Xms512m -Xmx1g -XX:+UseG1GC -Dusp.diagnostics.ooklaDurationMs=30000 -Dusp.diagnostics.errorRate=0.2" \
hub.friendly-tech.com/emulator/iot:latest
Manual:
java -Dusp.diagnostics.ooklaDurationMs=30000 -Dusp.diagnostics.errorRate=0.2 -jar gui/target/iot-emulator.jar --web-mode
Rules and Java Class Access
The emulator’s REST API is unauthenticated: anyone who can reach the HTTP port can change the configuration, start and stop devices, and save a JavaScript rule that then runs on every parameter update. Publish that port on a trusted network only. Rules cannot reach Java classes — Java.type(…) and Packages…. are refused, naming the class — so a rule is limited to the param, device and ctx objects documented in Rules. Set the JVM system property ft.rules.java to 1 to allow Java access again, from the next compile onwards; do this only where the port is already restricted, because it gives a rule everything the emulator process can read, including the mounted keystores and the ACS database password.
java -Dft.rules.java=1 -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 Device tree (XML) 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
-
Clone the repository:
git clone https://github.com/friendly-tech/iot-emulator.git cd iot-emulator -
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
Then open http://localhost:8556.
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 ship in the assembly under |
Container Management
| Command | Description |
|---|---|
|
Start the emulator in the background |
|
Stop / start the container |
|
Restart the application |
|
Follow application logs |
|
View the last 100 log lines |
|
Open a shell inside the container |
|
List the active configuration files |
|
View CPU / memory usage |
|
Show the container’s environment variables |
Updating
A mounted configuration directory replaces the directory in the image. See Configuration Directory.
A new image does not update these template files in the mounted directory:
-
*_gui.properties: protocol option definitions -
*_resources.xml: device trees -
*_controls_names.properties: control labels -
profiles.properties,sensor_info.properties,metering_plugin_db_connection.properties: profile, sensor and metering plugin settings
Without a refresh, the new build uses the option definitions of the previous release.
The /api/version response shows the new build. An option of the new release is still missing. For
example, USP Protocol Options has no Ookla speed test (Tozed X_T_SpeedTest) switch.
run-docker.sh refreshes the template files itself. With Docker Compose or plain Docker, the update
steps below include the refresh. A deployment without a mounted configuration directory always
uses the files of the image. The FT stack block in Adding the emulator to an existing FT stack is such a deployment.
The FT stack block needs no refresh.
Via run-docker.sh (recommended)
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 Docker Compose
-
Pull the new image:
cd /usr/local/ft-system docker compose pull iot-emulator -
Refresh the template files in
./iot-emulator/configuration. See Refreshing the template files. -
Recreate the container:
docker compose up -d iot-emulator -
Check the build of the running emulator:
curl -s http://localhost:8556/api/versionThe response shows
buildVersion,buildTimeandcommitof the new build.
Via plain Docker
-
Pull the new image:
docker pull hub.friendly-tech.com/emulator/iot:latest -
Refresh the template files in
./configuration. See Refreshing the template files. -
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 -
Check the build of the running emulator:
curl -s http://localhost:8556/api/versionThe response shows
buildVersion,buildTimeandcommitof the new build.
|
If you are not mounting a |
|
Every build of a release has the same version name, for example |
Refreshing the template files
The commands below copy the template files of the new image into the mounted directory. Before you
run the commands, set CONF to the mounted directory: ./iot-emulator/configuration for Docker
Compose or ./configuration for plain Docker.
|
The copy overwrites your changes in a template file of the image, for example a changed
|
IMAGE=hub.friendly-tech.com/emulator/iot:latest
CONF=./iot-emulator/configuration
# (Optional) back up the directory first
tar -czf emulator-config-$(date +%Y%m%d).tar.gz "$CONF"
STAGE=$(mktemp -d)
docker create --name tmp-emulator "$IMAGE" >/dev/null
docker cp tmp-emulator:/app/configuration/. "$STAGE/"
docker rm tmp-emulator >/dev/null
for f in "$STAGE"/*_gui.properties \
"$STAGE"/*_resources.xml \
"$STAGE"/*_controls_names.properties \
"$STAGE"/profiles.properties \
"$STAGE"/sensor_info.properties \
"$STAGE"/metering_plugin_db_connection.properties; do
[ -f "$f" ] || continue
cp "$f" "$CONF/"
done
rm -rf "$STAGE"
run-docker.sh makes the same copy. The copy keeps your user state:
-
gui_connection_options.properties: saved settings and selected option values -
snapshot.json: saved device list and parameter tree -
rules.js: rule script
The copy also keeps files from outside the image, for example TLS keystores or a device tree with its own file name.
Rollback
If the update refreshed the template files, restore the configuration backup before you start the
previous image. Run tar -xzf on the archive from the same directory as tar -czf. The previous
build then uses its own template files again. The restore also reverts the settings, snapshots and
rules from after the backup.
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 |
2 |
Configuration persistence |
Mount a |
3 |
Heap sizing |
Raise |
4 |
Network exposure |
Bind to loopback ( |
5 |
Health check |
Configure a healthcheck against |
6 |
Timezone |
Set |
7 |
Log management |
Application logs go to |
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
configurationvolume, ensure it contains all required files (both.propertiesand.xml) — a bind mount replaces the entire built-in directory. -
Re-extract the defaults (see Option 1: Docker Compose (recommended)) or deploy via
run-docker.sh.
An option is missing from Protocol Options after an update
Symptom: /api/version shows the new build. An option of the new release is missing from
Protocol Options. For example, USP Protocol Options has no Ookla speed test (Tozed X_T_SpeedTest)
switch.
Check: compare usp_gui.properties in the container with the same file in the image. The commands
look for the Ookla option. For another option, replace ookla with a part of its key.
# The file the running emulator reads (the mounted directory)
docker exec iot-emulator grep ookla /app/configuration/usp_gui.properties
# The same file in the image
docker run --rm --entrypoint grep hub.friendly-tech.com/emulator/iot:latest \
ookla /app/configuration/usp_gui.properties
Solution:
-
If only the second command prints the option, refresh the template files. See Refreshing the template files.
-
If both commands print the option, select USP. Protocol Options shows only the options of the selected protocol.
In the first case, the mounted configuration directory has the template files of the previous
release. After the refresh, reload the page. In Single Emulator mode, open Protocol Options again.
A container restart is not necessary. The emulator reads the *_gui.properties file on every request.
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 to127.0.0.1only — 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. |
Related Documentation
-
User Guide — how to drive the emulator from the Web UI
-
REST API Reference — automation and scripting
-
Supported Protocols — per-protocol options and ports