Installation & Deployment
This guide covers deploying the Service API — a Spring Boot 4 application built with Java 25 — as a single Docker container.
The infrastructure the Service API depends on — the ACS database and Hazelcast cluster, plus the Northbound API it calls for device operations — is assumed to be already deployed and reachable; this guide only points the Service API at it.
To deploy that infrastructure, see All in one server deployment or Separate server deployment — Server E: API Stack.
For every configuration item and its default, see Service API Configuration Guide.
1. Overview
The Service API is a single container (service-api) exposing REST and SOAP endpoints under the /iot-webservice context path for device service management and provisioning operations.
It is a plain Docker Compose service, built and shipped only as a container image — the Gradle build produces a bootJar (service-api.jar) that goes into the image, and there is no distribution archive for a manual or bare-metal install.
Service API -> Northbound API + ACS Database (ftacs schema) + Hazelcast
Everything to the right of the Service API box in that diagram is a prerequisite, not part of this installation.
|
To deploy Service API alongside Northbound API and Provision Portal as a single stack instead of standalone, see Java API Stack Deployment Guide. |
2. Prerequisites
2.1. Host Requirements
| Component | Minimum | Recommended | Notes |
|---|---|---|---|
Docker Engine |
20.10 |
20.10 or later |
If Docker is not installed, follow the Docker Installation Guide |
Docker Compose |
2.0 |
2.0 or later |
The |
RAM |
2 GB |
4 GB |
Single JVM container |
Free disk |
1 GB |
1 GB plus room for logs |
Image plus rolling logs in |
2.2. Required External Dependencies
These services must be installed, running, and reachable from this host before the Service API starts. None of them are deployed by this guide.
| Component | Minimum Version | Why It Is Needed | Port | Required |
|---|---|---|---|---|
MySQL or Oracle (ACS database) |
verify with FT DevOps — no database version is pinned in this repo; the JDBC drivers used are |
Holds the |
3306 (TCP) for MySQL, 1521 (TCP) for Oracle |
Yes |
Hazelcast |
5.6.0 (client library |
The application context wires a Hazelcast client bean; without a reachable cluster every cache-backed operation fails. |
5701 (TCP) |
Yes |
Northbound API |
verify with the Northbound API owner |
The Service API calls it over REST to dispatch device operations (GetParameterValues, SetParameterValues, and so on); without it no device operation reaches a device. |
8080 (HTTP) |
Yes |
The Hazelcast dependency is a functional dependency, not an optional cache.
The bundled hazelcast-client.yaml uses connection-strategy.async-start: true / reconnect-mode: ASYNC, so the client does not block startup: the container starts and connects in the background.
Until a cluster is reachable, cache-backed operations fail, so provide a reachable cluster before serving traffic.
(Whether the hazelcastCacheFactory bean itself fails fast depends on its implementation in the external api-common library — verify against a real startup log if strict fail-fast behavior matters.)
|
Deploying that infrastructure is out of scope here — see All in one server deployment for a single-host stack, or Separate server deployment — Server E for a split topology that brings up Northbound API and Service API together. |
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 FT_DISK — Service-API folder for the deployment files (compose.yml, the shared .env and the per-service service-api/.env) and the pre-configured service-api configuration package (service-api.yml, hazelcast-client.yaml).
3. Network Requirements
Connections the Service API opens to its infrastructure. Each row must be open through firewalls when the peer is on another host; traffic to peers on the same Docker bridge needs no rule.
| Destination | Port | Protocol | Purpose |
|---|---|---|---|
ACS Database |
3306 (Oracle: 1521) |
TCP |
|
Hazelcast |
5701 |
TCP |
Hazelcast client connection — see |
Northbound API |
8080 |
TCP |
REST calls to dispatch device operations (GetParameterValues, SetParameterValues, and so on) — |
Inbound traffic (the published HTTP/HTTPS port, including the Prometheus scrape of /iot-webservice/actuator/prometheus) is listed in Port Reference.
|
For a quick connectivity check from the Service API host:
|
3.1. Docker Networking
When the database, Hazelcast cluster, or Northbound API runs on the host machine rather than in Docker, use one of the following as the hostname in MYSQL_HOST / ORACLE_HOST, NORTHBOUND_API_URL, and in hazelcast-client.yaml:
-
host.docker.internal— works on Docker Desktop (macOS, Windows) and on Linux with--add-host=host.docker.internal:host-gateway(fordocker run). The providedcompose.ymlalready includesextra_hosts: - "host.docker.internal:host-gateway". -
The host machine’s real IP address (for example
192.168.1.10).
localhost or 127.0.0.1 will not work — inside the container these point to the container itself, not the host.
When a peer runs as a container on the same Compose network, use its container name instead (for example http://northbound-api:8080/iot-webservice).
4. Registry Authentication
The Service API image is pulled from hub.friendly-tech.com.
Authenticate once per host before the first docker compose up.
docker login hub.friendly-tech.com
Enter the read-only pull credentials when prompted:
| Field | Value |
|---|---|
Username |
|
Password |
|
The readonly account provides pull-only access to the published images. It cannot push.
|
Alternatively, log in non-interactively:
echo "fokxuw-fymte1-taSxyc" | docker login hub.friendly-tech.com -u readonly --password-stdin
Verify authentication:
docker info | grep -A 5 Registry
4.1. Offline Servers
When the Service API host cannot reach hub.friendly-tech.com, pull the image on a machine that does have registry access, export it to an archive, transfer it, and load it on the offline host.
The connected machine can run Linux, macOS, or Windows — commands are given for both shells below. It does not need to be the same platform as the offline host.
|
An explicit |
-
On the offline host, find out which architecture it runs — this is the value you will pass as
PLATFORMbelow. Ask Docker itself, since it reports what the daemon will actually accept:docker version --format '{{.Server.Arch}}'If Docker is not installed there yet, use the operating system instead —
uname -mon Linux, orecho $env:PROCESSOR_ARCHITECTUREin PowerShell on Windows. Map the result:docker versionreportsuname -m/ Windows reportsUse as PLATFORMamd64x86_64/AMD64linux/amd64arm64aarch64/ARM64linux/arm64On Windows with Docker Desktop,
{{.Server.Arch}}reports the architecture of the Linux VM that actually runs the containers — which is the value you want, not the Windows host’s own architecture. -
On a machine with registry access, log in. Run this on its own — it prompts for a password, so anything pasted after it on the same go would be swallowed as input:
docker login hub.friendly-tech.com -
Pull and export the image. Paste the whole block as-is; the only lines to change are
PLATFORMandTAGon top.Linux / macOS (bash):
PLATFORM=linux/amd64 TAG=latest docker pull --platform "$PLATFORM" "hub.friendly-tech.com/api/service-api:$TAG" docker save "hub.friendly-tech.com/api/service-api:$TAG" | gzip > "service-api-$TAG.tar.gz"Windows (PowerShell):
$PLATFORM = "linux/amd64" $TAG = "latest" docker pull --platform $PLATFORM "hub.friendly-tech.com/api/service-api:$TAG" docker save -o "service-api-$TAG.tar" "hub.friendly-tech.com/api/service-api:$TAG"On Windows, always write the archive with
docker save -o <file>. Piping or redirectingdocker savefrom PowerShell (docker save … > file.tar) corrupts the archive, because the PowerShell pipeline re-encodes the stream as text instead of passing raw bytes.docker loadthen fails withunexpected EOForinvalid tar header. To compress for transfer, use the bundledtar.exe(Windows 10 1803+ / Server 2019+):tar.exe -czf service-api.tar.gz service-api-$TAG.tar. -
Transfer the archive to the offline host, together with
compose.yml, the shared.env,service-api/.env, and theservice-api/config/directory (service-api.yml,hazelcast-client.yaml, andkeystore.p12if HTTPS is enabled). -
On the offline host, load the archive and start the service. Set
TAGto the same value you used above:Linux (bash):
TAG=latest gzip -dc "service-api-$TAG.tar.gz" | docker load docker compose up -dWindows (PowerShell):
$TAG = "latest" docker load -i "service-api-$TAG.tar" docker compose up -d
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/api/service-api
Upgrades use the same flow: pull the new tag on the connected machine, transfer and load the archive, then docker compose up -d.
|
This covers the Service API image only. The ACS database, Hazelcast, and the Northbound API are separate images on their own hosts — see All in one server deployment — Offline Servers for the procedure covering a whole stack. |
5. Preparation
Create the directory the Service API needs on the host and point it at the infrastructure listed in Required External Dependencies.
5.1. Directory Structure
mkdir -p /usr/local/ft-system/service-api/{config,logs}
cd /usr/local/ft-system
The application configuration files live in the config/ subdirectory, which the container mounts read-only at /etc/app.
compose.yml and both .env files stay on disk but are never mounted — they are loaded via env_file.
5.1.1. Directory Layout
The Service API slice of the platform deployment layout under /usr/local/ft-system:
/usr/local/ft-system/
├── compose.yml # from FT_DISK
├── .env # shared stack environment
└── service-api/
├── .env # per-service environment
├── config/ # -> /etc/app (read-only)
│ ├── service-api.yml
│ ├── hazelcast-client.yaml
│ └── keystore.p12 # only when HTTPS is enabled
└── logs/ # -> /app/logs (created automatically)
| Path | Content | Backup |
|---|---|---|
|
Docker Compose file for the stack |
Yes |
|
Shared stack environment — database connection, Hazelcast, inter-service URLs |
Yes |
|
Per-service environment — host ports, HikariCP pool, TLS, timeout, log level |
Yes |
|
Configuration mounted read-only at |
Yes |
|
Application logs written by the container at |
No |
Files inside service-api/config/:
| File | Purpose |
|---|---|
|
Main service configuration — email, speed tests, subscription parameters, service groups, wireless configuration. See Service API Configuration Guide. |
|
Hazelcast cluster client configuration |
|
PKCS12 TLS keystore — only when HTTPS is enabled, see TLS Keystore (optional) |
Download service-api.yml and hazelcast-client.yaml from the FT_DISK — Service-API folder, or copy the source-repository defaults:
| File | Path in repository |
|---|---|
|
|
|
|
|
|
5.2. Environment Configuration
The stack uses a two-layer environment file architecture — there is no per-vendor environment file.
| File | Purpose |
|---|---|
|
Shared by every service: database connection ( |
|
Service API only: host ports ( |
Edit both layers:
cd /usr/local/ft-system
vi .env # shared: database connection, Hazelcast, NORTHBOUND_API_URL
vi service-api/.env # per-service: ports, pool, TLS, timeout, log level
MySQL is the default.
Switching to Oracle is not a file swap: change DB_PROFILE, SPRING_PROFILES_ACTIVE, DB_HOST, DB_PORT and the other variables listed in Switching to Oracle inside the single root .env.
compose.yml maps the generic DB_* names from the root .env to the vendor-specific names each application expects (MYSQL_HOST, ORACLE_HOST, …) through the x-db-env YAML anchor, so the Service API — which reads MYSQL_* / ORACLE_* — works unchanged.
|
Host names for peers that run outside Docker are covered in Docker Networking.
Every variable the container reads, with the default that applies when the variable is not set.
The table below lists them by the name the application reads.
The MYSQL_* / ORACLE_* / DB_HOST names are supplied by the x-db-env mapping in compose.yml from the root .env — you set DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_SERVICE, DB_PROFILE and SPRING_PROFILES_ACTIVE there, not the vendor-specific names.
Everything else in the table (PORT, HTTPS_PORT, the SERVER_SSL_* group, the HikariCP group, TIMEOUT, LOGGING_LEVEL_COM_FRIENDLY_APISERVICE, CONFIG_PATH, CACHE_CONFIG_PATH) belongs in service-api/.env; TZ and NORTHBOUND_API_URL are shared and stay in the root .env.
Defaults come from src/main/resources/application.yml, application-mysql.yml, application-oracle.yml, logback-spring.xml and config/HttpsConfiguration.java in this repository; Required means the service cannot serve traffic correctly on a real deployment without it.
| Variable | Description | Default | Required |
|---|---|---|---|
SPRING_PROFILES_ACTIVE |
Database profile to activate: |
none (the shared |
Yes |
PORT |
HTTP connector port inside the container ( |
|
No |
SERVER_SSL_ENABLED |
Enables the HTTPS connector. |
|
No |
HTTPS_PORT |
HTTPS connector port inside the container. Must differ from |
|
No |
SERVER_SSL_KEY_STORE |
Keystore location, for example |
empty |
Yes when |
SERVER_SSL_KEY_STORE_PASSWORD |
Keystore password. |
empty |
Yes when |
SERVER_SSL_KEY_STORE_TYPE |
Keystore format. |
|
No |
SERVER_SSL_KEY_PASSWORD |
Private key password. |
empty |
Yes when |
SERVER_SSL_KEY_ALIAS |
Alias of the key entry used for TLS. |
|
No |
SERVER_SSL_TRUST_STORE |
Truststore location, when client certificates must be validated. |
empty |
No |
SERVER_SSL_TRUST_STORE_PASSWORD |
Truststore password. |
empty |
No |
TZ |
Container timezone; affects timestamps in logs and in the database. |
|
No |
DB_MAX_POOL_SIZE |
HikariCP maximum pool size. |
|
No |
DB_MIN_IDLE |
HikariCP minimum idle connections. |
|
No |
DB_CONNECTION_TIMEOUT_MS |
HikariCP connection timeout in milliseconds. |
|
No |
DB_HOST |
Fallback database host, used when |
|
No |
MYSQL_HOST |
MySQL host, used to build the JDBC URL on the |
|
Yes on the |
MYSQL_PORT |
MySQL port. |
|
No |
MYSQL_SCHEMA |
MySQL schema. Must already exist; it is created by the FTACS setup. |
|
No |
MYSQL_USER |
MySQL user. |
|
Yes on the |
MYSQL_PASSWORD |
MySQL password. |
|
Yes on the |
MYSQL_DRIVER_CLASS_NAME |
JDBC driver class for MySQL. |
|
No |
MYSQL_JDBC_URL |
Full JDBC URL. Overrides the URL assembled from |
assembled from the variables above |
No |
ORACLE_HOST |
Oracle host, used to build the JDBC URL on the |
|
Yes on the |
ORACLE_PORT |
Oracle listener port. |
|
No |
ORACLE_SERVICE |
Oracle service name. |
|
No |
ORACLE_USER |
Oracle user. |
|
Yes on the |
ORACLE_PASSWORD |
Oracle password. |
|
Yes on the |
ORACLE_DRIVER_CLASS_NAME |
JDBC driver class for Oracle. |
|
No |
ORACLE_JDBC_URL |
Full JDBC URL. Overrides the URL assembled from |
assembled from the variables above |
No |
NORTHBOUND_API_URL |
Base URL of the Northbound API, including the |
Yes |
|
CONFIG_PATH |
Directory the |
|
Yes |
CACHE_CONFIG_PATH |
Directory the |
|
Yes |
TIMEOUT |
Default timeout for device operations, in seconds. |
|
No |
LOGGING_LEVEL_COM_FRIENDLY_APISERVICE |
Log level for |
|
No |
The two environment files below set the same variables; the table is the reference, the files are the starting point to edit.
Click to expand .env (shared stack environment — Service API relevant subset)
# ==========================================================
# Shared stack environment - /usr/local/ft-system/.env
# Loaded by every service via env_file.
# ==========================================================
# Container timezone; affects timestamps in logs and in the database
TZ=Europe/Kyiv
# ==========================================================
# Main Database Configuration
# Generic names. compose.yml maps them to the vendor-specific
# names the Service API reads (MYSQL_HOST / ORACLE_HOST, ...)
# via the x-db-env anchor. MySQL is the default.
# ==========================================================
DB_PROFILE=mysql
SPRING_PROFILES_ACTIVE=mysql
DB_HOST=mysql # <-- replace when the DB is outside Docker
DB_PORT=3306
DB_USER=ftacs
DB_PASSWORD=<your-db-password> # <-- replace
DB_SERVICE=XEPDB1 # Oracle PDB service name; unused for MySQL
# ==========================================================
# Inter-Service URLs
# If both services are in the same Docker network, use the container name.
# If Northbound API runs on a separate host, use http://<host>:<port>/iot-webservice
# ==========================================================
NORTHBOUND_API_URL=http://northbound-api:8080/iot-webservice
Click to expand service-api/.env (per-service environment)
# ==========================================================
# Service API - per-service environment
# /usr/local/ft-system/service-api/.env
# ==========================================================
# Host ports published on the server. Map to the container ports below.
SERVICE_API_PORT=8085
SERVICE_API_HTTPS_PORT=9445
# Container connector ports
# PORT is the HTTP connector port inside the container.
PORT=8080
# HTTPS/TLS settings
# HTTP and HTTPS can run together when they use different container ports.
# SERVER_SSL_ENABLED=false -> HTTP only.
# SERVER_SSL_ENABLED=true -> HTTPS on HTTPS_PORT (set it explicitly, e.g. 8443).
# In dual mode, keep PORT different from HTTPS_PORT (example: PORT=8080, HTTPS_PORT=8443).
# HTTPS_PORT=8443
SERVER_SSL_ENABLED=false
SERVER_SSL_KEY_STORE=file:/etc/app/keystore.p12
SERVER_SSL_KEY_STORE_PASSWORD=<keystore-password> # <-- replace when HTTPS enabled
SERVER_SSL_KEY_STORE_TYPE=PKCS12
SERVER_SSL_KEY_PASSWORD=<key-password> # <-- replace when HTTPS enabled
SERVER_SSL_KEY_ALIAS=server
SERVER_SSL_TRUST_STORE=
SERVER_SSL_TRUST_STORE_PASSWORD=
# ==========================================================
# HikariCP Connection Pool
# ==========================================================
DB_MAX_POOL_SIZE=10
DB_MIN_IDLE=5
DB_CONNECTION_TIMEOUT_MS=30000
# ==========================================================
# Configuration Paths (inside container)
# ==========================================================
CONFIG_PATH=file:/etc/app/
CACHE_CONFIG_PATH=file:/etc/app/
# ==========================================================
# Logging
# ==========================================================
# Log level for com.friendly.apiservice. Values: ERROR, WARN, INFO, DEBUG.
# In DEBUG mode business errors include stack traces. Can also be changed
# at runtime via Actuator.
LOGGING_LEVEL_COM_FRIENDLY_APISERVICE=DEBUG
# ==========================================================
# Timeouts
# ==========================================================
# Default timeout for device operations (seconds)
TIMEOUT=60
hazelcast-client.yaml ships with development defaults (cluster-name: dev, cluster-members: [127.0.0.1:5701]); point it at the real cluster before starting:
hazelcast-client:
cluster-name: <your-cluster-name>
network:
cluster-members:
- <hazelcast-server-host>:<hazelcast-server-port>
For the full reference of service-api.yml and every environment variable, see Service API Configuration Guide.
5.3. TLS Keystore (optional)
The Service API serves HTTPS from a single PKCS12 keystore, keystore.p12, placed in config/ alongside the other configuration files, so it is mounted at /etc/app/keystore.p12.
For local development, generate a self-signed keystore:
cd /usr/local/ft-system/service-api/config
keytool -genkeypair \
-alias server \
-keyalg RSA \
-keysize 2048 \
-storetype PKCS12 \
-keystore keystore.p12 \
-validity 3650 \
-storepass <keystore-password> \
-keypass <key-password> \
-dname "CN=localhost, OU=Dev, O=Friendly, L=Local, ST=Local, C=US" \
-ext "SAN=dns:localhost,ip:127.0.0.1"
The keystore must end up in /usr/local/ft-system/service-api/config/keystore.p12 — that directory is mounted read-only at /etc/app, so the file appears as /etc/app/keystore.p12. Confirm it before continuing:
ls -l /usr/local/ft-system/service-api/config/keystore.p12
Then set these values in service-api/.env: SERVER_SSL_ENABLED=true, HTTPS_PORT=8443, SERVER_SSL_KEY_STORE=file:/etc/app/keystore.p12, SERVER_SSL_KEY_STORE_PASSWORD=<keystore-password>, SERVER_SSL_KEY_PASSWORD=<key-password>.
Finally uncomment the ${SERVICE_API_HTTPS_PORT:-9445}:8443 line in the ports: mapping of compose.yml and recreate the container — the HTTPS port is not published by default.
For production, use a certificate issued by your CA/security team and export it to PKCS12 format (.p12).
6. Deployment
6.1. Startup Dependencies
The Service API has no depends_on on its infrastructure — the ACS database, Hazelcast, and Northbound API live outside this stack, so Compose cannot gate on their health.
All of them should be reachable before the Service API serves traffic. The Hazelcast client connects during context initialisation; with the bundled async-start: true config it connects asynchronously and does not block startup, but operations that use the cache fail until the cluster is up (see Required External Dependencies).
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 Hazelcast
until nc -z <hazelcast-host-ip> 5701; do sleep 2; done
# Wait for Northbound API to be healthy
until curl -sf http://<nbi-host-ip>:8080/iot-webservice/actuator/health; do sleep 2; done
# Then start Service API
docker compose up -d
The Service API itself restarts cleanly at any time once the infrastructure is up — restart: unless-stopped reconnects it after a host reboot.
6.2. Docker Compose
Click to expand compose.yml (Service API)
version: "3.8"
services:
service-api:
image: hub.friendly-tech.com/api/service-api:latest
container_name: service-api
env_file:
- .env
- ./service-api/.env
ports:
- "${SERVICE_API_PORT:-8085}:8080" # HTTP
# - "${SERVICE_API_HTTPS_PORT:-9445}:8443" # Optional: publish when HTTPS is enabled
volumes:
- ./service-api/config:/etc/app:ro
- ./service-api/logs:/app/logs
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/iot-webservice/actuator/health"]
interval: 30s
timeout: 3s
start_period: 60s
retries: 3
networks:
- service-api
networks:
service-api:
driver: bridge
This follows the source repository’s docker/compose.yml, with three deliberate differences: the configuration mount is narrowed to the config/ layout used above (the repository file mounts the whole service directory), the env_file list is replaced by the two-layer .env + service-api/.env model the platform uses, and the published HTTP port is the platform-assigned 8085, not the 8080 in the repository file — on a /usr/local/ft-system host, 8080 is already published by FTACS.
To pin a specific release instead of latest, change the image: tag.
The database vendor is not selected here — it is selected by DB_PROFILE / SPRING_PROFILES_ACTIVE / DB_HOST in the root .env, see Switching to Oracle.
|
The HTTPS mapping in this file is |
cd /usr/local/ft-system
docker login hub.friendly-tech.com
docker compose up -d
docker compose ps
# Follow startup
docker compose logs -f service-api | grep -i "started"
curl -s http://localhost:8085/iot-webservice/actuator/health
|
Running the container without Compose
The same container can be started directly with
The port mappings are the platform-assigned ones: |
7. Verification
7.1. Startup Log
Wait 30—60 seconds after start, then check the logs:
docker compose logs service-api | tail -20
# or, without Compose:
docker logs service-api --tail 20
A successful startup ends with the StartupInfoLogger summary banner (timestamp, PID, version, build, and Java runtime vary; the active profile reflects your deployment):
INFO --- [service-api] [main] c.f.apiservice.config.StartupInfoLogger :
============================================================
Service API startup summary
------------------------------------------------------------
Active profiles : <active-profile>
Connectors : http (:8080)
Context path : /iot-webservice
Swagger UI : /iot-webservice/swagger-ui/index.html
SOAP Endpoint : /iot-webservice/FTACSWS/FTServiceAPI
SOAP WSDL : /iot-webservice/FTACSWS/FTServiceAPI?wsdl
API version : <version>
API build : <build>
Java runtime : <java-version>
PID : <pid>
============================================================
7.2. Endpoint Checks
# 1. Check container status
docker ps -f name=service-api
# Expected: container with status "Up" and mapped ports
# 2. Health check
curl -s http://localhost:8085/iot-webservice/actuator/health
# Expected: {"status":"UP"}
# 2b. HTTPS health check (when SERVER_SSL_ENABLED=true)
curl -sk https://localhost:9445/iot-webservice/actuator/health
# Expected: {"status":"UP"}
# 3. Swagger UI (open in browser)
# http://localhost:8085/iot-webservice/swagger-ui/index.html
7.3. Database Connectivity
The Actuator health endpoint runs with show-details: always, so the db component reports the JDBC connection state:
curl -s http://localhost:8085/iot-webservice/actuator/health | grep -o '"db":{[^}]*}'
# Expected: "db":{"status":"UP",...}
If the component is missing or DOWN, check reachability from inside the container and the credentials in the root .env:
docker exec service-api nc -zv <db-host> 3306 # Oracle: 1521
docker exec service-api env | grep -E 'MYSQL_|ORACLE_'
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 |
|---|---|---|---|
8085 → 8080 |
HTTP |
REST and SOAP API under |
Public (published by |
9445 → 8443 |
HTTPS |
Same endpoints over TLS when |
Not published by default — the mapping is commented out in |
By default the application runs on HTTP only (SERVER_SSL_ENABLED=false, PORT=8080). When HTTPS is enabled it can run both connectors simultaneously as long as PORT and HTTPS_PORT differ.
|
8.2. Outbound Connections
Ports the Service API dials on the existing infrastructure — confirm them against whoever operates that infrastructure, and set the matching variables in the root .env.
| Destination | Port | Protocol | Purpose |
|---|---|---|---|
ACS database |
3306 (Oracle: 1521) |
TCP |
|
Hazelcast cluster |
5701 |
TCP |
|
Northbound API |
8080 |
HTTP |
|
8.3. HTTP Endpoints
| Method | Path | Purpose | Auth |
|---|---|---|---|
GET |
|
Health check, also used by the container healthcheck |
None |
GET |
|
Prometheus metrics scrape endpoint |
None |
GET |
|
Swagger UI documentation interface |
None |
GET |
|
SOAP API definition, listed in the Swagger UI configuration |
None |
POST |
|
SOAP endpoint; the WSDL is served at the same path with |
None |
The application ships without Spring Security — no dependency on the classpath and no filter chain in the source — and management.endpoints.web.exposure.include is *. Restrict access to the published port at the network level.
|
9. Stack Management
9.1. Logs
docker compose logs -f service-api # follow application logs
docker logs --tail 100 service-api # last 100 lines without Compose
The application writes rolling logs to logs/application.log on the host (daily rotation, gzip-compressed, 30-day retention — see logback-spring.xml).
9.2. Start, Stop, Restart
| Command | Description |
|---|---|
|
Start the service in the background |
|
Stop and remove the container |
|
Restart the application |
|
Show container status |
9.3. Shell Access
| Command | Description |
|---|---|
|
Enter the application container |
|
Check mounted configuration files |
|
Check environment variables |
9.4. Updating Service API
# 1. Backup configuration
cd /usr/local/ft-system
tar -czf service-api-backup-$(date +%Y%m%d).tar.gz .env service-api/.env service-api/config/
# 2. Pull the latest image and recreate the container
docker compose pull service-api
docker compose up -d service-api
# 3. Verify
curl -s http://localhost:8085/iot-webservice/actuator/health
For servers without Harbor access, use the manual image transfer flow in Offline Servers instead of docker compose pull.
10. Production Checklist
-
Restart policy:
restart: unless-stoppedis set incompose.yml. -
Health check:
/iot-webservice/actuator/healthconfigured with a 30s interval. -
Database credentials replaced in the root
.env(DB_USER/DB_PASSWORD). -
NORTHBOUND_API_URLpoints to the correct Northbound API instance and includes the/iot-webservicepath. -
cluster-nameandcluster-membersinhazelcast-client.yamlmatch the real cluster. -
TLS: the self-signed
keystore.p12replaced with a CA-issued certificate if HTTPS is enabled. -
File permissions restricted on env files:
chmod 600 .env service-api/.env. -
Docker log rotation configured:
--log-opt max-size=50m --log-opt max-file=5. -
TZset in the root.env(the sample usesEurope/Kyiv). -
Resource limits set (
--memory=2gordeploy.resources.limitsincompose.yml). -
Access to the published port restricted at the network level — the application has no built-in authentication.
11. Troubleshooting
Start with quick diagnostics:
docker logs service-api --tail 100
docker inspect service-api --format '{{json .Config.Env}}'
docker exec service-api ls -la /etc/app
docker exec service-api ls -la /app/logs
11.1. Could not resolve placeholder 'CONFIG_PATH'
Symptom: PlaceholderResolutionException: Could not resolve placeholder 'CONFIG_PATH'
Fix:
-
Verify
env_fileincompose.ymllists both.envand./service-api/.env. -
Confirm
service-api/.envcontainsCONFIG_PATH=file:/etc/app/. -
Validate inside the container:
docker exec service-api env | grep CONFIG_PATH.
11.2. Hazelcast Unreachable — Cache Operations Fail
Symptom: The container starts, but the log shows repeated Hazelcast client reconnect attempts or Unable to connect to any address in the config, and cache-backed operations fail.
Cause: The Hazelcast client cannot reach the cluster. The bundled hazelcast-client.yaml uses async-start: true / reconnect-mode: ASYNC, so the container does not exit — it starts and keeps retrying the connection in the background — but any operation that relies on the cache fails until a cluster is reachable.
Fix:
-
Verify Hazelcast is running and reachable:
docker exec service-api nc -zv <hazelcast-host> 5701. -
Check that
cluster-name(defaultdev) andcluster-membersinhazelcast-client.yamlmatch the server configuration. -
Confirm
CACHE_CONFIG_PATHinservice-api/.envpoints to the mounted directory containinghazelcast-client.yaml.
11.3. FileNotFoundException: /etc/app/service-api.yml
Symptom: Application logs show the configuration file is missing.
Fix:
-
Confirm
service-api.ymlexists in theconfig/directory:ls -la /usr/local/ft-system/service-api/config/service-api.yml. -
Verify the volume mount, from
/usr/local/ft-system:-v $(pwd)/service-api/config:/etc/app:ro(or./service-api/config:/etc/app:roincompose.yml). -
Check inside the container:
docker exec service-api ls /etc/app.
11.4. Logs Are Not Written to the Host Directory
Symptom: application.log exists in the container, but logs/ on the host is empty or missing.
Fix:
-
Add a bind mount for logs, from
/usr/local/ft-system:-v $(pwd)/service-api/logs:/app/logs(already present incompose.ymlas./service-api/logs:/app/logs). -
Create the host directory before starting:
mkdir -p /usr/local/ft-system/service-api/logs. -
Verify mount configuration:
docker inspect service-api --format '{{json .Mounts}}'.
11.5. Database Connection Failure
Symptom: MySQL Communications link failure or Oracle ORA-12514 in logs.
Fix:
-
Verify the database host is reachable from the container:
docker exec service-api nc -zv <db-host> <db-port> -
Check
DB_HOST,DB_PORT,DB_USERandDB_PASSWORDin the root.env—compose.ymlmaps them toMYSQL_HOST/ORACLE_HOSTand the matching credentials. -
Confirm the
ftacsschema exists — the Service API does not create it.
11.6. Northbound API Connection Failure
Symptom: Connection refused or timeout errors when calling Northbound API.
Fix:
-
Verify
NORTHBOUND_API_URLin the root.envis correct and includes the/iot-webservicepath. -
Check that the Northbound API is running:
curl -s http://<nbi-host>:8080/iot-webservice/actuator/health. -
If using Docker networking, ensure both containers are on the same network and use the container name instead of
localhost.
11.7. Port Already in Use
Symptom: address already in use error.
Fix:
-
Find the process using the port:
lsof -i :8085 # or docker ps -
Stop the conflicting service, or publish a different host port:
-p 9080:8080.
11.8. Build Fails: Permission denied: ./gradlew
Symptom: Building the image from source (docker buildx build) fails on ./gradlew.
Fix:
-
On the host:
chmod +x gradlew. -
Ensure your source checkout preserves execute permissions.
11.9. Build Fails: Secret Errors
Symptom: Could not read script '/run/secrets/github_user' during a from-source build.
Fix:
-
Dockerfilerequires GitHub Packages credentials passed as BuildKit secrets:--secret id=github_user,… --secret id=github_token,…. -
Verify the credentials are valid and have access to the private dependencies the build pulls.
11.10. Getting Support
When raising an issue with Friendly Technologies support, attach:
-
docker logs service-api --tail 500and the relevant part oflogs/application.log. -
docker inspect service-api --format '{{json .Config.Env}}'with passwords removed. -
The image tag in use:
docker images hub.friendly-tech.com/api/service-api. -
Which database vendor and profile is active (
SPRING_PROFILES_ACTIVE).
12. Related Documentation
-
Service API Configuration Guide — every configuration item and its default.
-
Architecture Overview — Component Architecture — how the Service API sits between the Northbound API, the ACS database, and Hazelcast.
-
Database — Configuration — datasource profiles and connection pool settings.
-
All in one server deployment — deploying the prerequisite infrastructure on a single host.
-
Separate server deployment — Server E: API Stack — split topology.
-
Java API Stack Deployment Guide — Service API together with Northbound API and Provision Portal.