Installation & Deployment

← Back | Main Page

This guide covers deploying FT QoE Web — a Spring Boot application that monitors Quality of Experience (QoE) metrics for Customer Premises Equipment (CPE) — as a single Docker container.

1. Overview

FT QoE Web is a single container (ft-qoe-web) exposing the QoE dashboard, REST API, and scheduled report engine under the /qoeUI context path. It is a plain Compose service on the ft-network bridge, deployed from /usr/local/ft-system/ — the same layout as every deployment guide in the Friendly Tech portal, so it can be added to an existing ft-system stack without moving any directory.

Docker Compose is the only supported deployment method — there is no distribution package for bare-metal or manual installation; the image is built from a single-stage Dockerfile (eclipse-temurin:25-jre-alpine) and that is also how it ships to customers.

The infrastructure FT QoE Web depends on — a relational database, ClickHouse, and Hazelcast — is assumed to be already deployed and reachable; this guide only points FT QoE Web at it. To deploy that infrastructure, see All in one server deployment — Docker Compose File or Separate server deployment — Server A: Databases.

Browser / API client  ->  FT QoE Web  ->  MySQL / Oracle  +  ClickHouse  +  Hazelcast

FT QoE Web is one of three services in the "Specialized" group of the Friendly Tech platform (alongside FT Device Network Service and FT System Metrics). The full-system deployment guides run all three from one compose.yml under the ft-special Compose project; this page documents FT QoE Web on its own, for a fully split topology or a standalone evaluation install.

2. Prerequisites

2.1. Host Requirements

Component Minimum Recommended Notes

Docker Engine

20.10

Latest stable

Docker Compose is the only supported deployment method.

Docker Compose

2.0

Latest stable

This guide uses the docker compose v2 CLI syntax throughout.

CPU

verify with the infrastructure owner

 — 

No figure is published for FT QoE Web in isolation. The platform-wide guide reserves 4 cores for the entire Specialized group (FT Device Network Service + FT QoE Web + FT System Metrics) on one server; start from a conservative fraction of that for a standalone install and adjust from observed load.

RAM

verify with the infrastructure owner

 — 

Same caveat as CPU: 8 GB is the published figure for the whole Specialized group. The container sizes its heap from the container memory limit (-XX:MaxRAMPercentage=75.0, set in the Dockerfile).

Browser

Edge 81, Firefox 80, Chrome 81

Latest stable

For the QoE dashboard only — not a requirement on the container host.

2.2. Required External Dependencies

These services must be installed, running, and reachable from this host before the FT QoE Web container starts. None of them are deployed by this guide.

Component Minimum Version Why It Is Needed Port Required

MySQL or Oracle

verify with FT DevOps

Backs all three JDBC datasources (ftacs, ftacs_quartz, ftacs_qoe_ui); without it the application cannot start. The schemas and their tables must already exist — FT QoE Web runs no migrations against ftacs or ftacs_qoe_ui (spring.jpa.hibernate.ddl-auto=none), those tables are normally created by FTACS’s own migrations or the platform’s database init scripts. The one exception is ftacs_quartz, where Spring auto-initializes the scheduler tables on first start (spring.quartz.jdbc.initialize-schema=always).

MySQL 3306 (TCP), Oracle 1521 (TCP)

Yes

ClickHouse

26.2

Stores the QoE time-series data (default schema ftacs_qoe_ui_data, which must already exist); without it dashboards, KPIs and reports return no data. FT QoE Web connects with the official com.clickhouse:clickhouse-jdbc driver (build.gradle), which speaks HTTP — the same port FTACS uses.

8123 (HTTP)

Yes

Hazelcast

5.5.0

Distributed cache the application joins as a client during context initialisation (minimum 1 node, 2+ recommended for production); without a reachable member, caching silently has no effect.

5701 (TCP)

Yes

SMTP mail server

verify with the mail administrator

Delivers scheduled QoE reports by email. Without it the rest of the application works, only report delivery fails.

587 (TCP, STARTTLS)

No (optional)

No database or ClickHouse server version is pinned anywhere in this repository — the reference compose in Docker Compose deploys none of them, and build.gradle only pins JDBC drivers (com.mysql:mysql-connector-j, com.oracle.database.jdbc:ojdbc11, com.clickhouse:clickhouse-jdbc), which do not establish a server minimum. ClickHouse 26.2 is the supported version by product decision; the relational database minimum has to be confirmed with FT DevOps before it is written down here.

Deploying that infrastructure is out of scope here — see All in one server deployment — Docker Compose File for a single-host stack, or Separate server deployment — Server A: Databases for a split topology.

2.3. Supported Operating Systems

Deployment Operating system

Docker Compose

Linux (recommended), macOS, or Windows with WSL2

2.4. Registry Access

Network access to the hub.friendly-tech.com Docker registry (or offline image archives — see Offline Servers).

Access to the deployment files — compose.yml, the root .env, ft-qoe-web/.env and ft-qoe-web/config/hazelcast-client.yaml. FT QoE Web ships no pre-configured ft-qoe-web-conf package; the reference contents of all four files are reproduced in Preparation and Docker Compose.

3. Network Requirements

Connections FT QoE Web opens to its infrastructure. Each row must be open through firewalls when the peer is on another host.

Destination Port Protocol Purpose

Relational database

3306 (Oracle: 1521)

TCP

Three datasources: main (ftacs), Quartz (ftacs_quartz), UI (ftacs_qoe_ui)

ClickHouse

8123

HTTP

QoE time-series queries — FT QoE Web uses the official JDBC driver, which speaks HTTP

Hazelcast

5701

TCP

Hazelcast client connection

SMTP mail server

587

TCP (STARTTLS)

Scheduled QoE report delivery (only when mail is configured)

Inbound traffic — browsers and API clients reaching the dashboard and REST API — arrives on the published port documented in HTTP / HTTPS.

For a quick connectivity check from any host:

# Replace with target host IP and port
nc -zv <target-ip> <port>

3.1. Docker Networking

The compose block in Docker Compose attaches ft-qoe-web to the ft-network bridge (driver: bridge). Peers that sit on that same bridge — a database or ClickHouse container in the same ft-system stack — are reached by container name on the container port, and that traffic never leaves the host, so it needs no firewall rule. Set DB_HOST, CLICKHOUSE_HOST and HZ_MEMBERS to the container names in that case.

When the infrastructure runs on another host — the topology this guide assumes — those variables hold the peer’s IP address and the published port, and every row of the table above must be open through the firewall.

If a peer runs on the same host but outside Docker, use host.docker.internal rather than localhost — inside a container localhost is the container itself:

DB_HOST=host.docker.internal
HZ_MEMBERS=host.docker.internal:5701
CLICKHOUSE_HOST=host.docker.internal

host.docker.internal resolves to the host’s gateway address. On Docker Desktop (macOS, Windows) it works out of the box; on Linux it exists only if the container maps it, which the compose file in Docker Compose does:

extra_hosts:
  - "host.docker.internal:host-gateway"

The alternative is the host’s real LAN IP address.

4. Registry Authentication

The FT QoE Web image is pulled from hub.friendly-tech.com. Authenticate once per host before the first docker compose up.

docker login hub.friendly-tech.com

Enter the read-only pull credentials when prompted:

Field Value

Username

readonly

Password

fokxuw-fymte1-taSxyc

The readonly account provides pull-only access to the published images. It cannot push.

Alternatively, log in non-interactively:

echo "fokxuw-fymte1-taSxyc" | docker login hub.friendly-tech.com -u readonly --password-stdin

Verify authentication:

docker info | grep -A 5 Registry

4.1. Offline Servers

When the FT QoE Web host cannot reach hub.friendly-tech.com, pull the image on a machine that does have registry access, export it to an archive, transfer it, and load it on the offline host.

The connected machine can run Linux, macOS, or Windows — commands are given for both shells below. It does not need to be the same platform as the offline host.

An explicit --platform matching the offline host’s architecture is required. The image in Harbor is multi-arch (linux/amd64, linux/arm64); without --platform, docker pull selects the host architecture, which may not match the target. On an Apple Silicon (arm64) Mac or an arm64 Windows machine without --platform, the resulting archive will be arm64 and will fail with a platform does not match warning on amd64 servers. The examples below use linux/amd64; step 1 shows how to read the correct value off the offline host.

  1. On the offline host, find out which architecture it runs — this is the value you will pass as PLATFORM below. Ask Docker itself, since it reports what the daemon will actually accept:

    docker version --format '{{.Server.Arch}}'

    If Docker is not installed there yet, use the operating system instead — uname -m on Linux, or echo $env:PROCESSOR_ARCHITECTURE in PowerShell on Windows. Map the result:

    docker version reports uname -m / Windows reports Use as PLATFORM

    amd64

    x86_64 / AMD64

    linux/amd64

    arm64

    aarch64 / ARM64

    linux/arm64

    On Windows with Docker Desktop, {{.Server.Arch}} reports the architecture of the Linux VM that actually runs the containers — which is the value you want, not the Windows host’s own architecture.

  2. On a machine with registry access, log in. Run this on its own — it prompts for a password, so anything pasted after it on the same go would be swallowed as input:

    docker login hub.friendly-tech.com
  3. Pull and export the image. Paste the whole block as-is; the only lines to change are PLATFORM and TAG on top.

    Linux / macOS (bash):

    PLATFORM=linux/amd64
    TAG=latest
    
    docker pull --platform "$PLATFORM" "hub.friendly-tech.com/qoe/ft-qoe-web:$TAG"
    docker save "hub.friendly-tech.com/qoe/ft-qoe-web:$TAG" | gzip > "ft-qoe-web-$TAG.tar.gz"

    Windows (PowerShell):

    $PLATFORM = "linux/amd64"
    $TAG = "latest"
    
    docker pull --platform $PLATFORM "hub.friendly-tech.com/qoe/ft-qoe-web:$TAG"
    docker save -o "ft-qoe-web-$TAG.tar" "hub.friendly-tech.com/qoe/ft-qoe-web:$TAG"

    On Windows, always write the archive with docker save -o <file>. Piping or redirecting docker save from PowerShell (docker save …​ > file.tar) corrupts the archive, because the PowerShell pipeline re-encodes the stream as text instead of passing raw bytes. docker load then fails with unexpected EOF or invalid tar header. To compress for transfer, use the bundled tar.exe (Windows 10 1803+ / Server 2019+): tar.exe -czf ft-qoe-web.tar.gz ft-qoe-web-$TAG.tar.

  4. Transfer the archive to the offline host, together with compose.yml, .env, ft-qoe-web/.env, and ft-qoe-web/config/hazelcast-client.yaml.

  5. On the offline host, load the archive and start the service. Set TAG to the same value you used above:

    Linux (bash):

    TAG=latest
    
    gzip -dc "ft-qoe-web-$TAG.tar.gz" | docker load
    docker compose up -d ft-qoe-web

    Windows (PowerShell):

    $TAG = "latest"
    
    docker load -i "ft-qoe-web-$TAG.tar"
    docker compose up -d ft-qoe-web

Confirm the image is present before starting, so a missing or mis-architected image fails here rather than mid-startup:

docker images hub.friendly-tech.com/qoe/ft-qoe-web

Upgrades use the same flow: pull the new tag on the connected machine, transfer and load the archive, then docker compose up -d ft-qoe-web.

5. Preparation

Create the directories FT QoE Web needs on the host and point it at the infrastructure listed in Required External Dependencies.

5.1. Directory Structure

mkdir -p /usr/local/ft-system/ft-qoe-web/{config,logs}
cd /usr/local/ft-system

Place hazelcast-client.yaml (see Hazelcast Client Configuration) in ft-qoe-web/config/ — it is mounted read-only into the container. Ready-made copies of the two service files — ft-qoe-web/.env and ft-qoe-web/config/hazelcast-client.yaml — are published on FT_DISK (see Deployment Artifacts (FT_DISK)); apart from them FT QoE Web needs no acs_configuration.xml-style runtime configuration on disk, only the JDBC and Hazelcast connection settings supplied through environment variables. An ssl/ directory is deliberately not created here — it is needed only when TLS is terminated in the application, and TLS Keystore (optional) creates it as its first step.

5.1.1. Directory Layout

The platform layout, sliced to FT QoE Web — the same /usr/local/ft-system/ root the full-system deployment uses, so this service can be added to an existing stack without moving anything.

/usr/local/ft-system/
├── compose.yml                             # stack definition (see <<docker-compose>>)
├── .env                                    # shared stack environment
└── ft-qoe-web/
    ├── .env                                # per-service environment
    ├── config/                             # -> /etc/app/cache (read-only)
    │   └── hazelcast-client.yaml
    ├── ssl/                                # -> /etc/app/ssl (only when SSL_ENABLED=true)
    │   └── server.p12
    └── logs/                               # -> /app/logs (written by the container)
Path Content Backup

compose.yml

Service definition for ft-qoe-web — image, env_file, ports, volumes, healthcheck.

Yes

.env

Shared stack environment: TZ, database connection (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD), ClickHouse (CLICKHOUSE_*), Hazelcast (HZ_MEMBERS).

Yes

ft-qoe-web/.env

Per-service environment: host port, connector ports, TLS switches, HikariCP pool tuning, QOE_WEB_JAVA_RAM, QOE_JWT_SECRET, DM_ISP_MODE, mail settings.

Yes

ft-qoe-web/config/

hazelcast-client.yaml, mounted read-only at /etc/app/cache and loaded through CACHE_CONFIG_PATH.

Yes

ft-qoe-web/ssl/

Optional keystore, mounted read-only at /etc/app/ssl — only created when TLS is terminated in the application (TLS Keystore (optional)).

Yes

ft-qoe-web/logs/

Application log files written by the container at /app/logs.

No

5.2. Deployment Artifacts (FT_DISK)

Ready-made copies of the two FT QoE Web configuration files are published on FT_DISK, under FT_DISK → RD → prerelease → ft-qoe-web. Download them from the ft-qoe-web folder on SharePoint and place each file at the path shown below.

Artifact Destination Reference contents

.env (service)

/usr/local/ft-system/ft-qoe-web/.env

Environment Configuration

hazelcast-client.yaml

/usr/local/ft-system/ft-qoe-web/config/hazelcast-client.yaml

Hazelcast Client Configuration

These are the same folder and the same two files the full-system deployment guide downloads for this service (Per-service configuration files), so a stack that already carries other FT services keeps a single source for its configuration files.

compose.yml and the root .env are deliberately not published in this folder. They describe the whole host rather than one service, so a copy taken from a service folder would overwrite the definition of every other FT service running on it. Where they come from depends on the topology being deployed:

Both service files are reproduced inline in this document as well (see the Reference contents column), so the deployment can be completed by hand if FT_DISK is unreachable — review the downloaded copies against those sections and adjust hosts, ports and credentials to the target environment before starting the stack.

5.3. Environment Configuration

The stack uses a two-layer environment file architecture — the same one the full-system deployment uses. There is no env file per database vendor: both layers are loaded together by the env_file: list in Docker Compose, in this order.

File Purpose

.env

Shared stack environment: database connection (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD), ClickHouse, Hazelcast, time zone, and the database dialect (SPRING_PROFILES_ACTIVE). Shared by every service on the host.

ft-qoe-web/.env

FT QoE Web only: host and connector ports, TLS switches, HikariCP pool tuning, JVM settings, JWT signing key, feature flags, mail.

Selecting Oracle instead of MySQL is not a file swap — it is a change of SPRING_PROFILES_ACTIVE, DB_HOST, DB_PORT (and the other variables listed in the portal’s table) inside the single root .env. MySQL is the default. See Switching to Oracle for the full list.

Each MySQL datasource resolves through a fallback chain (application-mysql.yml): MAIN_DB_URL / QUARTZ_DB_URL / UI_DB_URL first, then the generic MYSQL_HOST / MYSQL_PORT / MYSQL_USER / MYSQL_PASSWORD, then DB_HOST / DB_PORT / DB_USER / DB_PASSWORD, and only then the built-in localhost:3306 / ftacs defaults. ClickHouse resolves the same way: CLICKHOUSE_DB_URL falls back to CLICKHOUSE_HOST / CLICKHOUSE_PORT / CLICKHOUSE_DATABASE.

So the root .env values below (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, CLICKHOUSE_HOST, CLICKHOUSE_PORT, CLICKHOUSE_DATABASE) are already sufficient to connect all three MySQL datasources and ClickHouse — setting MAIN_DB_URL / QUARTZ_DB_URL / UI_DB_URL / CLICKHOUSE_DB_URL explicitly is only needed to point at a non-default schema name or add extra JDBC parameters. The compose block in Docker Compose sets them explicitly anyway (derived from these same .env values) for clarity, not because it is required.

# /usr/local/ft-system/.env
TZ=Europe/Kyiv

# Database (existing)
DB_HOST=<db-host-ip>                       # <-- replace; use the container name if it shares this bridge
DB_PORT=3306
DB_USER=ftacs
DB_PASSWORD=<your-db-password>             # <-- replace

# Hazelcast (existing)
HZ_MEMBERS=<hazelcast-host-ip>:5701        # <-- replace

# ClickHouse (existing) -- HTTP port, the same one FTACS uses
CLICKHOUSE_HOST=<clickhouse-host-ip>       # <-- replace
CLICKHOUSE_PORT=8123
CLICKHOUSE_DATABASE=ftacs_qoe_ui_data
CLICKHOUSE_USERNAME=ftacs
CLICKHOUSE_PASSWORD=<your-ch-password>     # <-- replace

Create ft-qoe-web/.env with the per-service variables below. The compose block in Docker Compose also writes out the *_DB_URL / CLICKHOUSE_DB_URL values in full, derived from the root .env through Compose variable interpolation — an explicit, readable form of the same fallback chain described above, not a requirement.

Click to expand a commented ft-qoe-web/.env reference
# =============================================================================
# FT QoE Web
# =============================================================================

# Host port exposed on the server. Maps to the container's HTTPS_PORT below.
QOE_WEB_PORT=8086

# Primary container listen port (application default 8443). Serves HTTPS when
# SSL_ENABLED=true, plain HTTP otherwise. Must match the container-side port
# in the compose.yml `ports:` mapping (8080 in the example below).
HTTPS_PORT=8080

# Secondary plain-HTTP connector port (application default 8080). Only bound
# when SSL_ENABLED=true; must differ from HTTPS_PORT.
HTTP_PORT=8081

# Enable in-app HTTPS. Leave false when TLS is terminated upstream (nginx,
# Traefik, ingress). When true, a self-signed keystore ships in the image for
# dev -- replace via SSL_KEY_STORE / SSL_KEY_STORE_PASSWORD in production.
SSL_ENABLED=false

# --- TLS, read only when SSL_ENABLED=true (see the TLS Keystore section) ------
# Keystore location inside the container (file: or classpath: URL).
# SSL_KEY_STORE=file:/etc/app/ssl/server.p12
# Password for the keystore file.
# SSL_KEY_STORE_PASSWORD=changeit
# Keystore format -- PKCS12 or JKS.
# SSL_KEY_STORE_TYPE=PKCS12
# Alias of the key entry to serve. Must match the alias inside the keystore.
# SSL_KEY_ALIAS=ft-qoe-web
# TLS protocol versions offered by the connector.
# SSL_ENABLED_PROTOCOLS=TLSv1.3,TLSv1.2
# Password of the private key inside the keystore, when it differs from the
# keystore password. No SSL_-prefixed alias exists -- use the Spring Boot name.
# SERVER_SSL_KEY_PASSWORD=changeit
# Trust store for verifying client certificates (mutual TLS). Spring Boot names
# only; also set SERVER_SSL_CLIENT_AUTH, or the trust store is never consulted.
# SERVER_SSL_TRUST_STORE=file:/etc/app/ssl/truststore.p12
# SERVER_SSL_TRUST_STORE_PASSWORD=changeit
# SERVER_SSL_TRUST_STORE_TYPE=PKCS12
# SERVER_SSL_CLIENT_AUTH=need

# ISP mode for data model interpretation. Must match the ispMode configured
# in FTACS's acs_configuration.xml. 1 = Multi-Domain, 2 = Multi-ISP (default).
DM_ISP_MODE=2

# =============================================================================
# HikariCP Connection Pool
# =============================================================================
DB_MAX_POOL_SIZE=10
DB_MIN_IDLE=5
DB_CONNECTION_TIMEOUT_MS=30000

# =============================================================================
# JVM heap override. The image ships with container-aware percentage-based
# sizing (-XX:MaxRAMPercentage=75.0) baked into the Dockerfile ENV; set this
# only to pin a fixed heap instead. It is read by the compose environment:
# block, which appends -Duser.timezone=${TZ} to it -- do NOT write ${TZ} in
# this file: Compose passes env_file values through literally, without
# variable interpolation, so the JVM would receive the string "${TZ}".
# =============================================================================
QOE_WEB_JAVA_RAM=-Xms512m -Xmx2g

# =============================================================================
# Hazelcast client -- path to the mounted config directory (see below).
# The cluster name and member address are set INSIDE hazelcast-client.yaml,
# not through environment variables -- see the IMPORTANT note in the
# Hazelcast Client Configuration section.
# =============================================================================
CACHE_CONFIG_PATH=file:/etc/app/cache/

# =============================================================================
# REST API token signing key (base64, 32+ bytes, identical on every node).
# Left unset, a key is generated at startup and every issued token dies with
# the container.
# =============================================================================
QOE_JWT_SECRET=<your-base64-signing-key>    # <-- replace

# =============================================================================
# Mail (optional -- scheduled QoE report delivery)
# =============================================================================
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-app-password             # <-- replace if used
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS=true

# =============================================================================
# Logging
# =============================================================================
# LOGGING_LEVEL_COM_FRIENDLY=DEBUG

The variables the container actually reads, with the defaults that apply when they are left unset. Which layer each one belongs to: TZ, SPRING_PROFILES_ACTIVE, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, CLICKHOUSE_HOST, CLICKHOUSE_PORT, CLICKHOUSE_DATABASE, CLICKHOUSE_USERNAME, CLICKHOUSE_PASSWORD and HZ_MEMBERS are infrastructure values and belong in the root .env; every other variable in the table below is FT QoE Web’s own and belongs in ft-qoe-web/.env. The *_DB_URL overrides may sit in either layer — keeping them out of both, and letting the root .env values drive the connection, is the simpler setup (see Wrong Database Targeted Despite Correct DB_HOST). Defaults are taken from src/main/resources/application.yml, application-mysql.yml, logback-spring.xml and the Dockerfile ENV block in this repo; Required = Yes means the container cannot reach its infrastructure without it (directly, or through the matching *_DB_URL).

Variable Description Default Required

TZ

Container time zone.

Europe/Kyiv (Dockerfile)

No

SPRING_PROFILES_ACTIVE

Database dialect profile — mysql or oracle.

mysql (Dockerfile)

No

QOE_WEB_PORT

Host port published by the compose ports: mapping. Not read by the application itself.

8086

No

HTTPS_PORT

Primary connector port (server.port). Serves HTTPS when SSL_ENABLED=true, plain HTTP otherwise. Must match the container side of the ports: mapping.

8443

No

HTTP_PORT

Secondary plain-HTTP connector. Bound only when SSL_ENABLED=true; must differ from HTTPS_PORT.

8080

No

SSL_ENABLED

Terminate TLS in the application. Also sets the session cookie secure flag.

false

No

SSL_KEY_STORE

Keystore location when SSL_ENABLED=true.

classpath:config/keystore.p12

No

SSL_KEY_STORE_PASSWORD

Keystore password.

changeit

No

SSL_KEY_STORE_TYPE

Keystore format — PKCS12 or JKS.

PKCS12

No

SSL_KEY_ALIAS

Alias of the key entry served by the connector. Must match the alias inside the keystore — a keystore built with a different alias (the platform .env template uses server) fails to start with Alias name […​] does not identify a key entry.

ft-qoe-web

No

SSL_ENABLED_PROTOCOLS

TLS versions offered by the connector, comma-separated.

TLSv1.3,TLSv1.2

No

SERVER_SSL_KEY_PASSWORD

Password of the private key inside the keystore, when it differs from the keystore password. Spring Boot name only — there is no SSL_KEY_PASSWORD alias in application.yml (see Two naming forms for the TLS variables).

unset — the keystore password is used

No

SERVER_SSL_TRUST_STORE, SERVER_SSL_TRUST_STORE_PASSWORD, SERVER_SSL_TRUST_STORE_TYPE

Trust store used to verify client certificates (mutual TLS). Spring Boot names only — no SSL_TRUST_STORE* aliases exist. Setting them alone changes nothing: SERVER_SSL_CLIENT_AUTH must be want or need for the trust store to be consulted.

unset — the JVM default trust store

No

SERVER_SSL_CLIENT_AUTH

Client-certificate policy — none, want, or need. Spring Boot name only.

none

No

DB_HOST

Relational database host for all three datasources (ftacs, ftacs_quartz, ftacs_qoe_ui). Overridden by MYSQL_HOST, and by a full *_DB_URL.

localhost

Yes

DB_PORT

Database port.

3306

Yes

DB_USER

Database user for all three datasources.

ftacs

Yes

DB_PASSWORD

Database password.

ftacs

Yes

MAIN_DB_URL, QUARTZ_DB_URL, UI_DB_URL

Full JDBC URLs. Set them only to use non-default schema names or extra JDBC parameters — they win over DB_HOST / MYSQL_HOST.

Derived from DB_HOST / DB_PORT

No

DB_MAX_POOL_SIZE, DB_MIN_IDLE

HikariCP pool size for the main and UI datasources (the Quartz pool is fixed at 5 / 2).

10, 5

No

DB_CONNECTION_TIMEOUT_MS, DB_IDLE_TIMEOUT_MS, DB_MAX_LIFETIME_MS

HikariCP timeouts, in milliseconds.

30000, 600000, 1800000

No

CLICKHOUSE_HOST

ClickHouse host for the QoE time-series datasource.

localhost

Yes

CLICKHOUSE_PORT

ClickHouse HTTP port — the same one FTACS uses, not the native TCP port 9000.

8123

Yes

CLICKHOUSE_DATABASE

ClickHouse database holding the QoE data.

ftacs_qoe_ui_data

Yes

CLICKHOUSE_USERNAME, CLICKHOUSE_PASSWORD

ClickHouse credentials. CLICKHOUSE_DB_USERNAME / CLICKHOUSE_DB_PASSWORD take precedence.

ftacs, ftacs

Yes

CLICKHOUSE_DB_URL

Full ClickHouse JDBC URL; wins over the three variables above.

Derived from CLICKHOUSE_HOST / CLICKHOUSE_PORT / CLICKHOUSE_DATABASE

No

HZ_MEMBERS

Hazelcast member list (<host>:<port>, comma-separated). Resolved by the com.friendly:ft-cache client and overrides network.cluster-members in the mounted hazelcast-client.yaml.

unset — the bundled hazelcast-client.yaml default 127.0.0.1:5701 applies

Yes

CACHE_CONFIG_PATH

Directory the Hazelcast client configuration is loaded from.

classpath:config/

No

QOE_JWT_SECRET

HS256 signing key for REST API tokens (base64, 32+ bytes, identical on every node). Left empty, a key is generated at startup and every issued token dies with the container.

empty

No

DM_ISP_MODE

Data model interpretation mode. Must match ispMode in FTACS’s acs_configuration.xml. 1 = Multi-Domain, 2 = Multi-ISP.

2

No

MAIL_HOST, MAIL_PORT

SMTP server for scheduled QoE report delivery.

smtp.example.com, 587

No

MAIL_USERNAME, MAIL_PASSWORD

SMTP credentials.

your-email@example.com, your-email-password

No

MAIL_SMTP_AUTH, MAIL_SMTP_STARTTLS

SMTP authentication and STARTTLS switches.

true, true

No

JAVA_OPTS

JVM flags. The image ships container-aware heap sizing (-XX:MaxRAMPercentage=75.0); set this only to override it. Assembled in the compose environment: block from QOE_WEB_JAVA_RAM and TZ — not in an env file, because Compose does not interpolate ${…​} inside env_file values.

the Dockerfile ENV value

No

QOE_WEB_JAVA_RAM

Optional fixed-heap override fed into JAVA_OPTS by the compose block (for example -Xms512m -Xmx2g). Unset, only the time-zone flag is added and the image default heap sizing applies.

empty

No

LOG_DIR

Directory the file appenders write to.

/app/logs (${APP_HOME}/logs with app.home unset and WORKDIR /app)

No

DEBUG_MODE

Any non-empty value starts the JVM with the JDWP agent on port 5005.

unset

No

5.3.1. Two naming forms for the TLS variables

The short SSL_* names are aliases this application declares itself: application.yml binds each server.ssl.* property to a placeholder, key-store: ${SSL_KEY_STORE:classpath:config/keystore.p12} and so on. Only the six placeholders listed in the table above exist — SSL_ENABLED, SSL_KEY_STORE, SSL_KEY_STORE_PASSWORD, SSL_KEY_STORE_TYPE, SSL_KEY_ALIAS, SSL_ENABLED_PROTOCOLS.

Every server.ssl. property can *also be set through its canonical Spring Boot name, by upper-casing the property path and replacing . and - with : server.ssl.key-storeSERVER_SSL_KEY_STORE, server.ssl.trust-store-passwordSERVER_SSL_TRUST_STORE_PASSWORD. Environment variables outrank application.yml, so where both forms are set the SERVER_SSL* value wins.

server.ssl.key-password, server.ssl.trust-store, server.ssl.trust-store-password, server.ssl.trust-store-type and server.ssl.client-auth have no SSL_* alias. SSL_KEY_PASSWORD, SSL_TRUST_STORE and SSL_TRUST_STORE_PASSWORD are silently ignored — nothing reads them. Use the SERVER_SSL_* form for those five.

This is why the per-service ft-qoe-web/.env generated by the platform deployment pipeline — the resources/services/ft-qoe-web/.env template in the ft-qa-shared repository, laid down as <deployment-root>/ft-qoe-web/.env — uses SERVER_PORT and SERVER_SSL_* throughout: one naming form that covers the whole server. tree, including the properties without an alias. A file provisioned that way is *not misconfigured; it is the equivalent of the SSL_* form used in this guide. Both forms are supported — just do not set both for the same property.

The FT_QOE_WEB_SERVER_SSL_* variables from older pipelines were dropped with the per-service .env split and are read by nothing — see FT QA Shared — Configuration. Replace them with the plain SERVER_SSL_* names.

5.3.2. Hazelcast Client Configuration

A default hazelcast-client.yaml is bundled inside the Docker image, pointing at 127.0.0.1:5701 — suitable only for local development. CACHE_CONFIG_PATH tells the app which directory to load the file from; it does not itself carry the cluster address.

HZ_MEMBERS (root .env, see Environment Configuration) is the normal way to point FT QoE Web at the real Hazelcast cluster — it overrides the bundled network.cluster-members list at startup. The pinned com.friendly:ft-cache client library (build.gradle) resolves the member list from the HZ_MEMBERS environment variable and, when set, overrides whatever cluster-members the mounted hazelcast-client.yaml contains — confirmed by inspecting ft-cache 0.0.8 and 0.0.9 (resolveMembers() / membersOverride, logged as Hazelcast members overridden from env HZ_MEMBERS: {}); this repo pins ft-cache:0.0.7 exactly, whose jar was not available to inspect directly, so verify this behavior against your build’s actual ft-cache version if it matters for a critical rollout.

HAZELCAST_ADDRESS / HAZELCAST_CLUSTER_NAME are a different, unwired pair — they are not read anywhere in this codebase, so setting them has no effect. The cluster name (cluster-name: dev) is only configurable inside hazelcast-client.yaml — there is no environment override for it, so mount your own file to change it, or to tune near-cache / connection-strategy settings beyond the defaults.

For production deployments, set HZ_MEMBERS in the root .env (already shown in Environment Configuration) to point at the real cluster. Mount an external hazelcast-client.yaml only when you also need a non-default cluster-name or advanced tuning:

CACHE_CONFIG_PATH=file:/etc/app/cache/
-v ./ft-qoe-web/config:/etc/app/cache:ro
Download example hazelcast-client.yaml

Below is the full example configuration with all recommended settings:

hazelcast-client:
  # Must match the cluster-name configured on the Hazelcast server side
  cluster-name: dev

  properties:
    hazelcast.client.shuffle.member.list: true
    hazelcast.client.heartbeat.timeout: 60000
    hazelcast.client.heartbeat.interval: 5000
    hazelcast.client.event.thread.count: 5
    hazelcast.client.event.queue.capacity: 1000000
    hazelcast.client.invocation.timeout.seconds: 120

  network:
    # List of addresses for the client to try to connect to.
    # All members of a Hazelcast cluster accept client connections.
    # Use the format <hostname>:<port>
    cluster-members:
      - 127.0.0.1:5701
    cluster-routing:
      mode: "ALL_MEMBERS"
    redo-operation: true
    connection-timeout: 60000
    socket-options:
      tcp-no-delay: false
      keep-alive: true
      reuse-address: true
      linger-seconds: 3
      buffer-size: 128

  near-cache:
    default:
      time-to-live-seconds: 90
      max-idle-seconds: 100
      invalidate-on-change: true
      in-memory-format: OBJECT
      serialize-keys: true
      local-update-policy: INVALIDATE
      preloader:
        enabled: false
        directory: directory
        store-initial-delay-seconds: 50
        store-interval-seconds: 10

  connection-strategy:
    async-start: false
    reconnect-mode: ASYNC
    connection-retry:
      initial-backoff-millis: 2000
      max-backoff-millis: 60000
      multiplier: 3
      cluster-connect-timeout-millis: 120000
      jitter: 0.5

  user-code-deployment:
    enabled: false
Table 1. Key settings to customize for your environment
Setting Description

cluster-name

Must match the name configured on the Hazelcast server. Default: dev.

network.cluster-members

List of <host>:<port> addresses of Hazelcast cluster nodes — the fallback used only when HZ_MEMBERS is unset. In the deployment guides HZ_MEMBERS in .env is the value that actually takes effect; edit this list only for a standalone install with no HZ_MEMBERS set.

connection-strategy.reconnect-mode

ASYNC allows the application to start even if Hazelcast is temporarily unavailable and reconnect later.

near-cache.default.time-to-live-seconds

How long cached entries remain valid (seconds). Adjust based on data freshness requirements.

connection-retry.cluster-connect-timeout-millis

Maximum time (ms) the client will attempt to connect before giving up. Default: 120000 (2 min).

5.4. TLS Keystore (optional)

HTTPS is disabled by default (SSL_ENABLED=false) — the application listens as plain HTTP on HTTPS_PORT, and TLS is expected to be terminated by a reverse proxy or ingress in front of it.

To terminate TLS in the application itself, work through the four steps below in order — the keystore has to exist on the host, be mounted into the container, and be named by the environment before SSL_ENABLED=true has any chance of working.

1. Create the keystore directory — the mkdir in Directory Structure creates only config and logs:

mkdir -p /usr/local/ft-system/ft-qoe-web/ssl

2. Put a keystore in it, named server.p12. Use your CA-issued PKCS#12 keystore in production. For a development certificate, generate one directly into that directory:

keytool -genkeypair -alias ft-qoe-web -keyalg RSA -keysize 2048 \
        -storetype PKCS12 \
        -keystore /usr/local/ft-system/ft-qoe-web/ssl/server.p12 \
        -validity 3650 \
        -storepass changeit -dname "CN=ft-qoe-web, O=Friendly Technologies, C=US" \
        -ext "san=dns:localhost,ip:127.0.0.1"

The bundled config/keystore.p12 (classpath default) is a self-signed certificate intended only for local development — it is what the application falls back to when SSL_KEY_STORE is unset, and it must not be used in production.

3. Mount the directory — add the ssl line to the volumes: list of the ft-qoe-web service in compose.yml (Docker Compose), next to the config and logs mounts:

    volumes:
      - ./ft-qoe-web/config:/etc/app/cache:ro
      - ./ft-qoe-web/logs:/app/logs
      - ./ft-qoe-web/ssl:/etc/app/ssl:ro

4. Set the variables in ft-qoe-web/.env (SSL_ENABLED is already there, set to false) and recreate the container with docker compose up -d ft-qoe-web:

SSL_ENABLED=true
SSL_KEY_STORE=file:/etc/app/ssl/server.p12
SSL_KEY_STORE_PASSWORD=<your-strong-password>

Those three cover the common case: a PKCS#12 keystore holding one key entry, whose private key is protected by the keystore password. The remaining knobs default to values that match the keystore produced by step 2 and only need setting when your keystore differs:

# Keystore format -- set only for a JKS keystore.
SSL_KEY_STORE_TYPE=PKCS12
# Alias of the key entry. The default is ft-qoe-web, which is the alias step 2
# generates; a CA-issued keystore usually carries a different one.
SSL_KEY_ALIAS=ft-qoe-web
# Offered TLS versions.
SSL_ENABLED_PROTOCOLS=TLSv1.3,TLSv1.2
# Private-key password -- only when it differs from the keystore password.
# Spring Boot name: there is no SSL_KEY_PASSWORD alias.
SERVER_SSL_KEY_PASSWORD=<your-key-password>

A wrong alias is the most common failure here: the container starts, then Tomcat aborts with java.io.IOException: Alias name [ft-qoe-web] does not identify a key entry. List what the keystore actually contains with keytool -list -keystore /usr/local/ft-system/ft-qoe-web/ssl/server.p12 -storetype PKCS12.

For mutual TLS — verifying client certificates — add a trust store as well. These properties have no SSL_* alias either, so use the Spring Boot names (Two naming forms for the TLS variables), and remember that a trust store without SERVER_SSL_CLIENT_AUTH has no effect:

SERVER_SSL_TRUST_STORE=file:/etc/app/ssl/truststore.p12
SERVER_SSL_TRUST_STORE_PASSWORD=<your-truststore-password>
SERVER_SSL_TRUST_STORE_TYPE=PKCS12
# want = request a client certificate but allow the handshake without one
# need = reject clients that present none
SERVER_SSL_CLIENT_AUTH=need

Mount the trust store the same way as the keystore — step 3’s ssl mount already covers it if you put truststore.p12 in /usr/local/ft-system/ft-qoe-web/ssl/.

The path in SSL_KEY_STORE is the container path — /etc/app/ssl/server.p12 is where step 3 mounts the host file /usr/local/ft-system/ft-qoe-web/ssl/server.p12. If the mount is missing, the container starts pointing at a file that does not exist inside it.

Leave the ports as this guide sets them (HTTPS_PORT=8080, HTTP_PORT=8081). Enabling TLS does not move the primary connector: HTTPS_PORT is server.port in both modes, so the container keeps listening on 8080 and the compose mapping 8086 → 8080 keeps working. What changes is:

  • the primary connector on HTTPS_PORT now speaks HTTPS instead of plain HTTP — the UI moves from http://<host>:8086/qoeUI to https://<host>:8086/qoeUI;

  • the secondary plain-HTTP connector on HTTP_PORT (8081) is bound for the first time — it is unpublished by the compose block, so it stays reachable only inside the container and on the Docker network;

  • the session cookie gets the secure flag (server.servlet.session.cookie.secure follows SSL_ENABLED).

Changing HTTPS_PORT here means changing the container side of the ports: mapping in compose.yml to match — otherwise Compose publishes a port nothing listens on.

For the standalone docker run form in Docker Compose, the equivalent of step 3 is one more -v flag: -v ./ft-qoe-web/ssl:/etc/app/ssl:ro.

HTTPS_PORT and HTTP_PORT must differ — the application throws at startup if both ports are equal while SSL is enabled (HttpsConnectorConfig). The values used in this guide (8080 / 8081) and the application defaults from application.yml (8443 / 8080) both satisfy this.

6. Deployment

6.1. Startup Dependencies

FT QoE Web has no depends_on on its infrastructure — the database, ClickHouse, and Hazelcast live outside this stack, so Compose cannot gate on their health. All of them must be up before FT QoE Web launches: the Hazelcast client connects during context initialisation, and the JDBC pools fail their first health probe if the database or ClickHouse are unreachable.

Wait for each of them explicitly before starting the service:

# Wait for the database
until nc -z <db-host-ip> 3306; do sleep 2; done

# Wait for ClickHouse (HTTP port)
until nc -z <clickhouse-host-ip> 8123; do sleep 2; done

# Wait for Hazelcast
until nc -z <hazelcast-host-ip> 5701; do sleep 2; done

# Then start FT QoE Web
docker compose up -d ft-qoe-web

FT QoE Web itself restarts cleanly at any time once the infrastructure is up — restart: unless-stopped reconnects it after a host reboot.

6.2. Docker Compose

Click to expand compose.yml (FT QoE Web)
services:

  ft-qoe-web:
    image: hub.friendly-tech.com/qoe/ft-qoe-web:latest
    container_name: ft-qoe-web
    # The database, ClickHouse and Hazelcast are external to this stack, so
    # there is no depends_on. They must be healthy before ft-qoe-web starts --
    # DB_HOST, CLICKHOUSE_HOST and HZ_MEMBERS in .env point at them.
    env_file:
      - .env
      - ./ft-qoe-web/.env
    environment:
      # Interpolated here, not in ft-qoe-web/.env: Compose does not expand
      # ${...} inside env_file values, so ${TZ} has to be resolved in this
      # block. QOE_WEB_JAVA_RAM is the optional fixed-heap override.
      JAVA_OPTS: "${QOE_WEB_JAVA_RAM:-} -Duser.timezone=${TZ:-Europe/Kyiv}"
      # Optional: DB_HOST/DB_PORT/DB_USER/DB_PASSWORD from the root .env are
      # already enough to connect all three datasources (application-mysql.yml
      # falls back MAIN_DB_URL -> MYSQL_HOST/PORT/USER/PASSWORD -> DB_HOST/
      # PORT/USER/PASSWORD). Spelling out the full URLs here is only needed to
      # pin a non-default schema name or extra JDBC parameters.
      MAIN_DB_URL: jdbc:mysql://${DB_HOST}:${DB_PORT}/ftacs?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
      MAIN_DB_USERNAME: ${DB_USER}
      MAIN_DB_PASSWORD: ${DB_PASSWORD}
      QUARTZ_DB_URL: jdbc:mysql://${DB_HOST}:${DB_PORT}/ftacs_quartz?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
      QUARTZ_DB_USERNAME: ${DB_USER}
      QUARTZ_DB_PASSWORD: ${DB_PASSWORD}
      UI_DB_URL: jdbc:mysql://${DB_HOST}:${DB_PORT}/ftacs_qoe_ui?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
      UI_DB_USERNAME: ${DB_USER}
      UI_DB_PASSWORD: ${DB_PASSWORD}
      # FT QoE Web uses the official ClickHouse JDBC driver, so the URL targets
      # the HTTP port (CLICKHOUSE_PORT, 8123) -- not the native TCP port 9000.
      CLICKHOUSE_DB_URL: jdbc:clickhouse://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT}/${CLICKHOUSE_DATABASE}
      CLICKHOUSE_DB_USERNAME: ${CLICKHOUSE_USERNAME}
      CLICKHOUSE_DB_PASSWORD: ${CLICKHOUSE_PASSWORD}
    ports:
      - "${QOE_WEB_PORT:-8086}:8080"
    volumes:
      - ./ft-qoe-web/config:/etc/app/cache:ro
      - ./ft-qoe-web/logs:/app/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/qoeUI/actuator/health"]
      interval: 30s
      timeout: 3s
      start_period: 60s
      retries: 3
    restart: unless-stopped
    extra_hosts:
      - "host.docker.internal:host-gateway"   # peers running on this host outside Docker
    networks:
      - ft-network

networks:
  ft-network:
    driver: bridge

For Oracle, set SPRING_PROFILES_ACTIVE=oracle together with the Oracle DB_HOST / DB_PORT in the root .env — there is no separate Oracle env file — and replace the MAIN_DB_URL / QUARTZ_DB_URL / UI_DB_URL values above with Oracle JDBC URLs (jdbc:oracle:thin:@//<host>:1521/<service>) if you pin them explicitly. The complete variable list is in Switching to Oracle.

6.2.1. Start the Stack

Start FT QoE Web only after the database and ClickHouse accept connections, and Hazelcast is reachable — see Startup Dependencies.

cd /usr/local/ft-system
docker login hub.friendly-tech.com
docker compose up -d ft-qoe-web
docker compose ps

To run FT QoE Web as a single container without Compose:

cd /usr/local/ft-system

docker run -d \
  --name ft-qoe-web \
  --restart unless-stopped \
  --env-file .env --env-file ./ft-qoe-web/.env \
  -e JAVA_OPTS="-Duser.timezone=Europe/Kyiv" \
  -e MAIN_DB_URL="jdbc:mysql://<db-host>:3306/ftacs?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC" \
  -e MAIN_DB_USERNAME="ftacs" \
  -e MAIN_DB_PASSWORD="<your-db-password>" \
  -e QUARTZ_DB_URL="jdbc:mysql://<db-host>:3306/ftacs_quartz?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC" \
  -e QUARTZ_DB_USERNAME="ftacs" \
  -e QUARTZ_DB_PASSWORD="<your-db-password>" \
  -e UI_DB_URL="jdbc:mysql://<db-host>:3306/ftacs_qoe_ui?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC" \
  -e UI_DB_USERNAME="ftacs" \
  -e UI_DB_PASSWORD="<your-db-password>" \
  -e CLICKHOUSE_DB_URL="jdbc:clickhouse://<clickhouse-host>:8123/ftacs_qoe_ui_data?socket_timeout=120000" \
  -e CLICKHOUSE_DB_USERNAME="ftacs" \
  -e CLICKHOUSE_DB_PASSWORD="<your-ch-password>" \
  -v ./ft-qoe-web/config:/etc/app/cache:ro \
  -v ./ft-qoe-web/logs:/app/logs \
  -p 8086:8080 \
  hub.friendly-tech.com/qoe/ft-qoe-web:latest

6.2.2. Build the Docker Image Manually (for developers)

If you are a developer or need a customized build, you can create the Docker image manually from source.

Building the project requires access to Friendly Technologies private Maven dependencies. You must configure the following environment variables for authentication:

  • GITHUB_USERNAME — Your GitHub username

  • GITHUB_TOKEN — A GitHub personal access token with read:packages scope

Building also requires JDK 25 locally (the Gradle toolchain will download it if a matching JDK is not already installed) — this is a build-time requirement only; the running container needs no JDK, only the bundled JRE base image.

./gradlew clean build
docker build -t ft-qoe-web:latest .

7. Verification

7.1. Startup Log

Wait 30—​60 seconds after start, then check the logs:

docker compose logs ft-qoe-web | tail -20

# FT QoE Web takes 1-2 minutes to start
docker compose logs -f ft-qoe-web | grep -i "started"

Look for Hazelcast members overridden from env HZ_MEMBERS: …​ to confirm the client picked up the real cluster address rather than the bundled 127.0.0.1:5701 default.

7.2. Endpoint Checks

Check application health:

curl -s http://localhost:8086/qoeUI/actuator/health

The login screen is the check that does not depend on the actuator being present (see the note under HTTP Endpoints) — it should answer with a 200 or a redirect to the login page:

curl -sI http://localhost:8086/qoeUI

Open the same URL in a browser to see the login screen:

http://localhost:8086/qoeUI

The context path is always /qoeUI — it is not configurable. All application URLs are relative to it.

7.3. Database Connectivity

Check that the Quartz scheduler tables were auto-initialized in ftacs_quartz on first start:

USE ftacs_quartz;
SHOW TABLES LIKE 'qrtz_%';

If ftacs and ftacs_qoe_ui are empty, that points at the schema-creation prerequisite in Required External Dependencies, not at FT QoE Web — it never creates those tables itself.

Check ClickHouse reachability from inside the container — on the HTTP port 8123, not the native TCP port 9000. CLICKHOUSE_HOST exists only inside the container, so the expansion has to happen there: single-quote the command and run it through the container’s shell, otherwise your own shell expands the variable to nothing before docker exec starts.

docker exec ft-qoe-web sh -c 'nc -zv "$CLICKHOUSE_HOST" 8123'

8. Port Reference

Ports are written as published → container. Across hosts you connect to the published port; inside the Docker bridge you connect to the container port. The published port is configurable through QOE_WEB_PORT in ft-qoe-web/.env.

8.1. HTTP / HTTPS

Port Protocol Purpose Exposure

8086 → 8080

HTTP (HTTPS when SSL_ENABLED=true)

QoE dashboard, REST API, health check — published port set by QOE_WEB_PORT

Public (operators and API clients)

The container-side port (8080 above) is controlled by the HTTPS_PORT environment variable, which must match the right-hand side of the ports: mapping in compose.yml. Despite the name, it serves plain HTTP whenever SSL_ENABLED=false (the default) — see TLS Keystore (optional).

The Dockerfile also declares EXPOSE 8443 8080 5005 (the application default ports plus the JDWP debug port). EXPOSE publishes nothing on its own: the secondary HTTP_PORT connector is only bound when SSL_ENABLED=true, and 5005 only listens when DEBUG_MODE is set. The compose block above publishes neither.

8.2. Outbound Connections

Ports FT QoE Web dials on the existing infrastructure. These are the published ports of those services — confirm them against whoever operates them, and set the matching variables in .env.

Destination Port Protocol Purpose

Relational database

3306 (Oracle: 1521)

TCP

Three datasources; set through DB_HOST / DB_PORT

ClickHouse

8123

HTTP

QoE time-series queries; set through CLICKHOUSE_HOST / CLICKHOUSE_PORT

Hazelcast cluster

5701

TCP

Cache client connection; set through HZ_MEMBERS, or in hazelcast-client.yaml — see Hazelcast Client Configuration

SMTP mail server

587

TCP (STARTTLS)

Scheduled report delivery; set through MAIL_HOST / MAIL_PORT

8.3. HTTP Endpoints

Paths are relative to the host and published port, for example http://<host>:8086/qoeUI.

Method Path Purpose Auth

GET

/qoeUI

QoE dashboard login screen

Public login form

GET

/qoeUI/swagger-ui

Swagger UI (springdoc-openapi)

Public (permitAll in SecurityConfiguration)

GET

/qoeUI/v3/api-docs

OpenAPI document behind Swagger UI

Public (permitAll in SecurityConfiguration)

GET, POST

/qoeUI/qoe/…​

QoE REST API

Bearer token — see REST API — Authentication

GET, POST

/qoeUI/api/…​

Dashboard data endpoints

Session, role USER or ADMIN

GET

/qoeUI/actuator/health

Health check target of the Dockerfile and compose healthchecks

None — called by the container healthcheck from localhost

spring-boot-starter-actuator is not a declared dependency in this repository’s build.gradle, so this guide cannot prove the endpoint is served — it may come in transitively through a com.friendly:* library, or not at all. The path is documented as-is because it is what the platform ships as the healthcheck (full-system deployment — Docker Compose File) and what the Dockerfile in this repository calls.

If docker compose ps never reports healthy, do not treat the service as down on that evidence alone: check the login page instead (curl -sI http://localhost:8086/qoeUI — see Endpoint Checks) and raise the healthcheck path with FT DevOps.

9. Stack Management

9.1. Logs

# Follow logs
docker compose logs -f ft-qoe-web

# Last 100 lines
docker compose logs --tail 100 ft-qoe-web

The application writes to ft-qoe-web/logs/ on the host (mounted at /app/logs in the container). logback-spring.xml resolves the log directory as ${LOG_DIR:-${APP_HOME}/logs}; APP_HOME comes from the Spring property app.home, which nothing in this repo sets, so it falls back to ./, and the Dockerfile WORKDIR is /app — making the effective directory /app/logs. Set LOG_DIR explicitly to write elsewhere; the image also pre-creates /var/log/app, which stays empty unless LOG_DIR points at it.

9.2. Start, Stop, Restart

cd /usr/local/ft-system

docker compose ps                    # status of every service
docker compose stop ft-qoe-web       # stop the service
docker compose start ft-qoe-web      # start it again
docker compose restart ft-qoe-web    # restart it

9.3. Shell Access

docker exec -it ft-qoe-web sh
docker exec ft-qoe-web ls -la /app/logs

9.4. Updating FT QoE Web

cd /usr/local/ft-system

# 1. Back up the FT QoE Web configuration and logs
tar -czf ft-qoe-web-backup-$(date +%Y%m%d).tar.gz ./ft-qoe-web

# 2. Update the image tag in compose.yml, then:
docker compose pull ft-qoe-web
docker compose up -d ft-qoe-web

FT QoE Web does not run its own schema migrations — back up the ftacs_qoe_ui schema on the database host (outside this stack) before upgrading, per your existing FTACS/database backup procedure.

10. Production Checklist

  • Change all default passwords in .env / ft-qoe-web/.env (DB_PASSWORD, CLICKHOUSE_PASSWORD, and — if TLS is terminated in-app — SSL_KEY_STORE_PASSWORD plus SERVER_SSL_KEY_PASSWORD / SERVER_SSL_TRUST_STORE_PASSWORD where they are used).

  • Set QOE_JWT_SECRET to a base64 key of at least 32 bytes, identical on every node — left unset, a key is generated at startup and every issued REST API token dies with the container.

  • Set HZ_MEMBERS in the root .env to the real Hazelcast cluster address — it overrides the bundled hazelcast-client.yaml default of 127.0.0.1:5701. Mount a custom hazelcast-client.yaml only if cluster-name also needs to change from dev.

  • Configure firewall rules — allow only the required ports (Network Requirements); restrict database, ClickHouse, and Hazelcast access to trusted hosts only.

  • Decide TLS termination — either set SSL_ENABLED=true with a CA-issued keystore, or confirm a reverse proxy / ingress in front terminates TLS.

  • Enable auto-restart — restart: unless-stopped in compose.yml.

  • Verify the health check — the compose file defines it for docker compose up; add --health-cmd if running a standalone docker run.

  • Configure backups — the ftacs_qoe_ui schema, the ClickHouse QoE data, and log rotation for ft-qoe-web/logs/.

  • Confirm mail credentials if scheduled QoE report delivery is used (MAIL_HOST, MAIL_USERNAME, MAIL_PASSWORD).

  • Tune JVM heap — set QOE_WEB_JAVA_RAM in ft-qoe-web/.env (it feeds JAVA_OPTS in the compose environment: block) if the container-aware default (-XX:MaxRAMPercentage=75.0) does not fit your host’s memory budget.

11. Troubleshooting

For application-level issues (database connection failures, Hazelcast cluster warnings, ClickHouse query timeouts), see the application troubleshooting page. The items below are specific to the container / deployment layer and are not covered there.

11.1. Container Fails to Start

Symptom: The ft-qoe-web container exits immediately after docker compose up.

Fix:

  1. Read the error message from the logs:

    docker compose logs ft-qoe-web
    docker events --filter container=ft-qoe-web
  2. Confirm the environment files are loaded:

    docker exec ft-qoe-web env | grep DB_URL
  3. Confirm HZ_MEMBERS is set in .env — without it, and with an empty mount at /etc/app/cache, the bundled 127.0.0.1:5701 default stays in effect, which is a silent misconfiguration rather than a startup failure (see Hazelcast Cluster Issues).

11.2. Wrong Database Targeted Despite Correct DB_HOST

Symptom: DB_HOST / DB_PORT / DB_USER / DB_PASSWORD in .env are correct and point at the intended database, but FT QoE Web still connects somewhere else (or with different credentials).

Cause: MAIN_DB_URL / QUARTZ_DB_URL / UI_DB_URL / CLICKHOUSE_DB_URL always win over DB_HOST and the generic MYSQL_* / CLICKHOUSE_* variables when set — see the NOTE in Environment Configuration. A leftover *_DB_URL from an earlier config (or copied from another environment) silently overrides the values you just edited in .env.

Fix: Check whether a *_DB_URL is set in ft-qoe-web/.env or the compose environment: block before assuming .env alone controls the connection:

+

docker exec ft-qoe-web env | grep -E 'DB_URL|MYSQL_HOST|DB_HOST'

+ Remove or update the stale override, or leave DB_HOST / DB_PORT / DB_USER / DB_PASSWORD as the only source of truth and drop the *_DB_URL variables entirely if you do not need a non-default schema name.

11.3. Hazelcast Cluster Issues

Symptom: The log shows Unable to connect to any address in the config, or the application appears to run but caching silently has no effect (it is reaching 127.0.0.1:5701 inside its own container instead of the real cluster).

Fix:

  1. Confirm HZ_MEMBERS is set in the root .env and points at a reachable address — this is what ft-cache uses to override the bundled hazelcast-client.yaml member list at startup (see the IMPORTANT note in Hazelcast Client Configuration). Look for Hazelcast members overridden from env HZ_MEMBERS: …​ in the startup log to confirm it took effect.

  2. If HZ_MEMBERS is unset, the mounted hazelcast-client.yaml’s `network.cluster-members is what’s actually used — confirm the file is mounted and not the bundled 127.0.0.1:5701 default: docker exec ft-qoe-web cat /etc/app/cache/hazelcast-client.yaml.

  3. Verify cluster-name in hazelcast-client.yaml matches the value configured on the Hazelcast server (default dev) — there is no environment override for the cluster name, only for the member list.

  4. HAZELCAST_ADDRESS / HAZELCAST_CLUSTER_NAME are a different, unwired pair of environment variables — they are not read anywhere in this codebase; do not confuse them with HZ_MEMBERS.

11.4. ClickHouse Connection Fails

Symptom: QoE data queries fail; the log reports a ClickHouse connection or protocol error.

Fix:

  1. Confirm FT QoE Web targets ClickHouse’s HTTP port 8123 — the same port FTACS uses — and not the native TCP port 9000. In CLICKHOUSE_DB_URL this is jdbc:clickhouse://<host>:8123/<database>.

  2. Verify connectivity from inside the container. The variable must be expanded by the container’s shell, not yours — hence the single quotes and the sh -c:

    docker exec ft-qoe-web sh -c 'nc -zv "$CLICKHOUSE_HOST" 8123'

11.5. Port Already in Use

Symptom: docker compose up fails with bind: address already in use.

Fix:

  1. Find the process holding the port:

    sudo lsof -i :8086
    netstat -tulpn | grep 8086
  2. Either stop that process, or change the published port through QOE_WEB_PORT in ft-qoe-web/.env and run docker compose up -d again.

11.6. Permission Denied on Mounted Volumes

Symptom: The container cannot write to ft-qoe-web/logs.

Fix:

sudo chown -R $(whoami):$(whoami) /usr/local/ft-system/ft-qoe-web
chmod -R 755 /usr/local/ft-system/ft-qoe-web

The image runs as a non-root user (appuser, uid 1001) — if the host directory is owned by a different uid, the container cannot write to it even with 755 permissions; chown the directory to uid 1001 instead if $(whoami) on the host does not resolve to that uid inside the container’s namespace.

11.7. Out of Memory

Symptom: java.lang.OutOfMemoryError in the logs, or the container is killed by the OOM killer.

Fix:

  1. Raise the container memory limit (Docker Desktop: Settings → Resources → Memory).

  2. Tune the heap through QOE_WEB_JAVA_RAM in ft-qoe-web/.env, which the compose block appends to JAVA_OPTS — either raise the container memory limit so the default -XX:MaxRAMPercentage=75.0 yields more heap, or pin fixed -Xmx values.

  3. If the problem persists, capture a heap dump and analyse it for leaks.

11.8. Getting Support

If the issue persists, collect the following and contact Friendly Tech support:

  1. .env and ft-qoe-web/.env with passwords masked.

  2. docker compose logs ft-qoe-web — the last 1000 lines.

  3. docker version output.

  4. Database type and version.

  5. ClickHouse version.

← Back | Main Page