Installation & Deployment

← Back | Main Page

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

1. Overview

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

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

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

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

FT QoE Web is one of three services in the "Specialized" group of the Friendly Tech platform (alongside FT Device Network Service and FT System Metrics). The full-system deployment guides run all three from one compose.yml under the ft-special Compose project; this page documents FT QoE Web on its own, for a fully split topology or a standalone evaluation install. The ft-qoe-web folder on FT_DISK carries a single-service compose.yml in the platform layout for exactly that case, plus the per-service files; on a host running the full FT System stack the same service block is already part of the stack compose.yml from the FT System MySQL Stack / FT System Oracle Stack folder — use that one, and take the shared root .env from the same folder.

2. Prerequisites

2.1. Host Requirements

Component Minimum Recommended Notes

Docker Engine

20.10

Latest stable

Docker Compose is the only supported deployment method.

Docker Compose

2.0

Latest stable

This guide uses the docker compose v2 CLI syntax throughout.

CPU

verify with the infrastructure owner

 — 

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

RAM

verify with the infrastructure owner

 — 

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

Browser

Edge 81, Firefox 80, Chrome 81

Latest stable

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

2.2. Required External Dependencies

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

Component Minimum Version Why It Is Needed Port Required

MySQL or Oracle

MySQL 8.4 / Oracle 21c

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

MySQL 3306 (TCP), Oracle 1521 (TCP)

Yes

ClickHouse

26.2

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

8123 (HTTP)

Yes

Hazelcast

5.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 com.hazelcast:hazelcast 5.5.0 (build.gradle) — see Third-Party Components.

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

eclipse-temurin:25-jre-alpine

Runtime image the service is built from (Dockerfile)

Java

25

Language level and runtime (build.gradle)

Application framework

Spring Boot 4.0.7

Web layer, JPA, Quartz scheduling, security, mail (build.gradle)

Relational database

MySQL 8.4 or Oracle 21c

Backs the ftacs, ftacs_quartz and ftacs_qoe_ui datasources. Platform-wide minimum

MySQL driver

com.mysql:mysql-connector-j (version managed by Spring Boot)

JDBC access to MySQL (build.gradle)

Oracle driver

com.oracle.database.jdbc:ojdbc11 (version managed by Spring Boot)

JDBC access to Oracle (build.gradle)

ClickHouse

ClickHouse 26.2, HTTP on 8123

QoE time-series storage. Platform-wide minimum

ClickHouse driver

com.clickhouse:clickhouse-jdbc 0.9.8

Speaks HTTP, not the native TCP protocol — see ClickHouse Connection Fails (build.gradle)

Distributed cache

Client library com.hazelcast:hazelcast 5.5.0 (build.gradle); platform-wide server minimum is Hazelcast 5.0, 5.3+ recommended, per External Components

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 (ftacs), Quartz (ftacs_quartz), UI (ftacs_qoe_ui)

ClickHouse

8123

HTTP

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

Hazelcast

5701

TCP

Hazelcast client connection

SMTP mail server

587

TCP (STARTTLS)

Scheduled QoE report delivery (only when mail is configured)

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

For a quick connectivity check from any host:

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

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

readonly

Password

fokxuw-fymte1-taSxyc

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

Alternatively, log in non-interactively:

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

Verify authentication:

docker info | grep -A 5 Registry

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

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

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

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

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

    amd64

    x86_64 / AMD64

    linux/amd64

    arm64

    aarch64 / ARM64

    linux/arm64

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

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

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

    Linux / macOS (bash):

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

    Windows (PowerShell):

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

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

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

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

    Linux (bash):

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

    Windows (PowerShell):

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

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

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

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

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

compose.yml

Stack definition, including the ft-qoe-web service block — image, env_file, ports, volumes, healthcheck.

Yes

.env

Shared stack environment: TZ, database connection (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD), ClickHouse (CLICKHOUSE_*), Hazelcast (HZ_MEMBERS, read by the other FT services only — FT QoE Web takes the cluster address from config/hazelcast-client.yaml), and the compose-level QOE_WEB_PORT / QOE_WEB_JAVA_RAM.

Yes

ft-qoe-web/.env

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

Yes

ft-qoe-web/config/

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

Yes

ft-qoe-web/ssl/

Optional TLS certificate — a friendly.crt / friendly.key PEM pair or a PKCS#12 keystore — mounted read-only at /etc/app/ssl. Always mounted, but only populated when TLS is terminated in the application (TLS Certificate (optional)).

Yes

ft-qoe-web/logs/

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

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

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

compose.yml — single-service, in the platform layout (Docker Compose). Skip it on a host that runs the full FT System stack: its compose.yml already contains this service block

Shared environment

FT System MySQL Stack / FT System Oracle Stack

/usr/local/ft-system/.env

.env — shared by every FT service on the host

FT QoE Web environment

ft-qoe-web

ft-qoe-web/.env

Only one file: the .env in the folder root

FT QoE Web configuration

ft-qoe-web

ft-qoe-web/config/

hazelcast-client.yaml

TLS certificate (optional)

ft-qoe-web, ssl/ subfolder

ft-qoe-web/ssl/

friendly.crt, friendly.key — only when TLS is terminated in the application; the shipped ft-qoe-web/.env has SSL_ENABLED=true and points at this pair, see TLS Certificate (optional)

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

docker/.env.example

The root .env of the host — copy the values into the existing /usr/local/ft-system/.env, never over it: that file describes every FT service on the machine

docker/ft-qoe-web/.env.example

/usr/local/ft-system/ft-qoe-web/.env, the per-service layer FT_DISK publishes

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

.env

Shared stack environment: database connection (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD), ClickHouse, Hazelcast, time zone, and the database dialect (SPRING_PROFILES_ACTIVE) — shared by every service on the host — plus the compose-level QOE_WEB_PORT / QOE_WEB_JAVA_RAM, which live here only because Compose cannot read them from env_file: (see [env-file-is-not-interpolated]).

ft-qoe-web/.env

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 (application-mysql.yml): MAIN_DB_URL / QUARTZ_DB_URL / UI_DB_URL first, then the generic MYSQL_HOST / MYSQL_PORT / MYSQL_USER / MYSQL_PASSWORD, then DB_HOST / DB_PORT / DB_USER / DB_PASSWORD, and only then the built-in localhost:3306 / ftacs defaults. ClickHouse resolves the same way: CLICKHOUSE_DB_URL falls back to CLICKHOUSE_HOST / CLICKHOUSE_HTTP_PORT / CLICKHOUSE_DATABASE, and CLICKHOUSE_PORT is consulted only when CLICKHOUSE_HTTP_PORT is unset — the platform’s root .env uses CLICKHOUSE_PORT for the native TCP port (9000), which this driver cannot speak.

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

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

# 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 ${VAR} in compose.yml cannot live in a per-service env file. Compose resolves $\{…​} in the compose file from two places only: the shell environment, and the .env file in the project directory — the directory compose.yml sits in. Files named under env_file: are handed to the container at start-up; Compose never reads them while parsing the compose file.

The failure is silent. $\{QOE_WEB_JAVA_RAM:-} resolves to the empty string and the JVM simply starts without the flags; $\{QOE_WEB_PORT:-8086} falls back to 8086, so a changed value in ft-qoe-web/.env publishes nothing new and the mismatch only shows up when someone tries to move the port.

Keep both variables in the root .env. They are compose-level values, not application settings: neither is read by the application itself.

Variable Description Default Required

TZ

Container time zone.

Europe/Kyiv (Dockerfile)

No

SPRING_PROFILES_ACTIVE

Database dialect profile — mysql or oracle.

mysql (Dockerfile)

No

QOE_WEB_PORT

Host port published by the compose ports: mapping. Not read by the application itself. Belongs in the root .env — see [env-file-is-not-interpolated].

8086

No

HTTPS_PORT

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

8443

No

HTTP_PORT

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

8080

No

SSL_ENABLED

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

false; true in the shipped .env, together with the PEM pair from FT_DISK

No

SERVER_SSL_CERTIFICATE, SERVER_SSL_CERTIFICATE_PRIVATE_KEY

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 (WebServerSslBundle.createKeyStore). Spring Boot names only — no SSL_* aliases exist for them (Two naming forms for the TLS variables). Blank SSL_KEY_STORE_PASSWORD alongside them (TLS Certificate (optional)).

unset

No

SSL_KEY_STORE

Keystore location when SSL_ENABLED=true. Ignored when the SERVER_SSL_CERTIFICATE pair is set.

classpath:config/keystore.p12

No

SSL_KEY_STORE_PASSWORD

Keystore password. Also used as the private-key password when SERVER_SSL_KEY_PASSWORD is unset — which is why a PEM certificate needs this variable blanked (SSL_KEY_STORE_PASSWORD=), the PEM key having no password of its own.

changeit

No

SSL_KEY_STORE_TYPE

Keystore format — PKCS12 or JKS.

PKCS12

No

SSL_KEY_ALIAS

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

ft-qoe-web

No

SSL_ENABLED_PROTOCOLS

TLS versions offered by the connector, comma-separated.

TLSv1.3,TLSv1.2

No

SERVER_SSL_KEY_PASSWORD

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

unset — the keystore password is used

No

SERVER_SSL_TRUST_STORE, SERVER_SSL_TRUST_STORE_PASSWORD, SERVER_SSL_TRUST_STORE_TYPE

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

unset — the JVM default trust store

No

SERVER_SSL_CLIENT_AUTH

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

none

No

DB_HOST

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

localhost

Yes

DB_PORT

Database port.

3306

Yes

DB_USER

Database user for all three datasources.

ftacs

Yes

DB_PASSWORD

Database password.

ftacs

Yes

MAIN_DB_URL, QUARTZ_DB_URL, UI_DB_URL

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

Derived from DB_HOST / DB_PORT

No

DB_MAX_POOL_SIZE, DB_MIN_IDLE

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

10, 5

No

DB_CONNECTION_TIMEOUT_MS, DB_IDLE_TIMEOUT_MS, DB_MAX_LIFETIME_MS

HikariCP timeouts, in milliseconds.

30000, 600000, 1800000

No

CLICKHOUSE_HOST

ClickHouse host for the QoE time-series datasource.

localhost

Yes

CLICKHOUSE_HTTP_PORT

ClickHouse HTTP port — the same one FTACS uses, not the native TCP port 9000. Read before CLICKHOUSE_PORT, which the full-system stack sets to the native 9000.

8123

Yes

CLICKHOUSE_PORT

Fallback for the port when CLICKHOUSE_HTTP_PORT is unset. Point it at the HTTP port on a standalone host; in the full-system stack it is the native TCP port and QoE Web ignores it.

8123

Yes

CLICKHOUSE_DATABASE

ClickHouse database holding the QoE data.

ftacs_qoe_ui_data

Yes

CLICKHOUSE_USERNAME, CLICKHOUSE_PASSWORD

ClickHouse credentials. CLICKHOUSE_DB_USERNAME / CLICKHOUSE_DB_PASSWORD take precedence.

ftacs, ftacs

Yes

CLICKHOUSE_DB_URL

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

Derived from CLICKHOUSE_HOST / CLICKHOUSE_HTTP_PORT / CLICKHOUSE_DATABASE

No

HZ_MEMBERS

Not read by FT QoE Web, although the other FT services use it. The Hazelcast members come only from network.cluster-members in the mounted hazelcast-client.yaml — see Hazelcast Client Configuration.

 — 

No

CACHE_CONFIG_PATH

Directory the Hazelcast client configuration is loaded from.

classpath:config/

No

QOE_JWT_SECRET

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

empty

No

DM_ISP_MODE

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

2

No

MAIL_HOST, MAIL_PORT

SMTP server for scheduled QoE report delivery.

smtp.example.com, 587

No

MAIL_USERNAME, MAIL_PASSWORD

SMTP credentials.

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

No

MAIL_SMTP_AUTH, MAIL_SMTP_STARTTLS

SMTP authentication and STARTTLS switches.

true, true

No

JAVA_OPTS

JVM flags. Leave unset: the image ships container-aware heap sizing (-XX:MaxRAMPercentage=75.0, G1GC) and the JVM reads the zone from TZ. A value set here replaces the image’s, so any flag you still want has to be repeated.

the Dockerfile ENV value

No

QOE_WEB_JAVA_RAM

Optional fixed-heap value (for example -Xms512m -Xmx2g), used only if you uncomment the JAVA_OPTS line in the compose block. Unset, the image’s own heap sizing applies. Belongs in the root .env — see [env-file-is-not-interpolated].

empty

No

LOG_DIR

Directory the file appenders write to.

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

No

DEBUG_MODE

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

unset

No

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.

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

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 HZ_MEMBERS. The other FT services take the Hazelcast address from HZ_MEMBERS in the root .env; FT QoE Web is built against com.friendly:ft-cache 0.0.7, whose client configuration has no environment override — HZ_MEMBERS support arrived in ft-cache 0.0.8. Setting HZ_MEMBERS therefore changes nothing for this service, and its log never shows Hazelcast members overridden from env HZ_MEMBERS.

Put the real addresses into network.cluster-members of the mounted file. The shipped hazelcast:5701 is right only when Hazelcast runs as the hazelcast container on the same Docker bridge; when Hazelcast runs on another server, list that server’s address instead — otherwise FT QoE Web never reaches the cluster and restarts in a loop (see Hazelcast Cluster Issues).

HZ_CLUSTER_NAME, HAZELCAST_ADDRESS and HAZELCAST_CLUSTER_NAME are not read either: the cluster name is the cluster-name value in the same file.

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
Download example hazelcast-client.yaml

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
Table 1. Key settings to customize for your environment
Setting Description

cluster-name

Must match the name configured on the Hazelcast server and in the client files of the other FT services. Default: dev. There is no environment override.

network.cluster-members

List of <host>:<port> addresses of Hazelcast cluster nodes — the only place FT QoE Web takes them from; HZ_MEMBERS is not read. Keep hazelcast:5701 only when Hazelcast is the hazelcast container on the same Docker bridge; otherwise list every member’s address.

connection-strategy.async-start

Keep false. FT QoE Web fills its KPI cache while it starts, so with true it touches the cache before the first cluster connection completes and fails within a fraction of a second with Error creating bean with name 'kpiCache' …​ No connection found to cluster — Docker then restarts it in a loop. With false, startup waits for the cluster and the log shows why it cannot connect.

connection-strategy.reconnect-mode

ASYNC reconnects in the background after a connection drop.

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

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

connection-retry.cluster-connect-timeout-millis

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 -1 (never give up); the FT_DISK copy has 5000. With -1 and an unreachable cluster, startup waits and the log repeats the connection attempts.

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 .crt certificate next to its .key private 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.p12 file, 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, localhost and 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.com matches, qoe.qa.friendly-tech.com does 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 SSL_KEY_STORE_PASSWORD= on the last line is required, not a typo.

application.yml defaults that variable to changeit, and Spring Boot uses the keystore password as the private-key password whenever server.ssl.key-password is unset (DefaultSslManagerBundle) — including for the keystore it assembles in memory from the PEM pair. friendly.key carries no password, so the two do not match and the container dies during startup:

IllegalStateException: Could not load key manager factory:
Get Key failed: Given final block not properly padded

Blanking the variable — or removing it from every .env layer, which is not enough on its own because the default lives in application.yml — clears the password and the PEM key loads.

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_PORT now speaks HTTPS instead of plain HTTP — the UI moves from http://<host>:8086/qoeUI to https://<host>:8086/qoeUI;

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

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

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

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.

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

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 profiles: [qoe] and depends_on on the mysql (or oracle), clickhouse and hazelcast services, and sets CACHE_CONFIG_PATH: file:/etc/app/cache/ in its environment: block — COMPOSE_PROFILES in the root .env must then list qoe for a bare docker compose up -d to start it. Everything else, including the two-layer env_file: list and the mount paths, is identical.

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:

  • GITHUB_USERNAME — Your GitHub username

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

Building also requires JDK 25 locally (the Gradle toolchain will download it if a matching JDK is not already installed) — this is a build-time requirement only; the running container needs no JDK, only the 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 /qoeUI — it is not configurable. All application URLs are relative to it.

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 SSL_ENABLED=true)

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

Public (operators and API clients)

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

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

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 DB_HOST / DB_PORT

ClickHouse

8123

HTTP

QoE time-series queries; set through CLICKHOUSE_HOST / CLICKHOUSE_HTTP_PORT

Hazelcast cluster

5701

TCP

Cache client connection; the address is set in hazelcast-client.yaml (network.cluster-members) — see Hazelcast Client Configuration

SMTP mail server

587

TCP (STARTTLS)

Scheduled report delivery; set through MAIL_HOST / MAIL_PORT

9.3. HTTP Endpoints

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

Method Path Purpose Auth

GET

/qoeUI

QoE dashboard login screen

Public login form

GET

/qoeUI/swagger-ui

Swagger UI (springdoc-openapi)

Public (permitAll in SecurityConfiguration)

GET

/qoeUI/v3/api-docs

OpenAPI document behind Swagger UI

Public (permitAll in SecurityConfiguration)

GET, POST

/qoeUI/qoe/…​

QoE REST API

Bearer token — see REST API — Authentication

GET, POST

/qoeUI/api/…​

Dashboard data endpoints

Session, role USER or ADMIN

GET

/qoeUI/actuator/health

Health check target of the Dockerfile and compose healthchecks

None — called by the container healthcheck from localhost

The endpoint answers with the bare status only ({"status":"UP"}) — components and details are switched off, and no other actuator endpoint is exposed. Its aggregate covers the four datasources and free disk space; the mail indicator is deliberately excluded, so an unreachable SMTP relay does not drag the whole service to DOWN.

With SSL_ENABLED=true the primary connector serves the same path over HTTPS, which is why the healthcheck in Docker Compose tries plain HTTP first and falls back to HTTPS on the same port.

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.3. Shell Access

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

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 ftacs_qoe_ui schema on the database host (outside this stack) before upgrading, per your existing FTACS/database backup procedure.

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_PASSWORD plus SERVER_SSL_KEY_PASSWORD / SERVER_SSL_TRUST_STORE_PASSWORD where they are used).

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

  • Point ft-qoe-web/config/hazelcast-client.yaml at the real cluster — network.cluster-members with the Hazelcast addresses (FT QoE Web does not read HZ_MEMBERS), cluster-name matching 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=true with a CA-issued keystore, or confirm a reverse proxy / ingress in front terminates TLS.

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

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

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

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

  • Tune JVM heap — only if the container-aware default (-XX:MaxRAMPercentage=75.0) does not fit your host’s memory budget: set QOE_WEB_JAVA_RAM in the root .env and uncomment the JAVA_OPTS line 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:

  1. Read the error message from the logs:

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

    docker exec ft-qoe-web env | grep DB_URL
  3. If the container keeps restarting with No connection found to cluster, network.cluster-members in ft-qoe-web/config/hazelcast-client.yaml does not point at a reachable Hazelcast — HZ_MEMBERS in .env is 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:

  1. Check what the mounted file says, on the host:

    grep -nE 'cluster-name|:5701|async-start' ft-qoe-web/config/hazelcast-client.yaml
  2. 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
  3. Make cluster-name match the other FT services on the host:

    grep -rn --include=hazelcast-client.yaml cluster-name /usr/local/ft-system
  4. Set async-start: false and cluster-connect-timeout-millis: -1 (see the table in Hazelcast Client Configuration).

  5. 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>]:5701 followed by Members [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:

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

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

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

12.5. Port Already in Use

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

Fix:

  1. Find the process holding the port:

    sudo lsof -i :8086
    netstat -tulpn | grep 8086
  2. Either stop that process, or change the published port through QOE_WEB_PORT in the root .env (see [env-file-is-not-interpolated]) and run docker compose up -d again.

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:

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

  2. Tune the heap through QOE_WEB_JAVA_RAM in the root .env (see [env-file-is-not-interpolated]), which the commented-out JAVA_OPTS line in the compose block picks up — either raise the container memory limit so the default -XX:MaxRAMPercentage=75.0 yields more heap, or pin fixed -Xmx values.

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

12.8. Getting Support

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

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

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

  3. docker version output.

  4. Database type and version.

  5. ClickHouse version.

← Back | Main Page