Installation & Deployment
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 |
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 |
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 ( |
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 |
MySQL 8.4 / Oracle 21c |
Backs all three JDBC datasources ( |
MySQL 3306 (TCP), Oracle 1521 (TCP) |
Yes |
ClickHouse |
26.2 |
Stores the QoE time-series data (default schema |
8123 (HTTP) |
Yes |
Hazelcast |
5.0 (5.3+ recommended) |
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. This is the platform-wide server minimum from External Components; the client library this service embeds is |
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.
The figures above are platform-wide product decisions taken from External Components: MySQL 8.4 and Oracle 21c are the highest minimums any FT service states, and ClickHouse 26.2 is the supported minimum for the platform.
Provisioning below either figure is out of support even where this service happens to start.
|
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, and the two env layers are additionally kept as templates in the repository — see FT_DISK Downloads.
3. Third-Party Components
Every third-party product this service depends on, with the version it is built or verified
against. Values in the Technology / Version column are read off this repository — build.gradle, Dockerfile — except the external server minimums, which come from
External Components and are a platform-wide
product decision rather than something this repository pins. The Container runtime row is
neither: it points at Host Requirements instead of restating a figure, so this guide keeps
a single source for those minimums.
The table below is this service’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, Docker Compose — see Host Requirements for version minimums |
The only supported deployment method for this service |
Base image |
|
Runtime image the service is built from ( |
Java |
25 |
Language level and runtime ( |
Application framework |
Spring Boot 4.0.7 |
Web layer, JPA, Quartz scheduling, security, mail ( |
Relational database |
MySQL 8.4 or Oracle 21c |
Backs the |
MySQL driver |
|
JDBC access to MySQL ( |
Oracle driver |
|
JDBC access to Oracle ( |
ClickHouse |
ClickHouse 26.2, HTTP on 8123 |
QoE time-series storage. Platform-wide minimum |
ClickHouse driver |
|
Speaks HTTP, not the native TCP protocol — see ClickHouse Connection Fails ( |
Distributed cache |
Client library |
Client-side cache for CPE info, KPI data and parameters, TCP on 5701 |
SMTP server |
any, STARTTLS on 587 |
Scheduled QoE report delivery. Optional |
4. 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 ( |
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:
|
4.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 and CLICKHOUSE_HOST to the container names in that case, and list the Hazelcast container under network.cluster-members in hazelcast-client.yaml (see Hazelcast Client Configuration).
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
CLICKHOUSE_HOST=host.docker.internal
For Hazelcast the same address goes into hazelcast-client.yaml as - host.docker.internal:5701 under network.cluster-members — FT QoE Web does not read HZ_MEMBERS.
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.
5. 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 |
|
Password |
|
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
5.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 |
-
On the offline host, find out which architecture it runs — this is the value you will pass as
PLATFORMbelow. 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 -mon Linux, orecho $env:PROCESSOR_ARCHITECTUREin PowerShell on Windows. Map the result:docker versionreportsuname -m/ Windows reportsUse as PLATFORMamd64x86_64/AMD64linux/amd64arm64aarch64/ARM64linux/arm64On 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. -
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 -
Pull and export the image. Paste the whole block as-is; the only lines to change are
PLATFORMandTAGon 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 redirectingdocker savefrom PowerShell (docker save … > file.tar) corrupts the archive, because the PowerShell pipeline re-encodes the stream as text instead of passing raw bytes.docker loadthen fails withunexpected EOForinvalid tar header. To compress for transfer, use the built-intar.exe(Windows 10 1803+ / Server 2019+):tar.exe -czf ft-qoe-web.tar.gz ft-qoe-web-$TAG.tar. -
Transfer the archive to the offline host, together with
compose.yml,.env,ft-qoe-web/.env, andft-qoe-web/config/hazelcast-client.yaml. -
On the offline host, load the archive and start the service. Set
TAGto 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-webWindows (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.
6. Preparation
Create the directories FT QoE Web needs on the host and point it at the infrastructure listed in Required External Dependencies.
6.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 compose.yml, ft-qoe-web/.env and ft-qoe-web/config/hazelcast-client.yaml are published on FT_DISK (see FT_DISK Downloads); 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 not part of this mkdir — its content is needed only when TLS is terminated in the application, and TLS Certificate (optional) creates it as its first step. The compose block mounts it either way, so Docker creates it empty on first start if you never populate it. The certificate that goes into it is published on FT_DISK separately from the two configuration files, in the ssl subfolder of the same service folder.
6.1.1. Directory Layout
Once the files below are in place, the FT QoE Web part of /usr/local/ft-system/ looks like this — the same 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 # from FT_DISK -- ft-qoe-web (or the FT System <vendor> Stack)
├── .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)
│ ├── friendly.crt # PEM pair from FT_DISK, or
│ ├── friendly.key
│ └── server.p12 # a PKCS#12 keystore instead
└── logs/ # -> /app/logs (written by the container)
| Path | Content | Backup |
|---|---|---|
|
Stack definition, including the |
Yes |
|
Shared stack environment: |
Yes |
|
Per-service environment: connector ports, TLS switches, HikariCP pool tuning, |
Yes |
|
|
Yes |
|
Optional TLS certificate — a |
Yes |
|
Application log files written by the container at |
No |
6.2. FT_DISK Downloads
Download the following from the
ft-qoe-web folder on SharePoint
(FT_DISK → RD → prerelease → ft-qoe-web); the root .env comes from the FT System MySQL Stack / FT System Oracle Stack folder or from the sample in Environment Configuration:
| Component | FT_DISK | Target | Files |
|---|---|---|---|
Stack definition |
ft-qoe-web |
|
|
Shared environment |
FT System MySQL Stack / FT System Oracle Stack |
|
|
FT QoE Web environment |
ft-qoe-web |
|
Only one file: the |
FT QoE Web configuration |
ft-qoe-web |
|
|
TLS certificate (optional) |
ft-qoe-web, |
|
|
The folder carries no per-vendor environment file, and that is deliberate: the database vendor is selected once, in the root .env (SPRING_PROFILES_ACTIVE plus the DB_* values) — not by copying a .env.oracle over a .env.
The Script for clearing QoE data from ACS up to (and including) 6.4.14 subfolder in the same FT_DISK folder is not part of a deployment — it belongs to QoE Data Cleanup.
These are the same folder and the same 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. All of them are reproduced inline in this document as well (Environment Configuration, Hazelcast Client Configuration, Docker Compose), 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.
The same two env layers are also kept in the repository as templates, so a change to the service and a change to its configuration travel together:
| Template in the repository | Corresponds to |
|---|---|
|
The root |
|
|
FT_DISK stays the canonical copy for a deployment; the templates carry no host-specific values and
exist so the variable set can be reviewed against build.gradle, application*.yml and the
Dockerfile in the same commit that changes them.
The repository also holds a docker-compose.yaml at its root — that one is a developer stack
(locally built Hazelcast and config server, debug port 5005) and is not the deployment compose
described in Docker Compose.
6.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 |
|---|---|
|
Shared stack environment: database connection ( |
|
FT QoE Web only: connector ports, TLS switches, HikariCP pool tuning, 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 ( So the root |
# /usr/local/ft-system/.env
TZ=Europe/Kyiv
# Compose-level values: referenced as ${...} in compose.yml, so they must be
# here and not in ft-qoe-web/.env -- Compose does not interpolate env_file.
QOE_WEB_PORT=8086
QOE_WEB_JAVA_RAM=
# 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) -- read by the other FT services, NOT by FT QoE Web:
# put the same address into ft-qoe-web/config/hazelcast-client.yaml
HZ_MEMBERS=<hazelcast-host-ip>:5701 # <-- replace
# ClickHouse (existing) -- HTTP port, the same one FTACS uses
CLICKHOUSE_HOST=<clickhouse-host-ip> # <-- replace
# Platform-wide name for the HTTP port. In the full-system stack the root .env
# also carries CLICKHOUSE_PORT=9000 (native TCP) for the services that need it;
# FT QoE Web reads CLICKHOUSE_HTTP_PORT first and falls back to CLICKHOUSE_PORT,
# so a standalone host may set either one to 8123.
CLICKHOUSE_HTTP_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
# =============================================================================
# NOTE: QOE_WEB_PORT and QOE_WEB_JAVA_RAM are NOT set here. They are referenced
# as ${...} in compose.yml, and Compose does not interpolate env_file values --
# both live in the root .env instead. See the admonition in the guide.
# 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. The FT_DISK copy ships true, with the self-signed PEM
# pair published next to it -- replace the pair with a real certificate in
# production. Set false when TLS is terminated upstream (nginx, Traefik,
# ingress).
SSL_ENABLED=true
# --- TLS, read only when SSL_ENABLED=true (see the TLS Certificate section) ---
# PEM certificate and key, the form published on FT_DISK. Copy the pair to
# /usr/local/ft-system/ft-qoe-web/ssl/; compose.yml mounts that directory at
# /etc/app/ssl.
SERVER_SSL_CERTIFICATE=file:/etc/app/ssl/friendly.crt
SERVER_SSL_CERTIFICATE_PRIVATE_KEY=file:/etc/app/ssl/friendly.key
# Set it, and set it empty. Its application.yml default (changeit) is applied
# to the PEM key, which carries no password, and startup fails with
# "Get Key failed: Given final block not properly padded".
SSL_KEY_STORE_PASSWORD=
# TLS protocol versions offered by the connector.
# SSL_ENABLED_PROTOCOLS=TLSv1.3,TLSv1.2
# A CA-issued PKCS#12 keystore can be used instead of the PEM pair; the
# SSL_KEY_STORE* variables for it are described in the TLS Certificate section
# of the installation guide.
# 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
# =============================================================================
# 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.example.com
# MAIL_PORT=587
# MAIL_USERNAME=
# MAIL_PASSWORD= # <-- replace if enabled
# 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 follows two rules.
First, infrastructure values shared by every service on the host — TZ, SPRING_PROFILES_ACTIVE, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, CLICKHOUSE_HOST, CLICKHOUSE_HTTP_PORT, CLICKHOUSE_DATABASE, CLICKHOUSE_USERNAME and CLICKHOUSE_PASSWORD — belong in the root .env.
Second, any variable the compose file references as ${VAR} must also be in the root .env, whatever it configures: Compose interpolates only from the shell environment and from the .env file sitting next to compose.yml, and never from a file listed under env_file: (see [env-file-is-not-interpolated]).
That second rule covers QOE_WEB_PORT and QOE_WEB_JAVA_RAM.
Everything else 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).
|
A variable referenced as The failure is silent. Keep both variables in the root |
| Variable | Description | Default | Required |
|---|---|---|---|
|
Container time zone. |
|
No |
|
Database dialect profile — |
|
No |
|
Host port published by the compose |
|
No |
|
Primary connector port ( |
|
No |
|
Secondary plain-HTTP connector. Bound only when |
|
No |
|
Terminate TLS in the application. Also sets the session cookie |
|
No |
|
PEM certificate and its private key. Set both, or neither. When both are set they take precedence over the keystore variables below, which are then never read ( |
unset |
No |
|
Keystore location when |
|
No |
|
Keystore password. Also used as the private-key password when |
|
No |
|
Keystore format — |
|
No |
|
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 |
|
No |
|
TLS versions offered by the connector, comma-separated. |
|
No |
|
Password of the private key inside the keystore, when it differs from the keystore password. Spring Boot name only — there is no |
unset — the keystore password is used |
No |
|
Trust store used to verify client certificates (mutual TLS). Spring Boot names only — no |
unset — the JVM default trust store |
No |
|
Client-certificate policy — |
|
No |
|
Relational database host for all three datasources ( |
|
Yes |
|
Database port. |
|
Yes |
|
Database user for all three datasources. |
|
Yes |
|
Database password. |
|
Yes |
|
Full JDBC URLs. Set them only to use non-default schema names or extra JDBC parameters — they win over |
Derived from |
No |
|
HikariCP pool size for the main and UI datasources (the Quartz pool is fixed at 5 / 2). |
|
No |
|
HikariCP timeouts, in milliseconds. |
|
No |
|
ClickHouse host for the QoE time-series datasource. |
|
Yes |
|
ClickHouse HTTP port — the same one FTACS uses, not the native TCP port 9000. Read before |
|
Yes |
|
Fallback for the port when |
|
Yes |
|
ClickHouse database holding the QoE data. |
|
Yes |
|
ClickHouse credentials. |
|
Yes |
|
Full ClickHouse JDBC URL; wins over the three variables above. |
Derived from |
No |
|
Not read by FT QoE Web, although the other FT services use it. The Hazelcast members come only from |
— |
No |
|
Directory the Hazelcast client configuration is loaded from. |
|
No |
|
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 |
|
Data model interpretation mode. Must match |
|
No |
|
SMTP server for scheduled QoE report delivery. |
|
No |
|
SMTP credentials. |
|
No |
|
SMTP authentication and STARTTLS switches. |
|
No |
|
JVM flags. Leave unset: the image ships container-aware heap sizing ( |
the |
No |
|
Optional fixed-heap value (for example |
empty |
No |
|
Directory the file appenders write to. |
|
No |
|
Any non-empty value starts the JVM with the JDWP agent on port 5005. |
unset |
No |
6.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-store → SERVER_SSL_KEY_STORE, server.ssl.trust-store-password → SERVER_SSL_TRUST_STORE_PASSWORD.
Environment variables outrank application.yml, so where both forms are set the SERVER_SSL* value wins.
|
|
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.
|
6.3.2. Hazelcast Client Configuration
FT QoE Web reads its Hazelcast settings — the member addresses and the cluster name — from one place only: the hazelcast-client.yaml in the directory named by CACHE_CONFIG_PATH.
In the deployment guides that directory is /etc/app/cache, the container side of the ./ft-qoe-web/config mount, so the file to edit is ft-qoe-web/config/hazelcast-client.yaml on the host.
The copy built into the image points at 127.0.0.1:5701 and is used only when CACHE_CONFIG_PATH is unset — suitable only for local development.
|
FT QoE Web does not read Put the real addresses into
|
Mount the directory and point CACHE_CONFIG_PATH at it — every deployment except local development needs this:
CACHE_CONFIG_PATH=file:/etc/app/cache/
-v ./ft-qoe-web/config:/etc/app/cache:ro
Below is the full recommended configuration.
The copy on FT_DISK may still carry async-start: true and cluster-connect-timeout-millis: 5000 — change both as shown here (see the table below for why), and replace the member list with your cluster’s addresses.
# =============================================================================
# Hazelcast Client Configuration for FT QoE Web
# =============================================================================
# This configuration connects FT QoE Web to the Hazelcast cluster for
# distributed caching and messaging. FT QoE Web reads the member list and the
# cluster name ONLY from this file -- HZ_MEMBERS in .env is not read.
hazelcast-client:
# Cluster name must match the server-side cluster configuration
cluster-name: dev
properties:
# Shuffle member list to distribute load
hazelcast.client.shuffle.member.list: true
# Heartbeat timeout (ms) - increase for slow networks
hazelcast.client.heartbeat.timeout: 60000
# Heartbeat interval (ms)
hazelcast.client.heartbeat.interval: 5000
# Event thread pool size
hazelcast.client.event.thread.count: 5
# Event queue capacity
hazelcast.client.event.queue.capacity: 1000000
# Invocation timeout (seconds)
hazelcast.client.invocation.timeout.seconds: 120
network:
# List of Hazelcast cluster members -- replace with your cluster.
# 'hazelcast:5701' works only when Hazelcast is the 'hazelcast' container
# on the same Docker bridge; otherwise list every member's <ip>:5701.
cluster-members:
- hazelcast:5701
# Cluster routing mode - connect to all members
cluster-routing:
mode: "ALL_MEMBERS"
# Retry failed operations
redo-operation: true
# Connection timeout (ms)
connection-timeout: 60000
# TCP socket options
socket-options:
tcp-no-delay: false
keep-alive: true
reuse-address: true
linger-seconds: 3
buffer-size: 128
# Near cache configuration for frequently accessed data
near-cache:
default:
# Time to live (seconds) - data expires after this time
time-to-live-seconds: 90
# Max idle time (seconds) - data expires if not accessed
max-idle-seconds: 100
# Invalidate cache entries when they change on server
invalidate-on-change: true
# Store format in memory
in-memory-format: OBJECT
# Serialize keys for efficient storage
serialize-keys: true
# Local update policy
local-update-policy: INVALIDATE
# Preloader settings (disabled by default)
preloader:
enabled: false
directory: directory
store-initial-delay-seconds: 50
store-interval-seconds: 10
# Connection strategy for handling cluster disconnections
connection-strategy:
# Wait for the cluster at startup: FT QoE Web fills its KPI cache while
# starting and fails immediately if the client is not connected yet
async-start: false
# Reconnect mode - async reconnection
reconnect-mode: ASYNC
# Connection retry policy
connection-retry:
# Initial backoff delay (ms)
initial-backoff-millis: 2000
# Maximum backoff delay (ms)
max-backoff-millis: 60000
# Backoff multiplier
multiplier: 3
# How long to keep trying to reach the cluster before the client shuts
# down for good; -1 = never give up (survives a Hazelcast restart)
cluster-connect-timeout-millis: -1
# Jitter factor (0.0 - 1.0)
jitter: 0.5
# User code deployment (disabled - not needed for QoE)
user-code-deployment:
enabled: false
| Setting | Description |
|---|---|
|
Must match the name configured on the Hazelcast server and in the client files of the other FT services. Default: |
|
List of |
|
Keep |
|
|
|
How long cached entries remain valid (seconds). Adjust based on data freshness requirements. |
|
How long the client keeps trying to reach the cluster — at startup and after a connection loss — before it shuts itself down for good. A shut-down client is never revived, so the cache stays unusable until the container restarts. Use |
6.4. TLS Certificate (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 certificate 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.
The certificate can be supplied in either of two forms, and the choice changes only steps 2 and 4:
-
a PEM pair — a
.crtcertificate next to its.keyprivate key. This is the form FT_DISK publishes and the form the nginx-fronted FT services already consume, so a single certificate covers the whole stack. Use it unless a CA hands you something else. -
a PKCS#12 keystore — one
server.p12file, for a certificate a CA has already packaged that way.
Nothing has to be configured to choose between them: Spring Boot looks for the PEM properties first and falls back to the keystore only when they are absent (WebServerSslBundle.createKeyStore).
1. Create the certificate directory — the mkdir in Directory Structure creates only config and logs:
mkdir -p /usr/local/ft-system/ft-qoe-web/ssl
2. Put the certificate in it.
Option A — PEM pair (preferred). Download friendly.crt and friendly.key from FT_DISK, under FT_DISK → RD → prerelease → ft-qoe-web → ssl:
Place both files, side by side, in the directory from step 1:
/usr/local/ft-system/ft-qoe-web/ssl/friendly.crt
/usr/local/ft-system/ft-qoe-web/ssl/friendly.key
That pair is the Friendly Tech wildcard certificate, issued by the internal Friendly Tech Root CA and shared with the other FT services — FT UI Backend mounts the same two files at ui-backend/config/ssl/. Two properties of it are worth knowing before the first browser reaches the UI:
-
the issuer is an internal CA, so any client that does not already trust the Friendly Tech Root CA reports the certificate as untrusted. Import the root certificate on the clients, or accept the warning on a test bench — the connector itself is unaffected.
-
it is valid for
*.friendly-tech.com,friendly-tech.com,localhostand a short fixed list of lab IP addresses. Reaching the UI by any other host name or address fails host-name verification no matter how healthy the connector is. The wildcard also covers one label only:qoe.friendly-tech.commatches,qoe.qa.friendly-tech.comdoes not.
Option B — PKCS#12 keystore. Name it server.p12 and put it in the same directory. Use your CA-issued 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 config/keystore.p12 inside the image (classpath default) is a self-signed certificate intended only for local development — it is what the application falls back to when neither form is configured, and it must not be used in production.
3. Check that the directory is mounted. The compose block in Docker Compose already carries the ssl line, so on a stack built from this guide there is nothing to do here. A compose.yml that predates it needs the line added to the volumes: list of the ft-qoe-web service, next to the config and logs mounts. This step is the same for both options:
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.
Option A — PEM pair:
SSL_ENABLED=true
SERVER_SSL_CERTIFICATE=file:/etc/app/ssl/friendly.crt
SERVER_SSL_CERTIFICATE_PRIVATE_KEY=file:/etc/app/ssl/friendly.key
SSL_KEY_STORE_PASSWORD=
|
The empty
Blanking the variable — or removing it from every |
SSL_KEY_STORE and SSL_KEY_STORE_TYPE are not read in this mode; leaving them set does no harm, they are simply never reached.
SSL_KEY_ALIAS still applies — it names the key entry inside the in-memory keystore — but its default (ft-qoe-web) works, so leave it alone.
Neither PEM property has an SSL_* short alias: application.yml declares placeholders for the six keystore variables only (Two naming forms for the TLS variables), so the canonical SERVER_SSL_* names are the only way to set them.
Option B — PKCS#12 keystore:
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/.
Every path in these variables is a container path — /etc/app/ssl/ is where step 3 mounts the host directory /usr/local/ft-system/ft-qoe-web/ssl/, so file:/etc/app/ssl/friendly.crt resolves to the host file /usr/local/ft-system/ft-qoe-web/ssl/friendly.crt. If the mount is missing, the container starts pointing at files that do 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_PORTnow speaks HTTPS instead of plain HTTP — the UI moves fromhttp://<host>:8086/qoeUItohttps://<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
secureflag (server.servlet.session.cookie.securefollowsSSL_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.
The standalone docker run form in Docker Compose carries the same mount as a -v flag: -v ./ft-qoe-web/ssl:/etc/app/ssl:ro.
|
|
7. Deployment
7.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.
7.2. Docker Compose
The compose.yml shipped in the ft-qoe-web folder on FT_DISK:
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 and CLICKHOUSE_HOST in .env and network.cluster-members in
# ft-qoe-web/config/hazelcast-client.yaml point at them.
env_file:
- .env
- ./ft-qoe-web/.env
environment:
# No JAVA_OPTS here on purpose. The image already carries container-aware
# heap sizing and GC flags, and the JVM takes its zone from TZ, which the
# root .env sets -- so the common case needs nothing. Setting JAVA_OPTS
# REPLACES the image value, dropping those flags with it; uncomment the
# line below only to pin the heap, and repeat any image flag you still want.
# JAVA_OPTS: "${QOE_WEB_JAVA_RAM:--Xms512m -Xmx2g}"
# 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_HTTP_PORT, 8123) -- not the native TCP port 9000,
# which the platform's root .env calls CLICKHOUSE_PORT.
CLICKHOUSE_DB_URL: jdbc:clickhouse://${CLICKHOUSE_HOST}:${CLICKHOUSE_HTTP_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
# Certificate directory, read only when SSL_ENABLED=true. Harmless while
# TLS is terminated upstream: Compose creates the directory if it is
# missing and nothing in the container looks at it.
- ./ft-qoe-web/ssl:/etc/app/ssl:ro
healthcheck:
# Reads the port from the container environment, so it follows HTTPS_PORT in
# ft-qoe-web/.env instead of a hard-coded 8080. The $$ keeps Compose from
# expanding the variable on the host -- values set in an env_file are not
# available for interpolation, and ${HTTPS_PORT} would collapse to an empty
# string. The second probe covers SSL_ENABLED=true, where the same port
# answers HTTPS and the plain-HTTP probe fails (-k: the certificate is
# validated by clients, not by a loopback healthcheck).
test: ["CMD-SHELL", "curl -fs http://localhost:$${HTTPS_PORT:-8443}/qoeUI/actuator/health || curl -fsk https://localhost:$${HTTPS_PORT:-8443}/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
|
In the full FT System stack the same block additionally carries |
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.
7.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 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 \
-v ./ft-qoe-web/ssl:/etc/app/ssl:ro \
-p 8086:8080 \
hub.friendly-tech.com/qoe/ft-qoe-web:latest
7.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:
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 JRE base image. |
./gradlew clean build
docker build -t ft-qoe-web:latest .
8. Verification
8.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 Authenticated with server [<hazelcast-host>]:5701 and Members [N] to confirm the client reached the real cluster.
A container that keeps restarting with No connection found to cluster has the wrong address in hazelcast-client.yaml — see Hazelcast Cluster Issues.
8.2. Endpoint Checks
Check application health — the same endpoint the container healthcheck calls:
curl -s http://localhost:8086/qoeUI/actuator/health
Expect {"status":"UP"}.
With SSL_ENABLED=true the published port speaks HTTPS, so use curl -sk https://localhost:8086/qoeUI/actuator/health instead.
The login screen is the second check — 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 |
8.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'
9. 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 the root .env (see [env-file-is-not-interpolated]).
9.1. HTTP / HTTPS
| Port | Protocol | Purpose | Exposure |
|---|---|---|---|
8086 → 8080 |
HTTP (HTTPS when |
QoE dashboard, REST API, health check — published port set by |
Public (operators and API clients) |
|
The container-side port ( The |
9.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 |
ClickHouse |
8123 |
HTTP |
QoE time-series queries; set through |
Hazelcast cluster |
5701 |
TCP |
Cache client connection; the address is set in |
SMTP mail server |
587 |
TCP (STARTTLS) |
Scheduled report delivery; set through |
9.3. HTTP Endpoints
Paths are relative to the host and published port, for example http://<host>:8086/qoeUI.
| Method | Path | Purpose | Auth |
|---|---|---|---|
GET |
|
QoE dashboard login screen |
Public login form |
GET |
|
Swagger UI (springdoc-openapi) |
Public ( |
GET |
|
OpenAPI document behind Swagger UI |
Public ( |
GET, POST |
|
QoE REST API |
Bearer token — see REST API — Authentication |
GET, POST |
|
Dashboard data endpoints |
Session, role |
GET |
|
Health check target of the |
None — called by the container healthcheck from |
|
The endpoint answers with the bare status only ( With |
10. Stack Management
10.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.
10.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
10.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 |
11. 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_PASSWORDplusSERVER_SSL_KEY_PASSWORD/SERVER_SSL_TRUST_STORE_PASSWORDwhere they are used). -
Set
QOE_JWT_SECRETto 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. -
Point
ft-qoe-web/config/hazelcast-client.yamlat the real cluster —network.cluster-memberswith the Hazelcast addresses (FT QoE Web does not readHZ_MEMBERS),cluster-namematching the other services,async-start: false,cluster-connect-timeout-millis: -1. -
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=truewith a CA-issued keystore, or confirm a reverse proxy / ingress in front terminates TLS. -
Enable auto-restart —
restart: unless-stoppedincompose.yml. -
Verify the health check — the compose file defines it for
docker compose up; add--health-cmdif running a standalonedocker run. -
Configure backups — the
ftacs_qoe_uischema, the ClickHouse QoE data, and log rotation forft-qoe-web/logs/. -
Confirm mail credentials if scheduled QoE report delivery is used (
MAIL_HOST,MAIL_USERNAME,MAIL_PASSWORD). -
Tune JVM heap — only if the container-aware default (
-XX:MaxRAMPercentage=75.0) does not fit your host’s memory budget: setQOE_WEB_JAVA_RAMin the root.envand uncomment theJAVA_OPTSline in the compose block (see [env-file-is-not-interpolated]).
12. 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.
12.1. Container Fails to Start
Symptom: The ft-qoe-web container exits immediately after docker compose up.
Fix:
-
Read the error message from the logs:
docker compose logs ft-qoe-web docker events --filter container=ft-qoe-web -
Confirm the environment files are loaded:
docker exec ft-qoe-web env | grep DB_URL -
If the container keeps restarting with
No connection found to cluster,network.cluster-membersinft-qoe-web/config/hazelcast-client.yamldoes not point at a reachable Hazelcast —HZ_MEMBERSin.envis not read by FT QoE Web (see Hazelcast Cluster Issues).
12.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.
12.3. Hazelcast Cluster Issues
Symptom: The container restarts every few seconds — docker ps shows a container created long ago but Up only seconds, and docker inspect ft-qoe-web --format '{{.RestartCount}}' keeps growing.
The database connections succeed, then each start ends a fraction of a second after Trying to connect to cluster: dev with:
Error creating bean with name 'kpiCache' ... No connection found to cluster
With async-start: false the container stays up instead, but startup never completes and the log repeats the connection attempts.
Cause: The client cannot reach the cluster described in the mounted hazelcast-client.yaml.
Most often network.cluster-members still holds the shipped hazelcast:5701 while Hazelcast runs on another server, on the assumption that HZ_MEMBERS in .env overrides it — it does not (see the IMPORTANT note in Hazelcast Client Configuration).
async-start: true turns this into the immediate failure above: the KPI cache is filled during startup, before the first connection has a chance to complete.
Fix:
-
Check what the mounted file says, on the host:
grep -nE 'cluster-name|:5701|async-start' ft-qoe-web/config/hazelcast-client.yaml -
Replace the member list with the real Hazelcast addresses, and confirm they are reachable from this host:
timeout 3 bash -c '</dev/tcp/<hazelcast-host>/5701' && echo OPEN || echo CLOSED -
Make
cluster-namematch the other FT services on the host:grep -rn --include=hazelcast-client.yaml cluster-name /usr/local/ft-system -
Set
async-start: falseandcluster-connect-timeout-millis: -1(see the table in Hazelcast Client Configuration). -
Restart the container — the file is mounted from the host, so it does not need to be recreated — and confirm the connection in the log:
docker restart ft-qoe-web docker logs -f ft-qoe-web 2>&1 | grep -iE 'Authenticated with server|Members \[|Started'Authenticated with server [<hazelcast-host>]:5701followed byMembers [N]means the client reached the cluster.
HZ_MEMBERS, HZ_CLUSTER_NAME, HAZELCAST_ADDRESS and HAZELCAST_CLUSTER_NAME are not read by FT QoE Web; changing them has no effect on this problem.
12.4. ClickHouse Connection Fails
Symptom: QoE data queries fail; the log reports a ClickHouse connection or protocol error.
Fix:
-
Confirm FT QoE Web targets ClickHouse’s HTTP port
8123— the same port FTACS uses — and not the native TCP port9000. InCLICKHOUSE_DB_URLthis isjdbc:clickhouse://<host>:8123/<database>. -
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'
12.5. Port Already in Use
Symptom: docker compose up fails with bind: address already in use.
Fix:
-
Find the process holding the port:
sudo lsof -i :8086 netstat -tulpn | grep 8086 -
Either stop that process, or change the published port through
QOE_WEB_PORTin the root.env(see [env-file-is-not-interpolated]) and rundocker compose up -dagain.
12.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.
12.7. Out of Memory
Symptom: java.lang.OutOfMemoryError in the logs, or the container is killed by the OOM killer.
Fix:
-
Raise the container memory limit (Docker Desktop: Settings → Resources → Memory).
-
Tune the heap through
QOE_WEB_JAVA_RAMin the root.env(see [env-file-is-not-interpolated]), which the commented-outJAVA_OPTSline in the compose block picks up — either raise the container memory limit so the default-XX:MaxRAMPercentage=75.0yields more heap, or pin fixed-Xmxvalues. -
If the problem persists, capture a heap dump and analyse it for leaks.
13. Related Documentation
-
Multi-Database Architecture — how the three JDBC datasources and ClickHouse are wired.
-
ClickHouse Time-Series Storage — the QoE schema this guide expects to exist.
-
Authentication — token handling for the endpoints listed in HTTP Endpoints.
-
Troubleshooting — application-level failures beyond the container layer.
-
All in one server deployment — Docker Compose File — deploying the database, ClickHouse and Hazelcast on a single host.
-
Separate server deployment — Server B: Hazelcast — the split topology this page assumes.