Installation & Deployment

This guide covers deploying the UI Backend — the Spring Boot REST API service of the OneIoT Web platform (hub.friendly-tech.com/ui/backend) — on a dedicated host with Docker.

For HTTPS/TLS setup, see SSL Certificate Configuration. For image lifecycle details, see Docker Image Management Guide.

1. Overview

This guide deploys a single container, ui-backend, from the image hub.friendly-tech.com/ui/backend. Docker is the only supported deployment method — the repository builds no distribution archive, and the JRE is bundled in the image.

All persistent infrastructure (database, Hazelcast, FTACS, ClickHouse) is a prerequisite: it must already be running and reachable. This guide does not deploy it — it wires the backend to it via environment variables. See Required External Dependencies and Full System Deployment for deploying that infrastructure.

The UI Backend is the only service deployed here. Its companions are documented and released separately:

  • UI Portals (Support Center & Management Console — Angular / Nginx) — the browser-facing web applications that call this backend. Released from the portals repository; see All-in-One Deployment for a combined single-host layout.

  • AI Agent — optional assistant service. See AI Agent Service Deployment.

The backend exposes a dual-datasource REST API (FTACS schema + IOTW schema), talks to FTACS over SOAP for device operations, uses Hazelcast for distributed caching/session sharing, and queries ClickHouse for QoE analytics.

2. Prerequisites

2.1. Host Requirements

Component Minimum Recommended Notes

Docker Engine

20.10+

Latest stable

The shipped compose.yml uses Compose schema 3.8.

Docker Compose

2.0+

Latest stable

All commands in this guide use the docker compose (v2) syntax.

RAM

3 GB free

verify with the deployment owner

The backend JVM defaults to -Xms512m -Xmx2g (FT_UI_BACKEND_JAVA_RAM).

Disk space

15 GB free

verify with the deployment owner

Image, configuration under DATA_FOLDER, and rotating application logs.

Java is not required on the host — the JRE is bundled inside the container image (eclipse-temurin:25-jre). The service is a Spring Boot 3.5.6 application packaged as a WAR (pom.xml:8-10). The UI Backend is distributed as a Docker image only; there is no supported bare-metal package for it.

2.2. Required External Dependencies

These services must be installed, running, and reachable from this host before the UI Backend starts. None of them are deployed by this guide.

Component Minimum Version Why It Is Needed Port Required

MySQL or Oracle

MySQL 8.0+ / Oracle 21c+

Main application database. Without it the backend cannot start — both the FTACS and the IOTW schema must be reachable.

3306 (TCP, MySQL) / 1521 (TCP, Oracle)

Yes

Hazelcast

5.5

Distributed cache and session sharing between backend instances. Without it the backend has no shared cache or session store.

5701 (TCP)

Yes

FTACS

verify with the FTACS release owner

ACS SOAP web service. Without it device provisioning, parameter reads and firmware management fail.

8080 (HTTP)

Yes

ClickHouse

26.2

QoE analytics store. Queries use the native protocol; Flyway migrates the analytics schema over HTTP at startup.

9000 (native TCP) / 8123 (HTTP)

No (optional)

UI Portals (companion)

Released with the backend version

Angular front-ends that consume this backend’s REST API. Released separately; the backend starts without them.

8880 (HTTP) / 8843 (HTTPS)

No (optional)

Hazelcast 5.5 is the client library version compiled into the backend (com.hazelcast:hazelcast 5.5.0 in pom.xml). The ClickHouse client versions are com.github.housepower:clickhouse-native-jdbc 2.7.1 (queries, native TCP) and com.clickhouse:clickhouse-jdbc 0.9.6 (Flyway migrations, HTTP).

ClickHouse 26.2 is the supported version. The development reference compose in this repository (iot-web-app-build/docker/docker-compose.yml) still pins clickhouse/clickhouse-server:24.8; deploy 26.2 unless instructed otherwise — verify the pinned tag with FT DevOps.

2.3. Supported Operating Systems

Deployment Operating system

Docker (Compose or docker run)

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 FT_DISK on SharePoint for the deployment files (compose.yml, the shared .env, and the per-service ui-backend/.env) and the pre-configured ui-backend-conf package.

3. Network Requirements

The backend opens the following outbound connections. Ports are the defaults shipped in the shared .env and in ui-backend/.env.

Destination Port Protocol Purpose

MySQL or Oracle database

3306 / 1521

TCP (JDBC)

FTACS and IOTW schemas (DB_HOST, DB_PORT).

Hazelcast members

5701

TCP

Cluster membership, distributed cache and sessions (HZ_MEMBERS).

FTACS application server

8080

HTTP (SOAP)

Device provisioning, parameter reads, firmware management (ACS_WEB_SERVICE_HOST, ACS_WEB_SERVICE_PORT).

ClickHouse

9000

native TCP

QoE analytics queries (CLICKHOUSE_HOST, CLICKHOUSE_PORT).

ClickHouse

8123

HTTP

Flyway migration of the analytics schema at startup (CLICKHOUSE_HTTP_PORT).

Inbound traffic is limited to the published REST API port — see Port Reference.

If FTACS is configured to push event notifications back to this backend, the address in FTACS_WEBHOOK_BASE_URL must be the backend’s externally reachable host and published port (default 8881), not the internal 8880. See Environment Variable Reference.

3.1. Docker Networking

If the database, Hazelcast, FTACS, or ClickHouse runs on the same host but outside Docker, use host.docker.internal as the hostname in the .env file instead of localhost:

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

Inside a container, localhost points to the container itself, not the host. host.docker.internal resolves to the host’s gateway address. The provided compose.yml already maps it:

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

On Docker Desktop (macOS, Windows) this works out of the box; on Linux the extra_hosts mapping above is required (and is already present in the shipped compose.yml). Alternatively, use the host’s real LAN IP address.

4. Registry Authentication

All UI Backend images are pulled from hub.friendly-tech.com as hub.friendly-tech.com/ui/backend:<version>. Authenticate once per host before the first docker compose up.

docker login hub.friendly-tech.com

Enter the read-only pull credentials when prompted:

Field Value

Username

readonly

Password

fokxuw-fymte1-taSxyc

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

Alternatively, log in non-interactively:

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

Verify authentication:

docker info | grep -A 5 Registry

4.1. Offline Servers

When the UI Backend 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. 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 release pipeline can build multi-architecture images (linux/amd64, linux/arm64) — see .github/workflows/build-all-services.yml — but the default build target is linux/amd64, so a specific published tag is not guaranteed to include arm64. If you deploy to arm64, confirm the tag you pull actually contains an arm64 manifest (verify with the release owner), or build that platform explicitly. 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=<version>
    
    docker pull --platform "$PLATFORM" "hub.friendly-tech.com/ui/backend:$TAG"
    docker save "hub.friendly-tech.com/ui/backend:$TAG" | gzip > "ui-backend-$TAG.tar.gz"

    Windows (PowerShell):

    $PLATFORM = "linux/amd64"
    $TAG = "<version>"
    
    docker pull --platform $PLATFORM "hub.friendly-tech.com/ui/backend:$TAG"
    docker save -o "ui-backend-$TAG.tar" "hub.friendly-tech.com/ui/backend:$TAG"

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

  4. Transfer the archive to the offline host, together with compose.yml, the shared .env, the per-service ui-backend/.env, and the pre-configured ui-backend-conf package that populates ui-backend/config/.

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

    Linux (bash):

    TAG=<version>
    
    gzip -dc "ui-backend-$TAG.tar.gz" | docker load
    docker compose up -d ui-backend

    Windows (PowerShell):

    $TAG = "<version>"
    
    docker load -i "ui-backend-$TAG.tar"
    docker compose up -d ui-backend

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/ui/backend

Upgrades use the same flow: pull the new tag on the connected machine, transfer and load the archive, then docker compose up -d ui-backend. See Updating UI Backend.

This covers the UI Backend image only. The database, Hazelcast, FTACS, and ClickHouse are separate images on their own hosts — for the offline procedure covering a whole stack, see All in one server deployment — Offline Servers, which derives the image list from compose.yml itself.

5. Preparation

5.1. Directory Structure

Create the base directory and the backend’s config / logs folders.

/usr/local/ft-system may require sudo. Alternatively use a path in your home directory (e.g. ~/ft-system) and adjust all paths accordingly. This layout follows the same base-directory and per-service config/logs convention as the Full System Deployment guide; this deployment prepares only the subset needed for the UI Backend.

mkdir -p /usr/local/ft-system/ui-backend/{config,logs}
cd /usr/local/ft-system

Every volume in compose.yml is written as ${DATA_FOLDER:-.}/…​, so with DATA_FOLDER unset the mounts resolve against the current directory — which is why the cd above is what actually fixes the install root. Setting DATA_FOLDER in the shell is not required, and relying on it is fragile: the value is lost in the next login shell, and ui-backend/.env does not supply it either, because Compose reads env_file: into the container environment, not into its own variable interpolation. To pin the path independently of the working directory, put DATA_FOLDER in the shared .env next to compose.yml — that one Compose does read for interpolation.

Download compose.yml and the shared .env from the FT_DISK on SharePoint into /usr/local/ft-system, and the per-service ui-backend/.env into /usr/local/ft-system/ui-backend/. Download the pre-configured backend config files from the ui-backend-conf package into /usr/local/ft-system/ui-backend/config/.

5.1.1. Directory Layout

The tree shows the baseline layout plus common environment-dependent entries. Not every deployment needs every entry — some are default runtime config, some are loaded only when present, and some are used only for specific integrations.

/usr/local/ft-system/                           # DATA_FOLDER
├── compose.yml                                 # from FT_DISK
├── .env                                        # shared stack environment (from FT_DISK)
└── ui-backend/
    ├── .env                                    # per-service environment (from FT_DISK)
    ├── config/                                 # -> /opt/app/conf   (backup required)
    │   ├── hazelcast-client.xml                # Hazelcast client configuration
    │   ├── logback-spring.xml                  # External logging configuration
    │   ├── ftacs.keystore                      # Backend keystore (only if backend serves HTTPS)
    │   ├── interfaceItems.json                 # Interface seed data, loaded if present
    │   ├── sc-idp.crt / mc-idp.crt             # SAML IdP certificates (SAML only)
    │   ├── customization/                      # Portal customization JSON
    │   │   ├── def/                            # Default system-wide customizations
    │   │   ├── mc/                             # Management Console customizations
    │   │   └── sc/                             # Support Center customizations
    │   ├── ssl/                                # TLS certificates for the portals companion
    │   └── keys/                               # Integration credentials (when used)
    └── logs/                                   # -> /opt/app/logs   (created automatically)
Path Content Backup

compose.yml

Stack definition: the ui-backend service block, its image tag, published port, env files and volumes.

Yes

.env

Shared stack environment: database connection, ClickHouse, Hazelcast, inter-service hosts.

Yes

ui-backend/.env

Per-service environment: host port, JVM settings, log level, connection pools, optional integrations.

Yes

ui-backend/config/

Backend configuration. At minimum hazelcast-client.xml plus customizations; the SharePoint package may add service-specific files and directories.

Yes

ui-backend/logs/

Backend application logs.

No

The backend container merges configuration on startup: files you place in ui-backend/config/ take priority; any missing file is filled in from the image defaults. interfaceItems.json is always refreshed from the image.

5.2. Environment Configuration

The stack uses a two-layer environment file architecture — there is no per-database-vendor environment file. compose.yml loads both layers, in this order:

env_file:
  - .env                 # shared stack environment
  - ./ui-backend/.env    # per-service environment
File Purpose

.env

Database connection (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD), ClickHouse, Hazelcast, inter-service hosts and URLs, DATA_FOLDER, TZ

ui-backend/.env

Backend host port, JVM settings, log level, FT_UI_BACKEND_KEYWORD, HikariCP pool tuning, SSL keystore, LDAP/SAML integration

MySQL is the default. Switching to Oracle is not a file swap: change DB_PROFILE, SPRING_PROFILES_ACTIVE, DB_HOST, DB_PORT (and the remaining variables listed in Switching to Oracle) inside the single shared .env. compose.yml maps the generic DB_* names to the vendor-specific ones the backend expects (MYSQL_HOST, ORACLE_HOST, …​) through the x-db-env YAML anchor.

5.2.1. Host Variables

Edit the shared .env and replace every ~PLACEHOLDER~ value with a real hostname or IP address before starting the service.

Placeholder Variable Description

~HOST~

FT_UI_HOST

Public hostname or IP of this server, reachable from end-user browsers and from FTACS. Use a real IP or DNS name, not localhost.

~HAZELCAST_HOST~

HZ_MEMBERS

Hazelcast cluster member address(es). Format host:port, comma-separated for multiple members.

~FTACS_HOST~

ACS_WEB_SERVICE_HOST

Hostname or IP of the FTACS application server.

~CLICKHOUSE_HOST~

CLICKHOUSE_HOST

Hostname or IP of the ClickHouse server. Leave the placeholder if ClickHouse is not deployed — analytics features are skipped.

~MYSQL_HOST~

DB_HOST

Hostname or IP of the MySQL server (when DB_PROFILE=mysql). Both FTACS and IOTW schemas must be reachable here.

~ORACLE_HOST~

DB_HOST

Hostname or IP of the Oracle server (when DB_PROFILE=oracle). Both FTACS and IOTW schemas must be reachable here.

Example (shared .env):

FT_UI_HOST=192.168.1.100
HZ_MEMBERS=192.168.1.50:5701,192.168.1.51:5701
ACS_WEB_SERVICE_HOST=192.168.1.60
CLICKHOUSE_HOST=192.168.1.70
DB_HOST=192.168.1.80

Replace the default passwords (ftacs, friendly, superset-12345) before deploying to any non-development environment.

5.2.2. Environment Variable Reference

Shared .env — stack variables
Variable Description Default Required

DATA_FOLDER

Base directory for persistent data and configuration on the host. Set it to the install root used by this guide, /usr/local/ft-system.

/usr/local/ft-services

Yes

TZ

Container timezone.

Europe/Kyiv

Yes

DB_PROFILE

Database vendor: mysql or oracle. Selects the vendor-specific datasource configuration.

mysql

Yes

SPRING_PROFILES_ACTIVE

Activates the database-specific Spring configuration in the backend. Keep it equal to DB_PROFILE.

mysql

Yes

ACS_WEB_SERVICE_HOST

FTACS application host.

~FTACS_HOST~

Yes

ACS_WEB_SERVICE_PORT

FTACS application port.

8080

Yes

FTACS_WEBHOOK_BASE_URL

Backend base URL as reachable by FTACS for webhook event notifications. Set to the externally published host and port.

http://~HOST~:8880

No (optional)

HZ_MEMBERS

Hazelcast cluster members (comma-separated host:port).

~HAZELCAST_HOST~:5701

Yes

CLICKHOUSE_HOST

ClickHouse server address.

~CLICKHOUSE_HOST~

No (optional)

CLICKHOUSE_PORT

ClickHouse native protocol port used for analytics queries.

9000

No (optional)

CLICKHOUSE_HTTP_PORT

ClickHouse HTTP port used by Flyway for analytics schema migrations.

8123

No (optional)

CLICKHOUSE_DATABASE

ClickHouse database name (mapped to CLICKHOUSE_SCHEMA inside the container).

ftacs_qoe_ui_data

No (optional)

CLICKHOUSE_USERNAME / CLICKHOUSE_PASSWORD

ClickHouse credentials. Read directly only by the Flyway migration datasource; compose.yml also maps them to the names the analytics datasource reads (see the warning below).

ftacs / ftacs

No (optional)

The backend reads ClickHouse credentials under two different names. The Flyway migration datasource reads CLICKHOUSE_USERNAME / CLICKHOUSE_PASSWORD, but the analytics (native TCP) datasource reads CLICKHOUSE_USER / CLICKHOUSE_PASWORD — note the single S, which is a misspelling in application.yaml, not a typo in this guide.

Setting only CLICKHOUSE_USERNAME / CLICKHOUSE_PASSWORD therefore makes migrations succeed while every QoE query authenticates with the built-in default ftacs/ftacs and fails. The compose.yml block in Docker Compose maps the shared .env values onto both name pairs; keep that mapping if you write your own compose file.

ui-backend/.env — per-service variables
Variable Description Default Required

FT_UI_BACKEND_HTTP_PORT

Host port published for the backend REST API (maps to internal 8880).

8881

No (optional)

FT_UI_BACKEND_JAVA_RAM

Backend JVM heap settings.

-Xms512m -Xmx2g

No (optional)

FT_UI_BACKEND_LOG_LEVEL

Backend log level: info, debug, warn, error.

info

No (optional)

FT_UI_BACKEND_KEYWORD

Internal keyword the backend accepts for service-to-service calls (the portals companion uses it to upload permissions).

ft12Internal%^

Yes

SUPERSET_URL

Superset instance the backend queries for embedded dashboards.

https://superset.friendly-tech.com

No (optional)

SUPERSET_USERNAME / SUPERSET_PASSWORD

Superset credentials. Not present in the shipped env files — the backend falls back to these built-in defaults, so add both lines to ui-backend/.env whenever Superset dashboards are used.

superset / superset-12345

No (optional)

HikariCP pool tuning (FTACS_MAX_POOL, IOTW_MAX_POOL, CLICKHOUSE_MAX_POOL, HIKARI_*), the SSL keystore variables and the optional LDAP/SAML settings also live in this file — see the collapsible blocks below.

Database variables in the shared .env — MySQL (DB_PROFILE=mysql)
Variable Description Default Required

DB_HOST

MySQL server address.

~MYSQL_HOST~

Yes

DB_PORT

MySQL port. compose.yml passes it to the backend as MYSQL_PORT.

3306

Yes

DB_USER

Database username (FTACS schema). compose.yml passes it to the backend as MYSQL_USER.

ftacs

Yes

DB_PASSWORD

Database password (shared across schemas).

ftacs

Yes

Optional overrides let you split the FTACS and IOTW schemas onto separate hosts (MYSQL_HOST_IOTW, MYSQL_PORT_IOTW, MYSQL_SCHEMA_IOTW, and the _FTACS equivalents) or supply a full JDBC URL (MYSQL_URL_FTACS, MYSQL_URL_IOTW). All are commented out by default.

Database variables in the shared .env — Oracle (DB_PROFILE=oracle)
Variable Description Default Required

DB_HOST

Oracle server address.

~ORACLE_HOST~

Yes

DB_PORT

Oracle listener port. compose.yml passes it to the backend as ORACLE_PORT.

1521

Yes

DB_SERVICE

Oracle PDB service name. compose.yml passes it to the backend as ORACLE_SERVICE.

XEPDB1

Yes

DB_USER

Database username for the FTACS schema. compose.yml passes it to the backend as ORACLE_USER_FTACS.

ftacs

Yes

ORACLE_USER_IOTW

Oracle username for the IOTW schema.

iotw

Yes

DB_PASSWORD

Database password (shared across schemas).

ftacs

Yes

Optional integrations in ui-backend/.env (commented out by default)
  • LDAP — LDAP_URL, LDAP_BASE_DN, LDAP_USER_DN, search bases/filters, and role/domain mappings. See LDAP Login via Keycloak (OIDC).

  • SAML SSO — SAML_ENABLED (default false), SAML_REDIRECT_HOST, per-portal SP/IdP entity IDs and SSO URLs, and attribute mappings. When enabled, also mount sc-idp.crt / mc-idp.crt (see the compose snippet in Docker Compose). See SAML SSO Login.

  • SSL — SSL_KEYSTORE, SSL_KEYSTORE_PASSWORD, SSL_KEY_ALIAS, SSL_KEY_PASSWORD, SSL_KEYSTORE_TYPE. Only needed when the backend serves HTTPS directly (see TLS Keystore (optional)).

  • Connection pools / async — HikariCP (FTACS_MAX_POOL, IOTW_MAX_POOL, CLICKHOUSE_MAX_POOL, HIKARI_*) and executor tuning (ASYNC_*, SCHED_POOL). Defaults suit most deployments.

5.3. TLS Keystore (optional)

TLS termination for browsers is normally handled by the portals reverse proxy, so the backend serves plain HTTP on 8880 internally. Configure a backend keystore only when the backend must serve HTTPS directly.

The keystore file is method-neutral — any JKS or PKCS12 keystore works, however you obtain it (CA-issued, internal PKI, or self-signed for testing). Place it in ui-backend/config/ and point the backend at it:

SSL_KEYSTORE=${APP_HOME}/conf/ftacs.keystore
SSL_KEYSTORE_PASSWORD=<your-keystore-password>
SSL_KEYSTORE_TYPE=JKS

For full guidance across deployment scenarios, see SSL Certificate Configuration.

6. Deployment

6.1. Startup Dependencies

The backend does not manage its infrastructure and must not depends_on external containers. Before starting it, confirm each prerequisite is reachable. A simple wait-loop blocks until a dependency accepts connections.

The loops below read DB_HOST, HZ_MEMBERS and ACS_WEB_SERVICE_HOST from the shared .env. Compose reads that file for the container, but your shell does not — load it first, otherwise the variables expand to empty and the loops never terminate:

cd /usr/local/ft-system
set -a; . ./.env; set +a
# Wait for the database (MySQL example; use 1521 for Oracle)
until nc -z ${DB_HOST:-host.docker.internal} 3306; do
  echo "waiting for database..."; sleep 3
done

# Wait for Hazelcast (host taken from HZ_MEMBERS, whose format is host:port)
until nc -z ${HZ_MEMBERS%%:*} 5701; do
  echo "waiting for hazelcast..."; sleep 3
done

# Wait for FTACS
until curl -sf http://${ACS_WEB_SERVICE_HOST:-host.docker.internal}:8080 >/dev/null; do
  echo "waiting for FTACS..."; sleep 3
done

ClickHouse is optional — skip its wait-loop if analytics are not deployed. The backend starts without it and disables analytics features.

6.2. Docker Compose

The backend is defined in compose.yml. The relevant service block:

services:
  ui-backend:
    image: hub.friendly-tech.com/ui/backend:<version>   # <-- pin a released tag
    ports:
      - "${FT_UI_BACKEND_HTTP_PORT:-8881}:8880"          # host -> internal
    env_file:
      - .env                                             # shared stack environment
      - ./ui-backend/.env                                # per-service environment
    environment:
      USE_NOHUP: 0
      APP_HOME: "/opt/app"
      CLICKHOUSE_SCHEMA: "${CLICKHOUSE_DATABASE}"
      # The analytics datasource reads CLICKHOUSE_USER / CLICKHOUSE_PASWORD (single S);
      # only Flyway reads CLICKHOUSE_USERNAME / CLICKHOUSE_PASSWORD. Map both.
      CLICKHOUSE_USER: "${CLICKHOUSE_USERNAME}"
      CLICKHOUSE_PASWORD: "${CLICKHOUSE_PASSWORD}"
      JAVA_OPTS: "${FT_UI_BACKEND_JAVA_RAM:--Xms512m -Xmx2g} -Duser.timezone=${TZ}"
    volumes:
      - ${DATA_FOLDER:-.}/ui-backend/config:/opt/app/conf
      - ${DATA_FOLDER:-.}/ui-backend/logs:/opt/app/logs
      # SAML certificates (uncomment when SAML is enabled):
      # - ${DATA_FOLDER:-.}/ui-backend/config/sc-idp.crt:/opt/app/conf/sc-idp.crt
      # - ${DATA_FOLDER:-.}/ui-backend/config/mc-idp.crt:/opt/app/conf/mc-idp.crt
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: unless-stopped
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://127.0.0.1:8880/iot-webservice/swagger-ui/index.html" ]
      interval: 10s
      timeout: 5s
      retries: 5
    stop_grace_period: 45s

The SharePoint compose.yml also contains a portals service (the UI Portals companion). That service is released and documented separately — see All-in-One Deployment and the portals repository. This guide covers only ui-backend.

Start the backend:

cd /usr/local/ft-system
docker compose up -d ui-backend

Monitor startup:

docker compose logs -f ui-backend

6.2.1. Docker Run (Standalone)

To run the backend without Compose:

cd /usr/local/ft-system

docker run -d \
  --name ui-backend \
  --env-file .env \
  --env-file ui-backend/.env \
  -e USE_NOHUP=0 -e APP_HOME=/opt/app \
  -p 8881:8880 \
  --add-host host.docker.internal:host-gateway \
  -v /usr/local/ft-system/ui-backend/config:/opt/app/conf \
  -v /usr/local/ft-system/ui-backend/logs:/opt/app/logs \
  --restart unless-stopped \
  hub.friendly-tech.com/ui/backend:<version>

Ensure .env exists in /usr/local/ft-system and ui-backend/.env in /usr/local/ft-system/ui-backend, and that the database, Hazelcast, FTACS, and (optionally) ClickHouse are reachable before starting the container.

7. Verification

7.1. Startup Log

Check the container status first:

docker compose ps

Expected output:

NAME         STATUS     PORTS
ui-backend   healthy    0.0.0.0:8881->8880/tcp

Allow 30—​60 seconds after start, then inspect the logs:

docker compose logs ui-backend | tail -30

The backend prints a startup banner (from AcsAppConfig) followed by the Spring Boot ready marker. Values in angle brackets vary per deployment:

==================================================
    HTTP ports: 8880
    HTTPS ports: 8843
    ACS WS URL: http://<ftacs-host>:8080/ACSServer-ACS/ACSWebService?wsdl
    Swagger UI: /iot-webservice/swagger-ui/index.html
==================================================
INFO ... Started FtIotWebApp in <seconds> seconds (process running for <seconds>)

7.2. Endpoint Checks

# Swagger UI (also the container health endpoint)
curl -I http://localhost:8881/iot-webservice/swagger-ui/index.html

# OpenAPI document
curl http://localhost:8881/iot-webservice/v3/api-docs

# Actuator health (exposed endpoints: health, prometheus)
curl http://localhost:8881/actuator/health

Expected: HTTP 200 responses.

7.3. Database Connectivity

Both the FTACS and the IOTW schema must be reachable from the container:

# MySQL
docker compose exec ui-backend sh -c 'nc -zv "$DB_HOST" 3306'

# Oracle
docker compose exec ui-backend sh -c 'nc -zv "$DB_HOST" 1521'

The single quotes matter: $DB_HOST must be expanded by the shell inside the container, which has the env files loaded, not by your host shell.

Check the remaining external dependencies the same way:

# Hazelcast
docker compose exec ui-backend nc -zv <hazelcast-host> 5701

# FTACS SOAP endpoint
curl http://<ftacs-host>:8080

# ClickHouse (optional): 9000 native TCP for queries, 8123 HTTP for Flyway migrations
docker compose exec ui-backend nc -zv <clickhouse-host> 9000
docker compose exec ui-backend 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.

8.1. HTTP / HTTPS

Port Protocol Purpose Exposure

8881 → 8880

HTTP

UI Backend REST API under /iot-webservice, consumed by the UI Portals and by FTACS webhook callbacks. Configurable via FT_UI_BACKEND_HTTP_PORT.

Public (portals and FTACS)

8843 (container only)

HTTPS

Backend HTTPS connector (HTTPS_PORTS), active only when a keystore is configured. The shipped compose.yml does not publish it.

Internal (bridge only)

Verify the published port is listening:

netstat -tlnp | grep 8881

The companion UI Portals publish 8880 → 80 (HTTP) and 8843 → 443 (HTTPS). They are deployed from the portals package, not here.

8.2. Outbound Connections

Destination Port Protocol Purpose

MySQL or Oracle database

3306 / 1521

TCP (JDBC)

FTACS and IOTW schemas.

Hazelcast members

5701

TCP

Distributed cache and session sharing.

FTACS application server

8080

HTTP (SOAP)

Device provisioning, parameter reads, firmware management.

ClickHouse

9000

native TCP

QoE analytics queries.

ClickHouse

8123

HTTP

Flyway migration of the analytics schema at startup.

8.3. HTTP Endpoints

Method Path Purpose Auth

GET

/iot-webservice/swagger-ui/index.html

Swagger UI (also the container health check target).

None

GET

/iot-webservice/v3/api-docs

OpenAPI 3 document.

None

GET

/actuator/health

Actuator health status.

None

GET

/actuator/prometheus

Prometheus metrics.

None

9. Stack Management

9.1. Logs

# Container logs
docker compose logs -f ui-backend

# On-disk application log
tail -f /usr/local/ft-system/ui-backend/logs/application.log

To raise the log level, set it in ui-backend/.env and restart:

FT_UI_BACKEND_LOG_LEVEL=debug
docker compose restart ui-backend

9.2. Start, Stop, Restart

docker compose stop ui-backend
docker compose start ui-backend
docker compose restart ui-backend

To stop and remove the container:

# Stop (keeps data and config)
docker compose down

# Stop and remove named volumes (does NOT touch bind-mounted config/logs)
docker compose down -v

9.3. Shell Access

docker compose exec ui-backend bash

# Inspect loaded environment
docker compose exec ui-backend env | grep -E 'DB_|HZ_|ACS_|CLICKHOUSE_'

9.4. Updating UI Backend

# 1. Back up the config directory
tar -czf ui-backend-config-$(date +%Y%m%d).tar.gz /usr/local/ft-system/ui-backend/config/

# 2. Pull the new tag (registry-connected hosts)
docker compose pull ui-backend

# 3. Recreate the container with the new image
docker compose up -d ui-backend

For offline servers, replace step 2 with the manual image transfer flow in Offline Servers, then run docker compose up -d ui-backend.

10. Production Checklist

  • Change all default passwords: DB_PASSWORD and CLICKHOUSE_PASSWORD in the shared .env; SSL_KEYSTORE_PASSWORD, FT_UI_BACKEND_KEYWORD and — when Superset is used — SUPERSET_PASSWORD in ui-backend/.env.

  • Set FT_UI_HOST to an address reachable by end-user browsers and by FTACS — not localhost.

  • Verify FTACS_WEBHOOK_BASE_URL uses the backend’s externally published host and port (default 8881) when FTACS pushes webhooks.

  • Pin an explicit image tag (hub.friendly-tech.com/ui/backend:<version>) rather than latest.

  • Confirm external dependencies are reachable: database (FTACS + IOTW schemas), Hazelcast, FTACS, and ClickHouse if analytics are used.

  • Configure firewall rules — restrict inbound 8881 to the portals/reverse proxy and FTACS; restrict database access to backend hosts.

  • Enable auto-restart — restart: unless-stopped is set in compose.yml; add --restart unless-stopped to standalone docker run.

  • Configure TLS — terminate HTTPS at the portals reverse proxy, or configure the backend keystore (see TLS Keystore (optional)).

  • Set up monitoring — scrape /actuator/prometheus.

  • Configure backups — back up ui-backend/config/; rely on the external DB’s own backup strategy.

  • Tune the JVM — set FT_UI_BACKEND_JAVA_RAM to match host memory.

11. Troubleshooting

11.1. Container Fails to Start

Cause

Configuration error, missing environment file, or an unreachable dependency.

Fix
docker compose logs ui-backend
docker inspect ui-backend
docker compose exec ui-backend env | grep -E 'DB_|HZ_|ACS_'

11.2. Database Connection Failure

Cause

Database not running, wrong host/port, or bad credentials. Both FTACS and IOTW schemas must be reachable.

Fix
telnet <DB_HOST> 3306    # MySQL
telnet <DB_HOST> 1521    # Oracle
docker compose exec ui-backend env | grep -E 'DB_HOST|MYSQL_PORT|ORACLE_PORT|SPRING_PROFILES_ACTIVE'

Check that DB_HOST, DB_PORT, DB_PASSWORD, DB_PROFILE / SPRING_PROFILES_ACTIVE, and the service name in the shared .env match the database.

11.3. Hazelcast Connection Failure

Cause

The Hazelcast cluster is not running or not reachable.

Fix
telnet <HAZELCAST_HOST> 5701
docker compose exec ui-backend env | grep HZ_MEMBERS
cat /usr/local/ft-system/ui-backend/config/hazelcast-client.xml

Update HZ_MEMBERS with the correct addresses and confirm hazelcast-client.xml references $\{env.HZ_MEMBERS}.

11.4. Device Operations Fail (FTACS Unreachable)

Cause

The backend cannot reach the FTACS SOAP web service.

Fix
curl http://<ACS_WEB_SERVICE_HOST>:8080
docker compose exec ui-backend env | grep ACS_WEB_SERVICE

Confirm ACS_WEB_SERVICE_HOST and ACS_WEB_SERVICE_PORT are correct and FTACS is running.

11.5. Port Already in Use

Cause

Another process is bound to the host port.

Fix
sudo lsof -i :8881
# Either free the port, or change the mapping:
#   FT_UI_BACKEND_HTTP_PORT=9881 in ui-backend/.env

11.6. Permission Denied on Mounted Volumes

Cause

Host directory permissions do not allow the container user to read/write.

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

11.7. Out of Memory

Cause

JVM heap exceeds available memory, or the Docker memory limit is too low. The symptom is java.lang.OutOfMemoryError in the log or a container killed by the OOM killer.

Fix
  1. Increase the Docker memory limit (Docker Desktop: Settings → Resources → Memory: 4 GB+).

  2. Adjust the heap in ui-backend/.env:

    FT_UI_BACKEND_JAVA_RAM=-Xms1g -Xmx4g
  3. Restart: docker compose restart ui-backend.

11.8. Container Not Reachable From Host

Cause

The host firewall blocks traffic on Docker bridge interfaces (br-*) in the host INPUT/OUTPUT path. This is not a Compose or application issue.

Signs:

  • The container is healthy and responds on localhost inside the container.

  • Published ports look correct (0.0.0.0:8881→8880/tcp).

  • Requests from the host hang / time out (no ECONNREFUSED).

    Fix
    # IPv4 (all Docker bridges)
    iptables -I INPUT  1 -i br+ -j ACCEPT
    iptables -I OUTPUT 1 -o br+ -j ACCEPT
    
    # IPv6 (separate stack)
    ip6tables -I INPUT  1 -i br+ -j ACCEPT
    ip6tables -I OUTPUT 1 -o br+ -j ACCEPT

IPv4 working does not imply IPv6 works — they are separate firewall stacks. Docker programs NAT and FORWARD rules but does not override host INPUT/OUTPUT policies.

11.9. Getting Support

Collect the following before contacting Friendly Technologies support:

  • The image tag in use (docker compose images ui-backend).

  • Container logs (docker compose logs ui-backend > ui-backend.log).

  • The loaded environment with secrets removed (docker compose exec ui-backend env | grep -E 'DB_|HZ_|ACS_|CLICKHOUSE_').

  • The on-disk application log from /usr/local/ft-system/ui-backend/logs/.