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 port 8555.

  • Connection Request listener — an HTTP listener on port 9999 that 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/parameterstree inside 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 9999.

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

eclipse-temurin:25-jre-alpine

Runtime image the emulator is built from (Dockerfile)

Java

25

Language level and runtime (pom.xml)

Application framework

Spring Boot 3.5.6

Web UI, REST API and Swagger UI (pom.xml)

Smack

4.1.9

XMPP transport for Connection Requests (pom.xml, smack.version)

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 8555 serves the Web UI and REST API. Port 9999 is the HTTP Connection Request listener the ACS uses to initiate sessions with emulated devices. If your ACS will send Connection Requests, also set CONNECTION_REQUEST_HOST — see Connection Request Host.

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>

<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. 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/tr069: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. 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/tr069:latest \
      -o tr069-emulator.tar
    gzip tr069-emulator.tar
  3. Transfer tr069-emulator.tar.gz to the offline server (e.g., via scp or removable media).

  4. 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 Dockerfile copies target/.jar, the parameterstree/ directory and the root configuration files into the image, so mvn package must run *before docker build.

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 /app/parameterstree replaces the directory baked into the image. If you mount your own directory, make sure it contains the XML files you intend to use; otherwise the dropdown will be empty. To customize safely, first extract the defaults from the image, edit them, then mount your copy — see Deployment.

Runtime Configuration Files

The emulator persists runtime state to property files in its working directory (/app), not to a separate configuration directory:

File Description

start_params.properties

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.

updatableParameters.yaml

Definitions of auto-updating parameters (the Updatable Parameters feature). Written by POST /updatableParameters (the Updatable Parameters panel in the UI); read back on startup and by GET /updatableParameters.

cpe_self_updated_params.properties also ships in the image but is not read or written by the running application — it is not part of the persisted state above and does not need to be mounted.

These two files are not mounted as volumes and do not survive docker compose down, a container recreation, or an image upgrade — the emulator writes them inside the container only, and a fresh container starts from the image defaults every time.

If you need to keep them across a recreation, copy them out beforehand and back in afterwards with docker cp:

# Before recreating / stopping the container
docker cp tr069-emulator:/app/start_params.properties ./start_params.properties.bak
docker cp tr069-emulator:/app/updatableParameters.yaml ./updatableParameters.yaml.bak

# After the new container is up
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

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 CONNECTION_REQUEST_HOST is not set, the emulator auto-detects a local address of the same family as the configured ACS host, which in a container is usually not reachable from the ACS. Set it explicitly whenever the ACS needs to initiate sessions. Note that Docker’s default bridge network has no IPv6 connectivity: an IPv6 ACS needs --network host or a Docker network created with --ipv6.

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

/usr/local/ft-system/compose.yml

compose.yml — single-service (Option 1: Docker Compose (recommended)). Skip it on a host that runs the full FT System stack: its compose.yml already contains this service block under the testing profile, see Adding the emulator to an existing FT stack

TR-069 Emulator environment

tr069-emulator/.env

Only one file: the .env in the folder root

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

start_params.properties and updatableParameters.yaml are not laid out on the host and are not mounted — see [runtime-state-not-persisted]. They live inside the container only and do not survive a recreation; use docker cp beforehand if you need to keep them.

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 .env variables. Compose resolves $\{…​} inside compose.yml from the shell environment and from a .env file in the same directory as compose.yml — never from a file listed under env_file:. Writing TR069_WEB_PORT into tr069-emulator/.env and referencing it as ${TR069_WEB_PORT} in the ports mapping would resolve to nothing.

To publish on a different host port, change the left-hand side in compose.yml: "9555:8555". SERVER_PORT changes the port inside the container and has to match the right-hand side.

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 /api/status (Load Test run state) or /apiV2/activeEmulators / /apiV2/statistics (Single Mode) as liveness/monitoring endpoints — each returns 200 once the service is up. The Spring Boot Actuator endpoints (/actuator/*) are *not exposed by the running service, so do not rely on /actuator/health for health checks.

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

SERVER_PORT

Web UI / REST API port inside the container

8555

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

CONNECTION_REQUEST_HOST

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 -Dconnection.request.host. See Connection Request Host.

(auto-detect)

No

DEFAULT_ACS_URL

Default ACS URL pre-filled in the UI start forms

http://ftacs:8080/ftacs/ACS

No

TZ

Container timezone

UTC

No

Built on Spring Boot 3.5.6 / Java 25. The Connection Request listener defaults to HTTP on port 9999. The Docker image maps CONNECTION_REQUEST_HOST to -Dconnection.request.host=<host> (HTTP only). To serve Connection Requests over HTTPS, pass a full https:// base URL through -Dconnection.request.address=https://<host>:<port> instead — for example by running the JAR manually or overriding the container entrypoint; an additional TLS listener then starts using the certificate inside the image (certs/server-cert.pem / server-key.pem). -Dconnection.request.address takes precedence over -Dconnection.request.host and must carry an IPv6 literal in brackets (https://[2a01:4f9:c015:9835::1]:8443). The listener port can be overridden with -Dconnection.request.port. See Protocol Support — Connection Request Transports for the XMPP and UDP/STUN transports.

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 9555:8555); the container port stays 8555 unless you also change SERVER_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

  1. Clone the repository:

    git clone https://github.com/Friendly-Technologies/TR069_Emulator.git
    cd TR069_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.

java -jar target/FTCpeEmulator.jar --web-mode

Run from the project root so the parameterstree/ directory and the root configuration files are found relative to the working directory. To set the Connection Request address, pass -Dconnection.request.address=http://<host>:9999.

Run the Desktop GUI (Swing)

Launching the JAR without --web-mode starts the legacy Swing desktop application. The run.bat in the distribution does this with a larger heap; on Linux use linux/run_32gb_heap.sh for high-volume load tests.

# Windows
run.bat

# Linux (large heap)
./linux/run_32gb_heap.sh

Container Management

Command Description

docker run -d --name tr069-emulator -p 8555:8555 -p 9999:9999 <image>

Start the emulator in the background

docker stop tr069-emulator / docker start tr069-emulator

Stop / start the container

docker restart tr069-emulator

Restart the application

docker logs -f tr069-emulator

Follow application logs

docker exec -it tr069-emulator sh

Open a shell inside the container

docker exec tr069-emulator ls -la /app/parameterstree

List the available parameter-tree files

docker stats tr069-emulator --no-stream

View CPU / memory usage

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

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 parameterstree/ and logs/ are bind-mounted, docker compose up -d keeps them across the recreation automatically. start_params.properties and updatableParameters.yaml are not mounted, so steps 1 and 4 are the only way to carry them across a recreation — they are not a safety net, they are required if you want to keep that state.

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 /app/parameterstree (for example the throwaway form in Option 2: docker run), recreating the container resets parameter trees to the image defaults. Mount the volume to keep custom trees and uploaded XML across updates. start_params.properties and updatableParameters.yaml are never mounted — see [runtime-state-not-persisted] to carry them across a recreation with docker cp.

Rollback

Option 1: Docker Compose (recommended)

  1. Edit the image: tag in compose.yml to the previous release:

        image: hub.friendly-tech.com/emulator/tr069:<previous-version-tag>
  2. Recreate the container on the pinned tag (bind-mounted volumes are untouched):

    docker compose up -d
    docker compose logs -f tr069-emulator
  3. 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 --restart unless-stopped (or restart: unless-stopped in Compose)

2

Connection Request host

Set CONNECTION_REQUEST_HOST to an ACS-reachable address; publish port 9999

3

Heap sizing

Raise JAVA_OPTS (-Xmx) for large device counts; for very high volume run the desktop mode with linux/run_32gb_heap.sh

4

Persistence

Mount /app/parameterstree (see Option 1: Docker Compose (recommended)) so custom trees survive updates. start_params.properties and updatableParameters.yaml are not mounted and do not persist automatically — see [runtime-state-not-persisted]

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)

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_HOST to 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 is Up.

Port conflict (address already in use)

Symptom: The container fails to start because port 8555 or 9999 is taken.

Solution:

  • Publish different host ports: -p 9555:8555 and/or -p 19999:9999 (keep the right side matching the container port).

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".

  • For very large load tests, run the desktop mode with linux/run_32gb_heap.sh.

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.