Installation & Deployment
Overview
This guide walks you through installing and running the TR-069 Emulator — a Spring Boot application (Java 25) that simulates TR-069/CWMP CPE devices for testing ACS implementations. 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.
The deployment consists of:
-
TR-069 Emulator — Spring Boot service started with
--web-mode(no desktop Swing GUI), serving the Web UI, the REST API and Swagger UI on port8555. -
Connection Request listener — an HTTP listener on port
9999that lets the ACS initiate sessions with emulated devices. -
Docker container — pre-built image from the Harbor registry or loaded from an archive.
-
Parameter trees — XML device-model files under
/app/parameterstreeinside the container, baked into the image and optionally mounted as a volume for customization.
|
The emulator runs as a single container and needs no database of its own to start.
It connects outbound to your ACS (the host/port you configure in the UI), and — when Connection Request is enabled — accepts inbound requests from the ACS on port |
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; significantly more for large device counts — see Production / Load-Test Checklist)
-
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 from the emulator to your ACS (HTTP/HTTPS host and port).
-
If you will use Connection Request, the ACS can reach the emulator host on port
9999— see Connection Request Host.
Third-Party Components
Every third-party product the emulator is built or run against. Values are read off this
repository — pom.xml 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 3.5.6 |
Web UI, REST API and Swagger UI ( |
Smack |
4.1.9 |
XMPP transport for Connection Requests ( |
ACS |
external; version tracked with the platform release |
The emulator connects outbound to it and accepts Connection Requests from it |
The emulator needs no database, no cache and no message broker of its own.
Quick Start
For experienced users who already have Docker installed.
# 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 tr069-emulator \
-p 8555:8555 \
-p 9999:9999 \
hub.friendly-tech.com/emulator/tr069:latest
# 3. Verify (returns 200 with "Running" or "Stopped")
curl -s http://localhost:8555/api/status
After startup the Web UI is available at http://localhost:8555.
|
Port |
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/tr069:latest
To pin to a specific release tag instead of latest:
docker pull hub.friendly-tech.com/emulator/tr069:<version>
|
|
Option B: Transfer Image to an Offline Server
Use this option when the target server has no internet access. 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/tr069: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. 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/tr069:latest \ -o tr069-emulator.tar gzip tr069-emulator.tar -
Transfer
tr069-emulator.tar.gzto the offline server (e.g., viascpor removable media). -
On the offline server, load the image:
gzip -dc tr069-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 tr069-emulator:latest .
|
The |
After loading, pulling or building, verify the image is available:
docker images | grep tr069
Parameter Trees & Configuration
Parameter Trees Directory
Device data models are defined by XML files in the parameterstree/ directory (/app/parameterstree inside the container).
These files are listed, selected, uploaded and deleted from the Web UI and the REST API.
parameterstree/ ├── cpe_params.xml # default tree (EmulatorParameter PARAMS_XML) ├── cpe_params_big_tree.xml ├── device_Technicolor.xml ├── Huawei_HG8245Q2.xml ├── base/ │ ├── tr-098.xml # InternetGatewayDevice. root model │ └── tr-181.xml # Device. root model └── ... (other device XML files)
The base/ subdirectory holds canonical TR-098 (InternetGatewayDevice.) and TR-181 (Device.) trees used by the "start by protocol" flow (REST POST /apiV2/startProtocol).
Each file defines the TR-069 data model — parameter paths, values, types and writability:
<?xml version="1.0" encoding="UTF-8"?>
<device>
<parameter>
<name>Device.DeviceInfo.Manufacturer</name>
<value>MyCompany</value>
<type>string</type>
<writable>false</writable>
</parameter>
<!-- More parameters... -->
</device>
|
A bind mount on |
Runtime Configuration Files
The emulator persists runtime state to property files in its working directory (/app), not to a separate configuration directory:
| File | Description |
|---|---|
|
Persisted emulator start parameters — ACS protocol/host/port/URL path, group/concurrency/delay counts, and option toggles. Written when you click Save / Save Config in the UI. |
|
Definitions of auto-updating parameters (the Updatable Parameters feature). Written by |
|
|
|
These two files are not mounted as volumes and do not survive If you need to keep them across a recreation, copy them out beforehand and back in afterwards with
|
Connection Request Host
When running in Docker, the emulator must report an externally reachable address to the ACS so the ACS can send Connection Requests back to the emulated device.
Set the CONNECTION_REQUEST_HOST environment variable to the host IP or hostname the ACS can reach:
docker run -d \
--name tr069-emulator \
-p 8555:8555 \
-p 9999:9999 \
-e CONNECTION_REQUEST_HOST=192.168.1.100 \
hub.friendly-tech.com/emulator/tr069:latest
This sets the Connection Request URL reported to the ACS to http://192.168.1.100:9999/{serial} (the variable maps to the JVM property -Dconnection.request.host).
The same value is also reported as the device’s own address in the data model — InternetGatewayDevice.WANDevice.{i}.WANConnectionDevice.{i}.WAN*Connection.{i}.ExternalIPAddress on TR-098 and Device.IP.Interface.{i}.IPv4Address.{i}.IPAddress on TR-181 — so the ACS sees one consistent address instead of the container’s internal one.
IPv6
Pass an IPv6 address bare, without brackets; the emulator brackets it where a URL needs it:
docker run -d \
--name tr069-emulator \
--network host \
-e CONNECTION_REQUEST_HOST=2a01:4f9:c015:9835::1 \
hub.friendly-tech.com/emulator/tr069:latest
The ACS URL is configured the same way — enter the bare literal (2a01:4f9:3070:2869::2) in the Host field of the Web GUI or as host= in start_params.properties, and keep the port in the separate Port field.
To report an address that differs from the Connection Request host, set -Ddevice.ip.address=<address> through JAVA_OPTS; it overrides only the data-model parameters.
|
If |
An ACS addressed by name — a Compose service such as ftacs, or an FQDN — is resolved to decide that family, so a dual-stack name that answers with an AAAA record first makes the emulator advertise an IPv6 address.
The JVM orders the resolved addresses by java.net.preferIPv6Addresses, so passing -Djava.net.preferIPv6Addresses=true through JAVA_OPTS moves both the session and the advertised address to IPv6 together.
A name that cannot be resolved falls back to IPv4.
Deployment
Docker Compose with a per-service environment file is the supported form, and it is what the
tr069-emulator folder on FT_DISK ships: a single-service compose.yml in the platform layout and
tr069-emulator/.env. The docker run variants below remain for a throwaway container.
FT_DISK Downloads
Download the following from
FT_DISK — tr069-emulator (FT_DISK → RD → prerelease → tr069-emulator):
| Component | Target | Files |
|---|---|---|
Stack definition |
|
|
TR-069 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 tr069-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
parameter-tree directory replaces the built-in one, so extract the defaults from the image before
the first start:
mkdir -p /usr/local/ft-system/tr069-emulator/logs
cd /usr/local/ft-system
docker create --name tmp-tr069 hub.friendly-tech.com/emulator/tr069:latest
docker cp tmp-tr069:/app/parameterstree ./tr069-emulator/parameterstree
docker rm tmp-tr069
|
|
The result:
/usr/local/ft-system/
├── compose.yml # from FT_DISK -- tr069-emulator (or the FT System <vendor> Stack)
└── tr069-emulator/
├── .env # per-service environment, from FT_DISK
├── parameterstree/ # -> /app/parameterstree
└── logs/ # -> /app/logs
compose.yml:
name: ft-tr069-emulator
services:
tr069-emulator:
image: hub.friendly-tech.com/emulator/tr069:latest
container_name: tr069-emulator
env_file:
- ./tr069-emulator/.env
ports:
# Host ports are literal on purpose. The emulator does not read them, and
# Compose interpolates ${...} only from the shell environment and from a
# .env in this directory -- never from the env_file above.
- "8555:8555" # Web UI + REST API
- "9999:9999" # Connection Request listener
volumes:
- ./tr069-emulator/parameterstree:/app/parameterstree
- ./tr069-emulator/logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8555/api/status"]
interval: 30s
timeout: 3s
start_period: 30s
retries: 3
restart: unless-stopped
tr069-emulator/.env:
# ==========================================================
# TR-069 Emulator - per-service environment
# ==========================================================
# The only environment file this deployment uses. The emulator has no shared
# infrastructure -- no database, no ClickHouse, no Hazelcast -- so there is no
# root .env layer to put anything in.
#
# The published host ports are NOT here: they are a compose concern, not an
# application setting, and Compose does not interpolate env_file values anyway.
# Change them in compose.yml.
# Web UI / REST API port inside the container
SERVER_PORT=8555
# 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
# External IP or hostname reported to the ACS for Connection Requests, and the
# device address written into the data model (ExternalIPAddress / IPAddress).
# Maps to -Dconnection.request.host=<host>. IPv4, IPv6 or a hostname; pass an
# IPv6 address bare, without brackets. Leave empty to auto-detect the container
# address, which is usually NOT reachable from the ACS.
CONNECTION_REQUEST_HOST=
# Default ACS URL pre-filled in the UI start forms
DEFAULT_ACS_URL=http://ftacs:8080/ftacs/ACS
Start it:
docker compose up -d
docker compose logs -f tr069-emulator
|
The published host ports are not 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-tr069-emulator) with its own network, so the
ftacs service name in DEFAULT_ACS_URL does not resolve from it. The stack compose.yml
from the FT System MySQL Stack / FT System Oracle Stack folder already carries the
tr069-emulator block, on the shared ft-network bridge:
tr069-emulator:
image: hub.friendly-tech.com/emulator/tr069:latest
container_name: tr069-emulator
depends_on:
ftacs:
condition: service_healthy
env_file:
- ./tr069-emulator/.env
environment:
TZ: ${TZ:-Europe/Kyiv} # the host-wide zone from the root .env; overrides TZ in tr069-emulator/.env
ports:
- "${TR069_EMUL_PORT:-8555}:8555"
volumes:
- ./tr069-emulator/logs:/app/logs
restart: unless-stopped
profiles:
- testing
networks:
- ft-network
Only tr069-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 tr069-emulator
Note the differences from the single-service file: the published port comes from
TR069_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 — tr069-emulator/.env is
loaded last and would win with its own TZ=UTC), the parameter tree is not mounted, and the
Connection Request port 9999 is not published — the ACS reaches it on the bridge.
Option 2: docker run
For a throwaway container, without an environment file:
docker run -d \
--name tr069-emulator \
-p 8555:8555 \
-p 9999:9999 \
-e CONNECTION_REQUEST_HOST=192.168.1.100 \
--restart unless-stopped \
hub.friendly-tech.com/emulator/tr069:latest
The same environment file works here too, which keeps the two forms in sync. To also persist the
parameter tree across recreation, extract it first (same docker cp step as Option 1: Docker Compose (recommended))
and mount it:
docker run -d \
--name tr069-emulator \
-p 8555:8555 \
-p 9999:9999 \
--env-file ./tr069-emulator/.env \
-v "$(pwd)/tr069-emulator/parameterstree:/app/parameterstree" \
--restart unless-stopped \
hub.friendly-tech.com/emulator/tr069:latest
start_params.properties and updatableParameters.yaml are not mounted here either — see
[runtime-state-not-persisted].
Verify Installation
After starting the container, run the following checks:
# 1. Check container status
docker ps -f name=tr069-emulator
# Expected: container "Up" with 0.0.0.0:8555->8555/tcp and 0.0.0.0:9999->9999/tcp
# 2. Liveness check (returns 200 once the service is up)
curl -s http://localhost:8555/api/status
# Expected: "Running" or "Stopped"
# 3. Emulator status (Load Test) / active devices (Single Mode)
curl -s http://localhost:8555/api/status # "Running" or "Stopped"
curl -s http://localhost:8555/apiV2/activeEmulators # [] on a fresh start
# 4. Application logs
docker logs tr069-emulator --tail 50
# Look for the Spring Boot "Started ..." line
# 5. Web UI and Swagger UI (open in a browser)
# http://localhost:8555 # landing page (choose a mode)
# http://localhost:8555/load # Load Test Mode
# http://localhost:8555/single # Single Emulator Mode
# http://localhost:8555/swagger-ui.html # interactive REST API
# http://localhost:8555/v3/api-docs # OpenAPI spec
|
Use |
Environment Variables
These are the variables the container reads. In the Compose deployment they live in
tr069-emulator/.env (see Option 1: Docker Compose (recommended)); with docker run they are passed as -e
or through --env-file.
The published host ports are deliberately absent from the table — they are a Compose concern and the application never reads them.
| Variable | Description | Default | Required |
|---|---|---|---|
|
Web UI / REST API port inside the container |
|
No |
|
Active Spring profile |
|
No |
|
JVM options (heap sizing, GC, heap-dump on OOM). Increase the heap for large device counts. |
|
No |
|
External IP/hostname reported to the ACS for Connection Requests, and the device address written into the data model. IPv4, IPv6 (bare, no brackets) or a hostname. Maps to |
(auto-detect) |
No |
|
Default ACS URL pre-filled in the UI start forms |
No |
|
|
Container timezone |
|
No |
|
Built on Spring Boot 3.5.6 / Java 25. The Connection Request listener defaults to HTTP on port |
|
|
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-Technologies/TR069_Emulator.git cd TR069_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.
java -jar target/FTCpeEmulator.jar --web-mode
Then open http://localhost:8555.
|
Run from the project root so the |
Container Management
| Command | Description |
|---|---|
|
Start the emulator in the background |
|
Stop / start the container |
|
Restart the application |
|
Follow application logs |
|
Open a shell inside the container |
|
List the available parameter-tree files |
|
View CPU / memory usage |
|
Show the container’s environment variables |
Updating
Option 1: Docker Compose (recommended)
Assumes the layout from Option 1: Docker Compose (recommended), where parameterstree/ and logs/ are
bind-mounted. start_params.properties and updatableParameters.yaml are not — see
[runtime-state-not-persisted] — so back them up from the running container first if you want to
keep them.
# 1. Back up runtime state before recreating -- the bind mounts persist parameterstree/ and
# logs/ automatically, but start_params.properties / updatableParameters.yaml live only
# inside the container and are lost on recreation unless copied out first
cp -r tr069-emulator/parameterstree ./parameterstree-backup
docker cp tr069-emulator:/app/start_params.properties ./start_params.properties.bak
docker cp tr069-emulator:/app/updatableParameters.yaml ./updatableParameters.yaml.bak
# 2. Pull the new image
docker compose pull
# 3. Recreate the container on the new image (Compose detects the image changed;
# the bind-mounted volumes are untouched)
docker compose up -d
# 4. Restore the runtime state files into the new container (optional)
docker cp ./start_params.properties.bak tr069-emulator:/app/start_params.properties
docker cp ./updatableParameters.yaml.bak tr069-emulator:/app/updatableParameters.yaml
docker restart tr069-emulator
# 5. Verify (returns 200 with "Running" or "Stopped")
curl -s http://localhost:8555/api/status
|
Because |
Option 2: docker run
For containers started with the persistent docker run form from Option 2: docker run:
# 1. Back up parameter trees and runtime state. parameterstree/ is bind-mounted and already
# persists on the host; start_params.properties / updatableParameters.yaml are not mounted
# (see <<runtime-state-not-persisted>>) and are lost on recreation unless copied out first
docker cp tr069-emulator:/app/parameterstree ./parameterstree-backup
docker cp tr069-emulator:/app/start_params.properties ./start_params.properties.bak
docker cp tr069-emulator:/app/updatableParameters.yaml ./updatableParameters.yaml.bak
# 2. Pull the new image
docker pull hub.friendly-tech.com/emulator/tr069:latest
# 3. Recreate the container
docker rm -f tr069-emulator
docker run -d \
--name tr069-emulator \
-p 8555:8555 \
-p 9999:9999 \
--env-file ./tr069-emulator/.env \
-v "$(pwd)/tr069-emulator/parameterstree:/app/parameterstree" \
--restart unless-stopped \
hub.friendly-tech.com/emulator/tr069:latest
# 4. Restore the runtime state files into the new container (optional)
docker cp ./start_params.properties.bak tr069-emulator:/app/start_params.properties
docker cp ./updatableParameters.yaml.bak tr069-emulator:/app/updatableParameters.yaml
docker restart tr069-emulator
# 5. Verify (returns 200 with "Running" or "Stopped")
curl -s http://localhost:8555/api/status
|
If you are not mounting |
Rollback
Option 1: Docker Compose (recommended)
-
Edit the
image:tag incompose.ymlto the previous release:image: hub.friendly-tech.com/emulator/tr069:<previous-version-tag> -
Recreate the container on the pinned tag (bind-mounted volumes are untouched):
docker compose up -d docker compose logs -f tr069-emulator -
Verify (returns 200 with "Running" or "Stopped"):
curl -s http://localhost:8555/api/status
Option 2: docker run
start_params.properties and updatableParameters.yaml are not mounted (see
[runtime-state-not-persisted]), so back them up first if you want to carry them into the rolled
back container:
# Optional: keep the current runtime state across the rollback
docker cp tr069-emulator:/app/start_params.properties ./start_params.properties.bak
docker cp tr069-emulator:/app/updatableParameters.yaml ./updatableParameters.yaml.bak
docker rm -f tr069-emulator
# Re-run with the previous image tag
docker run -d \
--name tr069-emulator \
-p 8555:8555 \
-p 9999:9999 \
--env-file ./tr069-emulator/.env \
-v "$(pwd)/tr069-emulator/parameterstree:/app/parameterstree" \
--restart unless-stopped \
hub.friendly-tech.com/emulator/tr069:<previous-version-tag>
# Optional: restore the runtime state files
docker cp ./start_params.properties.bak tr069-emulator:/app/start_params.properties
docker cp ./updatableParameters.yaml.bak tr069-emulator:/app/updatableParameters.yaml
docker restart tr069-emulator
# Verify (returns 200 with "Running" or "Stopped")
curl -s http://localhost:8555/api/status
Production / Load-Test Checklist
| # | Item | Notes |
|---|---|---|
1 |
Restart policy |
Use |
2 |
Connection Request host |
Set |
3 |
Heap sizing |
Raise |
4 |
Persistence |
Mount |
5 |
Health check |
Configure a healthcheck against |
6 |
Timezone |
Set |
Troubleshooting
Start with quick diagnostics:
docker logs tr069-emulator --tail 100
docker inspect tr069-emulator --format '{{json .Config.Env}}'
docker exec tr069-emulator ls -la /app/parameterstree
Common Issues
ACS cannot reach the device (Connection Request fails)
Symptom: The ACS reports Connection Request timeouts; the device only informs on its own schedule.
Solution:
-
Set
CONNECTION_REQUEST_HOSTto an address the ACS can reach (not the container’s internal IP). See Connection Request Host. -
Publish port
9999(-p 9999:9999) and ensure no firewall blocks it.
Parameter-tree dropdown is empty / "file not found"
Symptom: No XML files in the dropdown, or a device fails to start with a missing-tree error.
Check:
docker exec tr069-emulator ls /app/parameterstree
Solution:
-
If you mounted
/app/parameterstree, ensure the directory actually contains XML files (a mount replaces the built-in trees). -
Re-extract the defaults (see Deployment, Option 1) or upload a tree from the UI.
Web UI not reachable on port 8555
Symptom: The browser cannot connect to http://localhost:8555.
Check:
docker ps -f name=tr069-emulator
curl -s http://localhost:8555/api/status
Solution:
-
Confirm the port mapping (
-p 8555:8555) and that the container isUp.
Port Reference
| Port | Protocol | Description |
|---|---|---|
8555 |
HTTP |
Web UI, REST API, and Swagger UI |
9999 |
HTTP |
Connection Request listener (ACS-initiated sessions) |
| Outbound traffic to the ACS (HTTP/HTTPS on the host/port you configure in the UI) does not need to be published from the container. |
Related Documentation
-
Load Test Mode — bulk device simulation
-
Single Emulator Mode — granular per-device control
-
REST API Reference — automation and scripting