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 |
verify with FT DevOps |
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.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 ( |
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:
|
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 |
|
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
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 |
-
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 bundledtar.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.
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 |
|---|---|---|
|
Service definition for |
Yes |
|
Shared stack environment: |
Yes |
|
Per-service environment: host port, connector ports, TLS switches, HikariCP pool tuning, |
Yes |
|
|
Yes |
|
Optional keystore, mounted read-only at |
Yes |
|
Application log files written by the container at |
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 |
|---|---|---|
|
|
|
|
|
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.
|
|
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 |
|---|---|
|
Shared stack environment: database connection ( |
|
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 ( So the root |
# /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 |
|---|---|---|---|
|
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 |
|
Keystore location when |
|
No |
|
Keystore password. |
|
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. |
|
Yes |
|
ClickHouse database holding the QoE data. |
|
Yes |
|
ClickHouse credentials. |
|
Yes |
|
Full ClickHouse JDBC URL; wins over the three variables above. |
Derived from |
No |
|
Hazelcast member list ( |
unset — the bundled |
Yes |
|
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. The image ships container-aware heap sizing ( |
the |
No |
|
Optional fixed-heap override fed into |
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 |
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-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.
|
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.
|
|
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
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
| Setting | Description |
|---|---|
|
Must match the name configured on the Hazelcast server. Default: |
|
List of |
|
|
|
How long cached entries remain valid (seconds). Adjust based on data freshness requirements. |
|
Maximum time (ms) the client will attempt to connect before giving up. Default: |
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_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.
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.
|
|
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:
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 |
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 |
QoE dashboard, REST API, health check — published port set by |
Public (operators and API clients) |
|
The container-side port ( The |
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 |
ClickHouse |
8123 |
HTTP |
QoE time-series queries; set through |
Hazelcast cluster |
5701 |
TCP |
Cache client connection; set through |
SMTP mail server |
587 |
TCP (STARTTLS) |
Scheduled report delivery; set through |
8.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 |
|
If |
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.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 |
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_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. -
Set
HZ_MEMBERSin the root.envto the real Hazelcast cluster address — it overrides the bundledhazelcast-client.yamldefault of127.0.0.1:5701. Mount a customhazelcast-client.yamlonly ifcluster-namealso needs to change fromdev. -
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 — set
QOE_WEB_JAVA_RAMinft-qoe-web/.env(it feedsJAVA_OPTSin the composeenvironment: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:
-
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 -
Confirm
HZ_MEMBERSis set in.env— without it, and with an empty mount at/etc/app/cache, the bundled127.0.0.1:5701default 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:
-
Confirm
HZ_MEMBERSis set in the root.envand points at a reachable address — this is whatft-cacheuses to override the bundledhazelcast-client.yamlmember list at startup (see the IMPORTANT note in Hazelcast Client Configuration). Look forHazelcast members overridden from env HZ_MEMBERS: …in the startup log to confirm it took effect. -
If
HZ_MEMBERSis unset, the mountedhazelcast-client.yaml’s `network.cluster-membersis what’s actually used — confirm the file is mounted and not the bundled127.0.0.1:5701default:docker exec ft-qoe-web cat /etc/app/cache/hazelcast-client.yaml. -
Verify
cluster-nameinhazelcast-client.yamlmatches the value configured on the Hazelcast server (defaultdev) — there is no environment override for the cluster name, only for the member list. -
HAZELCAST_ADDRESS/HAZELCAST_CLUSTER_NAMEare a different, unwired pair of environment variables — they are not read anywhere in this codebase; do not confuse them withHZ_MEMBERS.
11.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'
11.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_PORTinft-qoe-web/.envand rundocker compose up -dagain.
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:
-
Raise the container memory limit (Docker Desktop: Settings → Resources → Memory).
-
Tune the heap through
QOE_WEB_JAVA_RAMinft-qoe-web/.env, which the compose block appends toJAVA_OPTS— 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.
12. 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.