Installation & Deployment
Overview
This guide walks you through installing and running the Northbound API — a Spring Boot 3 application built with Java 25 and Gradle. Docker is the recommended deployment method for consistency and ease of use.
The deployment includes:
-
Northbound API - Spring Boot REST/SOAP service exposing device management endpoints
-
Docker container - Pre-built image from Harbor registry or loaded from archive
Prerequisites
System Requirements
-
Docker Engine 20.10+
-
Docker Compose 2.0+
-
Minimum 2 GB RAM (4+ GB recommended)
-
1 GB free disk space for the image and logs
|
JDK 25 is only required for local Gradle builds. Docker deployment does not need a JDK on the host machine. |
Required External Components
The following components must be installed and accessible:
| Component | Purpose | Default Port |
|---|---|---|
MySQL or Oracle |
ACS database (schema must already exist) |
3306 (MySQL) / 1521 (Oracle) |
Hazelcast |
Distributed cache (if caching is used) |
5701 |
Supported Operating Systems
-
Linux (recommended)
-
macOS
-
Windows with WSL2
Before starting, make sure:
-
Docker Engine 20.10+ and Docker Compose 2.0+ installed. If Docker is not installed, follow the Docker Installation Guide.
-
You have network access to the ACS database (MySQL or Oracle).
-
You have network access to the Hazelcast cluster (if caching is used).
Quick Start
For experienced users who already have Docker installed and configuration files ready.
The example below uses MySQL; for Oracle replace .env.mysql with .env.oracle.
# 1. Go to the working directory (files should already be in place -- see "Preparation")
cd /usr/local/nbi-api
# 2. Get the Docker image (choose one):
# Option A: Pull from Harbor registry (recommended)
docker login hub.friendly-tech.com
# Note: "docker pull" is optional if you use docker compose --
# "docker compose up" will pull the image automatically.
docker pull hub.friendly-tech.com/api/northbound-api:latest
# Option B: Load from archive (for offline servers)
# gzip -dc northbound-api.tar.gz | docker load
# 3. Edit the environment file for your database
vi .env.mysql # or .env.oracle for Oracle
# set MYSQL_HOST, MYSQL_PASSWORD, ACS_URL, etc.
# 4. Make sure compose.yml references the correct env file
# env_file: .env.mysql (default) or .env.oracle
# 5. Start the application
docker compose up -d
# 6. Verify
curl -s http://localhost:8080/iot-webservice/actuator/health
|
To switch between MySQL and Oracle, change the
|
After startup the application is available at http://localhost:8080.
Preparation
1. Prepare Working Directory
Create the working directory on the host machine:
mkdir -p /usr/local/nbi-api/logs
cd /usr/local/nbi-api
Download the deployment files from the FT_DISK on SharePoint and place them on the server. Alternatively, copy the files from the source repository.
Expected Layout
/usr/local/nbi-api/
+-- .env.mysql # Environment variables (MySQL profile)
+-- .env.oracle # Environment variables (Oracle profile)
+-- api.properties # Main API configuration
+-- hazelcast-client.yaml # Hazelcast cache cluster config
+-- logs/ # Application logs (mounted volume)
|
You only need the env file that matches your database ( Configuration files are also available in the source repository:
|
2. Get the Docker Image
Option A: Pull from Harbor Registry (recommended)
# Authenticate with the registry
docker login hub.friendly-tech.com
# Pull the latest image (optional if using docker compose --
# "docker compose up" will pull the image automatically)
docker pull hub.friendly-tech.com/api/northbound-api:latest
To pin to a specific release version:
docker pull hub.friendly-tech.com/api/northbound-api:v1.0.0-b0.0.13
|
The version |
Optionally, retag the image for shorter references in docker run commands:
docker tag hub.friendly-tech.com/api/northbound-api:latest northbound-api:latest
If you skip retagging, use the full image name (hub.friendly-tech.com/api/northbound-api:latest) in all subsequent commands.
To request Harbor access, contact the DevOps team for a user account or robot token.
Option B: Transfer Image to Offline Server
Use this option when the target server has no internet access and cannot pull images from Harbor directly. The image is pulled on a machine that does have Harbor access, exported to a tar archive, transferred to the offline server, and loaded there.
-
On a machine with Harbor access, log in and pull the image for the target server’s architecture:
docker login hub.friendly-tech.com docker pull --platform linux/amd64 \ hub.friendly-tech.com/api/northbound-api:<version>An explicit
--platformmatching the offline target server’s architecture is required. The image in Harbor is multi-arch (linux/amd64,linux/arm64); without--platform,docker pullselects the host architecture, which may not match the target. For example, on an Apple Silicon (arm64) Mac without--platform, the resulting archive will be arm64 and will fail with aplatform does not matchwarning on amd64 servers. The example useslinux/amd64; replace it with the platform of your offline target server (linux/arm64, etc.). -
Save the pulled image to a tar archive and compress it:
docker save hub.friendly-tech.com/api/northbound-api:<version> \ -o northbound-api-<version>.tar gzip northbound-api-<version>.tar -
Transfer
northbound-api-<version>.tar.gzto the offline server (e.g., viascpor removable media). -
On the offline server, load the image:
gzip -dc northbound-api-<version>.tar.gz | docker load
Option C: Build from Source (developers only)
docker buildx build \
--platform linux/amd64,linux/arm64 \
--secret id=github_token,env=GITHUB_TOKEN \
--secret id=github_user,src=<(printf '%s' "Friendly-Technologies") \
-t northbound-api:latest \
--load .
|
Building from source requires valid GitHub credentials with access to private dependencies. Loading a pre-built image is faster and avoids build-environment issues. |
After loading or pulling, verify the image is available:
docker images | grep northbound-api
3. Configure Environment
The Northbound API reads its configuration from:
-
.env.mysqlor.env.oracle— environment variables loaded via Docker--env-file -
api.properties— application-level properties (mounted to/etc/app/api.properties) -
hazelcast-client.yaml— cache configuration (mounted to/etc/app/hazelcast-client.yaml)
Choose the appropriate environment file:
- MySQL
-
cd /usr/local/nbi-api vi .env.mysql - Oracle
-
cd /usr/local/nbi-api vi .env.oracle
For comprehensive details on every configuration option, see the Configuration Guide.
|
If the ACS, database, or Hazelcast cluster runs on the host machine (not in Docker), use one of the following as the hostname in
|
|
Pre-configured template files are available on FT_DISK and in the source repository. Copy the file that matches your database to the working directory:
|
TLS Keystore (HTTPS)
To enable HTTPS, provide a PKCS12 keystore file at /etc/app/keystore.p12 inside the container.
With the recommended mount (-v $(pwd):/etc/app), place the file in your working directory as keystore.p12.
For local development, you can generate a self-signed keystore:
cd /usr/local/nbi-api
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"
Then set these values in .env.mysql or .env.oracle:
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>.
These settings enable HTTPS for the Northbound API container itself. ACS connection settings are configured separately via ACS_URL and ACS_PORT:
use http://…; + 8080 for HTTP ACS, or https://…; + 443/8443 for HTTPS ACS.
|
For production, use a certificate issued by your CA/security team and export it to PKCS12 format (.p12).
Deployment
Option 1: Docker Compose (recommended)
Create a compose.yml file in your working directory:
services:
northbound-api:
image: hub.friendly-tech.com/api/northbound-api:latest
container_name: northbound-api
env_file:
- .env.mysql # or .env.oracle
ports:
- "8080:8080" # <host-port>:<container-port> -- change the host port
# if 8080 is already used by another application (e.g. "9080:8080")
- "8443:8443" # Optional: publish this mapping only when HTTPS is enabled
volumes:
- .:/etc/app
- ./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
To change external ports, edit only the left side of each mapping (<host-port>:<container-port>),
for example 9080:8080 for HTTP and 9443:8443 for HTTPS.
Publishing both mappings at the same time is valid and does not conflict.
Conflicts happen only if host ports overlap (left side) or if HTTP/HTTPS are configured to the same container port.
For HTTP-only deployment, remove the 8443:8443 mapping.
# Start the application
docker compose up -d
# View logs
docker compose logs -f northbound-api
# Stop
docker compose down
Option 2: docker run
cd /usr/local/nbi-api
docker run -d \
--name northbound-api \
--env-file .env.mysql \
-v $(pwd):/etc/app \
-v $(pwd)/logs:/app/logs \
-p 8080:8080 \
-p 8443:8443 \
--restart unless-stopped \
hub.friendly-tech.com/api/northbound-api:latest
If default host ports are busy, change only the left side:
-p 9080:8080 -p 9443:8443.
For HTTP-only deployment, omit -p 8443:8443.
Replace --env-file .env.mysql with --env-file .env.oracle for Oracle.
If you retagged the image locally, use northbound-api:latest instead of the full Harbor path.
Docker Networking
If the Northbound API container must communicate with other containers (database, Hazelcast, etc.), create a shared Docker network:
docker network create northbound-net
Add --network northbound-net to your docker run command or add the network section to your compose.yml.
Use container names (not raw IPs) in .env.mysql / .env.oracle to leverage Docker DNS resolution.
|
To deploy Northbound API alongside Service API and Provision Portal as a single stack, see the Java API Stack Deployment Guide. |
Verify Deployment
After starting the container, run the following checks:
# 1. Check container status
docker ps -f name=northbound-api
# Expected: container with status "Up" and port 0.0.0.0:8080->8080/tcp
# 2. Health check
curl -s http://localhost:8080/iot-webservice/actuator/health
# Expected: {"status":"UP"}
# 2b. HTTPS health check (when SERVER_SSL_ENABLED=true)
curl -ks https://localhost:8443/iot-webservice/actuator/health
# Expected: {"status":"UP"}
# 3. Check application logs
docker logs northbound-api --tail 50
# Look for: "Started NorthboundApiApplication in XX.XXX seconds"
# 4. Swagger UI (open in browser)
# http://localhost:8080/iot-webservice/swagger-ui/index.html
# https://localhost:8443/iot-webservice/swagger-ui/index.html
# 5. Test SOAP endpoint
curl -s http://localhost:8080/iot-webservice/FTACSWS/ACSWS?wsdl | head -5
# Expected: beginning of the WSDL XML document
Environment Variables
Common Configuration
- MySQL
-
# ========================================================== # Northbound API - Environment Configuration (MySQL) # ========================================================== # Spring profile: mysql or oracle SPRING_PROFILES_ACTIVE=mysql # 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). # If these container ports are equal, only one connector can bind to that port. # 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= # Container timezone (default: UTC) TZ=UTC # ========================================================== # Database # ========================================================== DB_MAX_POOL_SIZE=10 DB_MIN_IDLE=5 DB_CONNECTION_TIMEOUT_MS=30000 # MySQL connection MYSQL_HOST=<your-mysql-host> # <-- replace MYSQL_PORT=3306 MYSQL_SCHEMA=ftacs MYSQL_USER=ftacs MYSQL_PASSWORD=<your-db-password> # <-- replace MYSQL_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver # ========================================================== # ACS Service # ========================================================== # Base URL of FTACS ACS service (scheme + host, without port/path). # Examples: # - same Docker network service: http://ftacs # - local host: http://127.0.0.1 # - Docker host gateway: http://host.docker.internal # - remote host: http://acs.example.internal # If ACS is exposed via HTTPS, use `https://` (for example: https://acs.example.internal). # Note: host.docker.internal works on Docker Desktop. # On Linux, add this to the target service in compose.yml: # extra_hosts: # - "host.docker.internal:host-gateway" ACS_URL=http://<your-acs-host> # <-- replace ACS_PORT=8080 # 8080 (HTTP) or 443/8443 (HTTPS) ACS_USERNAME=<acs-user> # <-- replace ACS_PASSWORD=<acs-password> # <-- replace # ========================================================== # SQL Login Queries # ========================================================== # Choose ONE set depending on which portal stack is deployed. # # Old C# stack (CSR, CPEAdmin): # admin.login table — columns: name, password, location_id # # New Java/Spring stack (Support Portal, Management Portal): # iotw.iotw_user table — columns: username, password, domain_id # -- Old C# stack (CSR / CPEAdmin) — admin.login table -- SQL_LOGIN_BY_NAME_AND_LOCATION0="SELECT password, location_id FROM admin.login WHERE name = :name AND location_id = 0" SQL_LOGIN_BY_NAME_AND_LOCATIONS="SELECT password, location_id FROM admin.login WHERE name = :name AND location_id IN (:ids)" SQL_LOGIN_BY_NAME_NULL_OR_0="SELECT password, location_id FROM admin.login WHERE name = :name AND (location_id IS NULL OR location_id = 0)" # -- New Java/Spring stack (Support / Management portals) — iotw.iotw_user table -- # SQL_LOGIN_BY_NAME_AND_LOCATION0="SELECT password, domain_id FROM iotw.iotw_user WHERE username = :name AND domain_id = 0" # SQL_LOGIN_BY_NAME_AND_LOCATIONS="SELECT password, domain_id FROM iotw.iotw_user WHERE username = :name AND domain_id IN (:ids)" # SQL_LOGIN_BY_NAME_NULL_OR_0="SELECT password, domain_id FROM iotw.iotw_user WHERE username = :name AND (domain_id IS NULL OR domain_id = 0)" # ========================================================== # API / Cache # ========================================================== API_CONFIG_PATH=file:/etc/app/api.properties CACHE_CONFIG_PATH=file:/etc/app/ # ========================================================== # Authentication (JWT) # ========================================================== # Secret key used to sign and verify JWT tokens (HMAC-SHA256). # Requirements: # - Minimum 32 characters (256 bits) # - Use a random alphanumeric string # - Must be unique per environment (dev / staging / prod) # The default value "friendly" is for development only — never use it in production. # Generate a secure value: openssl rand -base64 48 JWT_TOKEN_SECRET=friendly-tech-jwt-secret-key-2025 # Token expiration time in milliseconds (36000000 = 10 hours) JWT_TOKEN_EXPIRED=36000000 # ========================================================== # Logging # ========================================================== # Log level for the application package (com.friendly.northboundapi). # Values: ERROR, WARN, INFO, DEBUG. In DEBUG mode business errors include stack traces. # Can also be changed at runtime via Actuator — see Configuration Guide. LOGGING_LEVEL_COM_FRIENDLY_NORTHBOUNDAPI=DEBUG - Oracle
-
# ========================================================== # Northbound API - Environment Configuration (Oracle) # ========================================================== # Spring profile: mysql or oracle SPRING_PROFILES_ACTIVE=oracle # 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). # If these container ports are equal, only one connector can bind to that port. # 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= # Container timezone (default: UTC) TZ=UTC # ========================================================== # Database # ========================================================== DB_MAX_POOL_SIZE=10 DB_MIN_IDLE=5 DB_CONNECTION_TIMEOUT_MS=30000 # Oracle connection ORACLE_HOST=<your-oracle-host> # <-- replace ORACLE_PORT=1521 ORACLE_SERVICE=XEPDB1 ORACLE_USER=ftacs ORACLE_PASSWORD=<your-db-password> # <-- replace ORACLE_DRIVER_CLASS_NAME=oracle.jdbc.OracleDriver # ========================================================== # ACS Service # ========================================================== # Base URL of FTACS ACS service (scheme + host, without port/path). # Examples: # - same Docker network service: http://ftacs # - local host: http://127.0.0.1 # - Docker host gateway: http://host.docker.internal # - remote host: http://acs.example.internal # If ACS is exposed via HTTPS, use `https://` (for example: https://acs.example.internal). # Note: host.docker.internal works on Docker Desktop. # On Linux, add this to the target service in compose.yml: # extra_hosts: # - "host.docker.internal:host-gateway" ACS_URL=http://<your-acs-host> # <-- replace ACS_PORT=8080 # 8080 (HTTP) or 443/8443 (HTTPS) ACS_USERNAME=<acs-user> # <-- replace ACS_PASSWORD=<acs-password> # <-- replace # ========================================================== # SQL Login Queries # ========================================================== # Choose ONE set depending on which portal stack is deployed. # # Old C# stack (CSR, CPEAdmin): # admin.login table — columns: name, password, location_id # # New Java/Spring stack (Support Portal, Management Portal): # iotw.iotw_user table — columns: username, password, domain_id # -- Old C# stack (CSR / CPEAdmin) — admin.login table -- SQL_LOGIN_BY_NAME_AND_LOCATION0="SELECT password, location_id FROM admin.login WHERE name = :name AND location_id = 0" SQL_LOGIN_BY_NAME_AND_LOCATIONS="SELECT password, location_id FROM admin.login WHERE name = :name AND location_id IN (:ids)" SQL_LOGIN_BY_NAME_NULL_OR_0="SELECT password, location_id FROM admin.login WHERE name = :name AND (location_id IS NULL OR location_id = 0)" # -- New Java/Spring stack (Support / Management portals) — iotw.iotw_user table -- # SQL_LOGIN_BY_NAME_AND_LOCATION0="SELECT password, domain_id FROM iotw.iotw_user WHERE username = :name AND domain_id = 0" # SQL_LOGIN_BY_NAME_AND_LOCATIONS="SELECT password, domain_id FROM iotw.iotw_user WHERE username = :name AND domain_id IN (:ids)" # SQL_LOGIN_BY_NAME_NULL_OR_0="SELECT password, domain_id FROM iotw.iotw_user WHERE username = :name AND (domain_id IS NULL OR domain_id = 0)" # ========================================================== # API / Cache # ========================================================== API_CONFIG_PATH=file:/etc/app/api.properties CACHE_CONFIG_PATH=file:/etc/app/ # ========================================================== # Authentication (JWT) # ========================================================== # Secret key used to sign and verify JWT tokens (HMAC-SHA256). # Requirements: # - Minimum 32 characters (256 bits) # - Use a random alphanumeric string # - Must be unique per environment (dev / staging / prod) # The default value "friendly" is for development only — never use it in production. # Generate a secure value: openssl rand -base64 48 JWT_TOKEN_SECRET=friendly-tech-jwt-secret-key-2025 # Token expiration time in milliseconds (36000000 = 10 hours) JWT_TOKEN_EXPIRED=36000000 # ========================================================== # Logging # ========================================================== # Log level for the application package (com.friendly.northboundapi). # Values: ERROR, WARN, INFO, DEBUG. In DEBUG mode business errors include stack traces. # Can also be changed at runtime via Actuator — see Configuration Guide. LOGGING_LEVEL_COM_FRIENDLY_NORTHBOUNDAPI=DEBUG
Application Settings
| Variable | Description | Default | Required |
|---|---|---|---|
|
Spring profile ( |
- |
Yes |
|
Application HTTP port inside the container |
|
Yes |
|
Container timezone |
|
No |
|
Path to |
|
Yes |
|
Path to cache configuration directory inside the container |
|
Yes |
Database Configuration
- MySQL
-
Variable Description Default Required MYSQL_HOSTMySQL server address
<your-mysql-host>Yes
MYSQL_PORTMySQL port
3306Yes
MYSQL_SCHEMAMySQL schema name
ftacsYes
MYSQL_USERMySQL username
ftacsYes
MYSQL_PASSWORDMySQL password
-
Yes
MYSQL_DRIVER_CLASS_NAMEJDBC driver class name
com.mysql.cj.jdbc.DriverYes
DB_MAX_POOL_SIZEMaximum database connection pool size
10No
DB_MIN_IDLEMinimum idle connections in pool
5No
DB_CONNECTION_TIMEOUT_MSConnection timeout in milliseconds
30000No
- Oracle
-
Variable Description Default Required ORACLE_HOSTOracle server address
<your-oracle-host>Yes
ORACLE_PORTOracle port
1521Yes
ORACLE_SERVICEOracle service name
XEPDB1Yes
ORACLE_USEROracle username
ftacsYes
ORACLE_PASSWORDOracle password
-
Yes
ORACLE_DRIVER_CLASS_NAMEJDBC driver class name
oracle.jdbc.OracleDriverYes
DB_MAX_POOL_SIZEMaximum database connection pool size
10No
DB_MIN_IDLEMinimum idle connections in pool
5No
DB_CONNECTION_TIMEOUT_MSConnection timeout in milliseconds
30000No
ACS Service Configuration
| Variable | Description | Default | Required |
|---|---|---|---|
|
ACS service base URL ( |
Yes |
|
|
ACS service port. Typical values: |
|
Yes |
|
ACS authentication username |
- |
Yes |
|
ACS authentication password |
- |
Yes |
Authentication (JWT)
| Variable | Description | Default | Required |
|---|---|---|---|
|
Secret key for signing JWT tokens (HMAC-SHA256). Minimum 32 characters. Generate with: |
|
Yes |
|
Token expiration time in milliseconds |
|
No |
|
The default |
SQL Login Queries
Queries authenticate portal users by username and domain/location scope. Choose one set depending on which portal stack is deployed:
-
Old C# stack (CSR, CPEAdmin) — uses
admin.logintable with columnsname,password,location_id. -
New Java/Spring stack (Support Portal, Management Portal) — uses
iotw.iotw_usertable with columnsusername,password,domain_id.
| Variable | Description | Default | Required |
|---|---|---|---|
|
Authenticate user with global access (location/domain = 0) |
See |
Yes |
|
Authenticate a user when the location / domain matches one of the allowed IDs |
See |
Yes |
|
Authenticate user when location/domain is unset (NULL) or 0 — treats both as global access |
See |
Yes |
Container Management
| Command | Description |
|---|---|
|
Start all services in the background |
|
Stop and remove containers |
|
Follow application logs |
|
Show container status |
|
Restart the application |
|
Update images to latest versions |
|
Enter the application container |
|
Check mounted configuration files |
|
View last 100 log lines |
|
View resource usage |
|
Check environment variables |
Updating
Via Docker Compose (recommended)
docker compose pull downloads the latest image from the registry, then docker compose up -d recreates the container with the new version. Configuration files and logs are preserved.
# 1. Backup configuration
cd /usr/local/nbi-api
tar -czf nbi-backup-$(date +%Y%m%d).tar.gz .env.* api.properties hazelcast-client.yaml
# 2. Pull the latest image
docker compose pull northbound-api
# 3. Recreate the container with the new image
docker compose up -d northbound-api
# 4. Verify
curl -s http://localhost:8080/iot-webservice/actuator/health
Via docker load (offline servers)
When the upgrade target server has no access to the Harbor registry, the new image must be pulled on a machine that does have Harbor access, exported to a tar archive, transferred to the offline server, and loaded there.
-
On a machine with Harbor access, log in and pull the new image for the target server’s architecture:
docker login hub.friendly-tech.com docker pull --platform linux/amd64 \ hub.friendly-tech.com/api/northbound-api:<new-version>An explicit
--platformmatching the offline target server’s architecture is required. The image in Harbor is multi-arch (linux/amd64,linux/arm64); without--platform,docker pullselects the host architecture, which may not match the target. For example, on an Apple Silicon (arm64) Mac without--platform, the resulting archive will be arm64 and will fail with aplatform does not matchwarning on amd64 servers. The example useslinux/amd64; replace it with the platform of your offline target server (linux/arm64, etc.). -
Save the pulled image to a tar archive and compress it:
docker save hub.friendly-tech.com/api/northbound-api:<new-version> \ -o northbound-api-<new-version>.tar gzip northbound-api-<new-version>.tar -
Transfer
northbound-api-<new-version>.tar.gzto the offline server (e.g., viascpor removable media). -
On the offline server, load the new image and replace the running container:
# 1. Load the new image from archive gzip -dc northbound-api-<new-version>.tar.gz | docker load # 2. Replace the container docker stop northbound-api docker rm northbound-api docker run -d \ --name northbound-api \ --env-file .env.mysql \ -v $(pwd):/etc/app \ -v $(pwd)/logs:/app/logs \ -p 8080:8080 \ --restart unless-stopped \ hub.friendly-tech.com/api/northbound-api:<new-version> # 3. Verify curl -s http://localhost:8080/iot-webservice/actuator/health
Rollback
docker stop northbound-api
docker rm northbound-api
# Re-run with the previous image tag
docker run -d --name northbound-api \
--env-file .env.mysql \
-v $(pwd):/etc/app \
-v $(pwd)/logs:/app/logs \
-p 8080:8080 \
--restart unless-stopped \
hub.friendly-tech.com/api/northbound-api:<previous-version-tag>
Production Checklist
| # | Item | Notes |
|---|---|---|
1 |
Restart policy |
|
2 |
Health check |
Configured for |
3 |
Database credentials |
Replace default values in the |
4 |
ACS credentials |
Replace |
5 |
JWT secret |
Replace default |
6 |
File permissions |
Restrict env files: |
7 |
Log management |
Mount |
8 |
Timezone |
Set |
9 |
Resource limits |
Add |
Troubleshooting
Start with quick diagnostics:
docker logs northbound-api --tail 100
docker inspect northbound-api --format '{{json .Config.Env}}'
docker exec northbound-api ls -la /etc/app
Common Issues
Could not resolve placeholder 'API_CONFIG_PATH'
Symptom: PlaceholderResolutionException: Could not resolve placeholder 'API_CONFIG_PATH'
Check:
docker exec northbound-api env | grep API_CONFIG
Solution:
-
Verify
--env-filepoints to the correct.env.mysqlor.env.oraclefile. Use an absolute path if needed:--env-file /usr/local/nbi-api/.env.mysql. -
Confirm the env file contains
API_CONFIG_PATH=file:/etc/app/api.properties.
open .env.mysql: no such file or directory
Symptom: Docker fails to start with a "file not found" error.
Check:
ls -la /usr/local/nbi-api/.env.*
Solution:
-
Run
docker runfrom the directory containing the env file, or use an absolute path:--env-file /usr/local/nbi-api/.env.mysql. -
Check file permissions:
chmod 640 .env.*.
FileNotFoundException: /etc/app/api.properties
Symptom: Application logs show the properties file is missing.
Check:
docker exec northbound-api ls /etc/app
ls -la /usr/local/nbi-api/api.properties
Solution:
-
Confirm
api.propertiesexists in/usr/local/nbi-api/. -
Verify the volume mount:
-v $(pwd):/etc/app.
Database Connection Failures
Symptom: MySQL Communications link failure or Oracle ORA-12514 in logs.
Check:
docker exec northbound-api nc -zv <db-host> <db-port>
Solution:
-
Verify the database host is reachable from the container.
-
Check
MYSQL_HOST/ORACLE_HOSTand credentials in your.env.*file. -
If using Docker networking, ensure both containers are on the same network:
docker network inspect northbound-net
Hazelcast Connection Failure
Symptom: Unable to connect to any address in logs.
Check:
docker exec northbound-api env | grep CACHE_CONFIG
docker exec northbound-api cat /etc/app/hazelcast-client.yaml
Solution:
-
Verify Hazelcast members are running and on the same Docker network.
-
Check that
cluster-nameandcluster-membersinhazelcast-client.yamlmatch the server configuration. -
Confirm
CACHE_CONFIG_PATHin the env file points to the mounted directory.
Port Conflict
Symptom: address already in use error.
Check:
lsof -i :8080
# or
docker ps
Solution:
-
Stop the conflicting service, or expose a different host port:
-p 9080:8080.
Build Fails: Permission denied: ./gradlew
Symptom: Docker build stage fails on ./gradlew.
Check:
ls -la gradlew
Solution:
-
On the host:
chmod +x gradlew. -
Ensure your source checkout preserves execute permissions.
Build Fails: Secret Errors
Symptom: Could not read script '/run/secrets/github_user' during build.
Check:
echo $GITHUB_TOKEN | head -c 5
Solution:
-
Provide the required
--secretflags when runningdocker build(see Preparation step 2, Option C). -
Verify GitHub credentials are valid and have access to private dependencies.
Port Reference
| Port | Protocol | Description |
|---|---|---|
8080 |
HTTP |
HTTP connector (REST, SOAP, Actuator, Swagger UI) |
8443 |
HTTPS |
TLS connector port (used when |
When HTTPS is enabled (SERVER_SSL_ENABLED=true and keystore settings are provided), Tomcat can run with two connectors:
HTTPS on HTTPS_PORT (typically 8443) and HTTP on PORT (typically 8080).
Use different values for those container ports to keep both connectors active.
Publishing both Docker mappings (-p 8080:8080 and -p 8443:8443) is safe.
|