Installation & Deployment

Overview

This guide walks you through installing and running ft-configs-service — a Spring Boot 4 application built with Java 25 and Gradle. Docker is the recommended deployment method for consistency and ease of use.

The deployment includes:

  • ft-configs-service — Spring Boot REST service for telecom/network device configuration management

  • Docker container — Pre-built image from Harbor registry or loaded from archive

  • Database — MySQL 8.4 or Oracle (schema and user created manually — see 3. Create the Database Schema and User; tables auto-created by Liquibase on first startup)

  • Hazelcast — Distributed cache for runtime consumers (optional, but recommended)

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

Configuration database (schema and user provisioned manually — see 3. Create the Database Schema and User; tables created by Liquibase migrations)

3306 (MySQL) / 1521 (Oracle)

Hazelcast

Distributed cache for runtime consumers (ACS, northbound-api, angular backend)

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 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/ft-configs-service

# 2. Get the Docker image (choose one):

#    Option A: Pull from Harbor registry (recommended)
docker login hub.friendly-tech.com
#    Read-only pull credentials:
#      Username: readonly
#      Password: fokxuw-fymte1-taSxyc
#    Note: "docker pull" is optional if you use docker compose --
#    "docker compose up" will pull the image automatically.
docker pull hub.friendly-tech.com/configs/ft-configs-service:latest

#    Option B: Load from archive (for offline servers — see "Transfer Image to Offline Server" below)
#    gzip -dc ft-configs-service-<version>.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, JWT_SECRET,
                             # FT_CONFIGS_BOOTSTRAP_ADMIN_PASSWORD, 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/configs-service/actuator/health

On first startup a bootstrap admin account is auto-created from the FT_CONFIGS_BOOTSTRAP_ADMIN_* environment variables. The default username is admin. You must set FT_CONFIGS_BOOTSTRAP_ADMIN_PASSWORD to a non-empty value, otherwise the admin account will not be created.

To switch between MySQL and Oracle, change the env_file value in compose.yml:

env_file:
  - .env.oracle    # instead of .env.mysql

After startup the application is available at http://localhost:8080/configs-service.

Preparation

1. Prepare Working Directory

Create the working directory on the host machine:

mkdir -p /usr/local/ft-configs-service/{configs,logs}
cd /usr/local/ft-configs-service

# The container runs as a non-root user (UID 1001) and writes file logs to the
# mounted logs/ directory. Grant write access so the application can write logs:
sudo chown -R 1001:1001 /usr/local/ft-configs-service/logs

The container runs as the non-root user appuser (UID/GID 1001). The bind-mounted logs/ directory is created on the host as root, so the container cannot write to it until ownership is granted (chown -R 1001:1001 logs). Without this step the application logs to stdout only (visible via docker logs), and file logging under /app/logs fails silently. The configs/ directory is read-only for the app and needs no ownership change.

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/ft-configs-service/
+-- .env.mysql                    # Environment variables (MySQL profile)
+-- .env.oracle                   # Environment variables (Oracle profile)
+-- compose.yml                   # Docker Compose file (create from the example in the Deployment section below)
+-- configs/                      # Mounted to /etc/app inside container
|   +-- keystore.p12              # TLS keystore (only when HTTPS enabled)
+-- logs/                         # Application logs (mounted volume)

You only need the env file that matches your database (.env.mysql or .env.oracle), not both.

Configuration files are also available in the source repository:

File Path in repository

.env.mysql, .env.oracle

docker/

Dockerfile

project root (multi-stage build)

Dockerfile.simple

project root (CI/CD only, expects pre-built JAR)

docker-compose.yaml (dev)

project root

2. Get the Docker Image

# Authenticate with the registry.
# Read-only pull credentials:
#   Username: readonly
#   Password: fokxuw-fymte1-taSxyc
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/configs/ft-configs-service:latest

To pin to a specific release version:

docker pull hub.friendly-tech.com/configs/ft-configs-service:v1.0.0-b0.0.1

The version v1.0.0-b0.0.1 is an example. Always use the version tag that corresponds to your deployment.

Optionally, retag the image for shorter references in docker run commands:

docker tag hub.friendly-tech.com/configs/ft-configs-service:latest ft-configs-service:latest

If you skip retagging, use the full image name (hub.friendly-tech.com/configs/ft-configs-service:latest) in all subsequent commands.

The readonly account above provides pull-only access and is sufficient for installation and upgrades. To request elevated Harbor access (for example, push rights or a dedicated robot token), contact the DevOps team.

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.

  1. On a machine with Harbor access, log in:

    # Read-only pull credentials:
    #   Username: readonly
    #   Password: fokxuw-fymte1-taSxyc
    docker login hub.friendly-tech.com
  2. Pull the image for the target server’s architecture:

    docker pull --platform linux/amd64 hub.friendly-tech.com/configs/ft-configs-service:<version>

    An explicit --platform matching the offline target server’s architecture is required. The image in Harbor is multi-arch (linux/amd64, linux/arm64); without --platform, docker pull selects the host architecture, which may not match the target. For example, on an Apple Silicon (arm64) Mac without --platform, the resulting archive will be arm64 and will fail with a platform does not match warning on amd64 servers. The example uses linux/amd64; replace it with the platform of your offline target server (linux/arm64, etc.).

  3. Save the image to a tar archive and compress it:

    docker save hub.friendly-tech.com/configs/ft-configs-service:<version> -o ft-configs-service-<version>.tar
    gzip ft-configs-service-<version>.tar
  4. Transfer ft-configs-service-<version>.tar.gz to the offline server (e.g., via scp or removable media).

  5. On the offline server, load the image:

    gzip -dc ft-configs-service-<version>.tar.gz | docker load

Option C: Build from Source (developers only)

# Build the Docker image (multi-stage Dockerfile builds the JAR inside the container)
docker build -t ft-configs-service:latest .

Building from source requires network access to Maven Central and GitHub Packages (private dependencies).

Loading a pre-built image is faster and avoids build-environment issues.

Dockerfile.simple is used only by CI/CD pipelines — it expects a pre-built JAR at build/libs/ft-configs-service.jar.

After loading or pulling, verify the image is available:

docker images | grep ft-configs-service

3. Create the Database Schema and User

ft-configs-service does not create the database, schema, or database user itself. Liquibase migrations run on startup and create the tables inside an already-existing schema, using a user that already has the required privileges. You must provision the schema and user before the first startup.

The credentials you create here must match the values set in .env.mysql / .env.oracle (MYSQL_SCHEMA/MYSQL_USER/MYSQL_PASSWORD or ORACLE_USER/ORACLE_PASSWORD) in the next step.

The passwords in the snippets below (ftacs_configs, configs) are defaults intended for development. Use a secure password in production and set the same value in the corresponding .env file.

MySQL

Connect as a privileged user (e.g. root) and create the configs schema and the ftacs_configs user:

-- ft-configs-service schema and user
CREATE DATABASE IF NOT EXISTS configs;
CREATE USER IF NOT EXISTS 'ftacs_configs'@'%' IDENTIFIED BY 'ftacs_configs';
GRANT ALL PRIVILEGES ON configs.* TO 'ftacs_configs'@'%';
FLUSH PRIVILEGES;

Run it, for example, with the MySQL client:

mysql -h <mysql-host> -u root -p < create-configs-db.sql
The shared QA provisioning script creates several service databases (flowable, ftacs_quartz, ftacs_qoe_ui, configs) in one file. Only the configs schema and the ftacs_configs user shown above are required by ft-configs-service.
Oracle

Connect as a privileged user (e.g. SYS/SYSTEM) and create the tablespace and the configs user:

-- ft-configs-service tablespace and user
CREATE TABLESPACE configs_data
DATAFILE '&1/configs_data01.dbf' SIZE 100M
AUTOEXTEND ON NEXT 10M MAXSIZE UNLIMITED;

CREATE USER configs IDENTIFIED BY configs
DEFAULT TABLESPACE configs_data
TEMPORARY TABLESPACE temp
QUOTA UNLIMITED ON configs_data;

GRANT CONNECT, RESOURCE TO configs;
GRANT CREATE TABLE TO configs;
GRANT CREATE SEQUENCE TO configs;
GRANT CREATE VIEW TO configs;

&1 is an SQL*Plus substitution variable for the datafile directory inside the database’s persisted volume. Pass it as the first script argument, or replace it with an absolute path:

# Pass the datafile directory as the first argument
sqlplus sys/<password>@//<oracle-host>:1521/XEPDB1 as sysdba @create-configs-db.sql /opt/oracle/oradata/XE/XEPDB1

This user (configs) and its password must match ORACLE_USER/ORACLE_PASSWORD in .env.oracle. The connection uses the XEPDB1 pluggable-database service (ORACLE_SERVICE=XEPDB1).

4. Configure Environment

The ft-configs-service reads its configuration from environment variables loaded via Docker --env-file.

Choose the appropriate environment file:

MySQL
cd /usr/local/ft-configs-service
vi .env.mysql
# ============================================================
# Spring Profile
# ============================================================
SPRING_PROFILES_ACTIVE=mysql

# ============================================================
# Server Ports
# ============================================================
SERVER_PORT=8080
SERVER_HTTP_PORT=8080

# ============================================================
# HTTPS / TLS Settings
#
# Dual-mode setup: SERVER_SSL_ENABLED=true enables HTTPS on SERVER_PORT.
# For dual-mode (HTTP + HTTPS simultaneously), set:
#   SERVER_PORT=8443        <- HTTPS primary
#   SERVER_HTTP_PORT=8080   <- plain HTTP secondary
# SERVER_PORT and SERVER_HTTP_PORT MUST differ in dual mode.
#
# Keystore: Place keystore.p12 in ./configs/ on the host
# (mapped to /etc/app/ inside the container via volume mount).
# ============================================================
# Set to true to enable HTTPS (default: false = plain HTTP only)
SERVER_SSL_ENABLED=false
# Place keystore.p12 in ./configs/ on the host (mapped to /etc/app/ inside the container)
SERVER_SSL_KEY_STORE=file:/etc/app/keystore.p12
# Replace when HTTPS is enabled
SERVER_SSL_KEY_STORE_PASSWORD=
SERVER_SSL_KEY_STORE_TYPE=PKCS12
# Replace when HTTPS is enabled
SERVER_SSL_KEY_PASSWORD=
SERVER_SSL_KEY_ALIAS=server
SERVER_SSL_TRUST_STORE=
SERVER_SSL_TRUST_STORE_PASSWORD=

# ============================================================
# Timezone
# ============================================================
TZ=UTC

# ============================================================
# Database -- MySQL
# ============================================================
DB_MAX_POOL_SIZE=10
DB_MIN_IDLE=5
DB_CONNECTION_TIMEOUT_MS=30000
# Replace with your MySQL host if needed
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_SCHEMA=configs
MYSQL_USER=ftacs_configs
MYSQL_PASSWORD=ftacs_configs
MYSQL_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver

# ============================================================
# JWT Authentication
# ============================================================
# REQUIRED. Base64-encoded HMAC-SHA256 signing key for access/refresh tokens.
# Must decode to at least 32 bytes (256-bit); startup fails otherwise.
# Generate a fresh, unique value per environment: openssl rand -base64 32
JWT_SECRET=
# Access token lifetime in milliseconds (86400000 = 24h)
JWT_EXPIRATION=86400000
# Refresh token lifetime in milliseconds (604800000 = 7 days)
JWT_REFRESH_TOKEN_EXPIRATION=604800000

# ============================================================
# Cookie
# ============================================================
# Mark auth cookies as Secure (sent only over HTTPS). Set true whenever the UI is
# served over HTTPS (directly or behind a TLS-terminating proxy); keep false for plain-HTTP dev.
COOKIE_SECURE=false
# Cookie Domain attribute. Leave blank to scope cookies to the request host.
# Set a parent domain (e.g. .example.com) only to share cookies across subdomains.
COOKIE_DOMAIN=

# ============================================================
# CORS
# ============================================================
# Comma-separated list of allowed origins for cross-origin requests.
# Must match the URL the user sees in the browser address bar (scheme + host + port);
# scheme-sensitive -- http:// and https:// are different origins.
# Without proper CORS origins, the browser blocks API requests with "Invalid CORS request".
# List every origin the UI is reached on. The UI is served on both HTTP and HTTPS
# (defaults: 3001 for HTTP, 3443 for HTTPS, both published by Docker) -- include both,
# not only http://.
# This is the host the user types in the browser address bar: "localhost" only when
# browsing locally, otherwise the public hostname (e.g. https://management.example.com).
# NEVER the internal Docker service name -- "http://ft-configs-ui:3001" is wrong here,
# the browser never sends it as an Origin (that name belongs in the UI's BACKEND_URL, not CORS).
# Replace <ui-host> with that hostname (e.g. localhost for local browsing, or your FQDN).
CORS_ALLOWED_ORIGINS=http://<ui-host>:3001,https://<ui-host>:3443

# ============================================================
# Mail (password reset, registration, deletion OTP)
#
# MAIL_MODE is the PRIMARY control for email delivery:
#   AUTO    (default) -- infer from SMTP config: OFFLINE when MAIL_HOST,
#                        MAIL_USERNAME or MAIL_PASSWORD are not all set. When SMTP
#                        is configured but the server is unreachable, delivery
#                        degrades to OFFLINE behaviour at runtime (see below).
#   OFFLINE           -- never send email.
#
# OFFLINE behaviour (chosen explicitly, inferred, or reached at runtime when the
# configured SMTP server is unreachable) degrades gracefully instead of failing:
#   * Create user / reset password: the temporary password is RETURNED in the
#     API response (passwordDelivery=RETURNED) for the admin to hand over
#     out-of-band, instead of being emailed.
#   * User deletion: OTP email confirmation is disabled. The request returns
#     otpRequired=false (no token); the account is deleted in the confirm step,
#     called without an OTP.
#
# RUNTIME FALLBACK: under AUTO with SMTP configured, if the server is unreachable
# or a send fails, both password delivery and user deletion degrade to the
# OFFLINE behaviour above instead of erroring. SMTP connect/read/write timeout is
# fixed at 5s for a prompt fallback.
#
# With the defaults below (empty MAIL_USERNAME/MAIL_PASSWORD) AUTO resolves to
# OFFLINE out of the box -- intended for air-gapped customer installs.
# ============================================================
# Delivery mode: AUTO | OFFLINE
MAIL_MODE=AUTO
# SMTP server hostname (example -- replace with your SMTP host)
MAIL_HOST=smtp.example.com
# SMTP server port (587 for STARTTLS, 465 for SSL)
MAIL_PORT=587
# SMTP auth username, usually the sender address (under AUTO, blank => offline)
MAIL_USERNAME=
# SMTP auth password (under AUTO, blank => offline)
MAIL_PASSWORD=
# Mail transport protocol (smtp or smtps)
MAIL_PROTOCOL=smtp
# Enable SMTP authentication; true/false
MAIL_SMTP_AUTH=true
# Enable STARTTLS encryption for the SMTP connection; true/false
MAIL_SMTP_STARTTLS=true
# Sender address shown in the From header of outgoing messages (example -- replace with yours)
MAIL_FROM=no-reply@example.com
# URL for the "Sign in" button in registration emails. Full absolute URL with
# http:// or https:// scheme. Blank/non-HTTP => the button is replaced by a text hint.
MAIL_LOGIN_URL=

# ============================================================
# Bootstrap Admin
# ============================================================
FT_CONFIGS_BOOTSTRAP_ADMIN_USERNAME=admin
# REQUIRED on first startup. Rotate immediately after calling /auth/first-login
FT_CONFIGS_BOOTSTRAP_ADMIN_PASSWORD=
# Optional admin email
FT_CONFIGS_BOOTSTRAP_ADMIN_EMAIL=
FT_CONFIGS_BOOTSTRAP_ADMIN_LOCALE=

# ============================================================
# Hazelcast
# ============================================================
# Path to Hazelcast client configuration; classpath: uses the JAR-bundled default
CACHE_CONFIG_PATH=classpath:
# Uncomment and set when connecting to an external Hazelcast cluster:
# HZ_MEMBERS=hazelcast-host:5701

# ============================================================
# ACS Integration
# ============================================================
# Hazelcast cache name from which ACS publishes product-class groups, used by the
# dataStoreExclusionModels dropdown. Must match the name ACS publishes to.
# Leave commented to use the built-in default (qoeProductClassGroupCache).
# FT_CONFIGS_ACS_EXCLUSION_MODELS_CACHE_NAME=qoeProductClassGroupCache

# ============================================================
# Audit
# ============================================================
# Days to retain audit events (auto-purge at 03:00 daily). Min 3, max 90; 0 = keep forever.
AUDIT_RETENTION_DAYS=30
# Extra sensitive field names to redact in audit snapshots (comma-separated),
# added on top of the built-in defaults (password, token, secret, apiKey, ...).
# AUDIT_REDACTION_FIELDS=

# ============================================================
# Logging
# ============================================================
LOGGING_LEVEL_COM_FRIENDLY_FTCONFIGSSERVICE=INFO
With MAIL_MODE=AUTO, blank SMTP credentials (MAIL_HOST/MAIL_USERNAME/MAIL_PASSWORD not all set) select offline mode: temporary passwords are returned in the create-user/reset-password API responses instead of being emailed, and account deletion uses a plain confirmation instead of OTP (the account is still deleted in the confirm step). Set MAIL_MODE=OFFLINE to force this for air-gapped installs, or provide real, reachable SMTP credentials for email delivery (a configured-but-unreachable server under AUTO degrades to the same offline behaviour instead of failing).
Oracle
cd /usr/local/ft-configs-service
vi .env.oracle
# ============================================================
# Spring Profile
# ============================================================
SPRING_PROFILES_ACTIVE=oracle

# ============================================================
# Server Ports
# ============================================================
SERVER_PORT=8080
SERVER_HTTP_PORT=8080

# ============================================================
# HTTPS / TLS Settings
#
# Dual-mode setup: SERVER_SSL_ENABLED=true enables HTTPS on SERVER_PORT.
# For dual-mode (HTTP + HTTPS simultaneously), set:
#   SERVER_PORT=8443        <- HTTPS primary
#   SERVER_HTTP_PORT=8080   <- plain HTTP secondary
# SERVER_PORT and SERVER_HTTP_PORT MUST differ in dual mode.
#
# Keystore: Place keystore.p12 in ./configs/ on the host
# (mapped to /etc/app/ inside the container via volume mount).
# ============================================================
# Set to true to enable HTTPS (default: false = plain HTTP only)
SERVER_SSL_ENABLED=false
# Place keystore.p12 in ./configs/ on the host (mapped to /etc/app/ inside the container)
SERVER_SSL_KEY_STORE=file:/etc/app/keystore.p12
# Replace when HTTPS is enabled
SERVER_SSL_KEY_STORE_PASSWORD=
SERVER_SSL_KEY_STORE_TYPE=PKCS12
# Replace when HTTPS is enabled
SERVER_SSL_KEY_PASSWORD=
SERVER_SSL_KEY_ALIAS=server
SERVER_SSL_TRUST_STORE=
SERVER_SSL_TRUST_STORE_PASSWORD=

# ============================================================
# Timezone
# ============================================================
TZ=UTC

# ============================================================
# Database -- Oracle
# ============================================================
DB_MAX_POOL_SIZE=10
DB_MIN_IDLE=5
DB_CONNECTION_TIMEOUT_MS=30000
# Replace with your Oracle host
ORACLE_HOST=oracle
ORACLE_PORT=1521
ORACLE_SERVICE=XEPDB1
ORACLE_USER=configs
# Replace with a secure password
ORACLE_PASSWORD=configs
ORACLE_DRIVER_CLASS_NAME=oracle.jdbc.OracleDriver
# Optional Hibernate default schema. Leave blank to use the connection user's schema.
# ORACLE_SCHEMA=

# ============================================================
# JWT Authentication
# ============================================================
# REQUIRED. Base64-encoded HMAC-SHA256 signing key for access/refresh tokens.
# Must decode to at least 32 bytes (256-bit); startup fails otherwise.
# Generate a fresh, unique value per environment: openssl rand -base64 32
JWT_SECRET=
# Access token lifetime in milliseconds (86400000 = 24h)
JWT_EXPIRATION=86400000
# Refresh token lifetime in milliseconds (604800000 = 7 days)
JWT_REFRESH_TOKEN_EXPIRATION=604800000

# ============================================================
# Cookie
# ============================================================
# Mark auth cookies as Secure (sent only over HTTPS). Set true whenever the UI is
# served over HTTPS (directly or behind a TLS-terminating proxy); keep false for plain-HTTP dev.
COOKIE_SECURE=false
# Cookie Domain attribute. Leave blank to scope cookies to the request host.
# Set a parent domain (e.g. .example.com) only to share cookies across subdomains.
COOKIE_DOMAIN=

# ============================================================
# CORS
# ============================================================
# Comma-separated list of allowed origins for cross-origin requests.
# Must match the URL the user sees in the browser address bar (scheme + host + port);
# scheme-sensitive -- http:// and https:// are different origins.
# Without proper CORS origins, the browser blocks API requests with "Invalid CORS request".
# List every origin the UI is reached on. The UI is served on both HTTP and HTTPS
# (defaults: 3001 for HTTP, 3443 for HTTPS, both published by Docker) -- include both,
# not only http://.
# This is the host the user types in the browser address bar: "localhost" only when
# browsing locally, otherwise the public hostname (e.g. https://management.example.com).
# NEVER the internal Docker service name -- "http://ft-configs-ui:3001" is wrong here,
# the browser never sends it as an Origin (that name belongs in the UI's BACKEND_URL, not CORS).
# Replace <ui-host> with that hostname (e.g. localhost for local browsing, or your FQDN).
CORS_ALLOWED_ORIGINS=http://<ui-host>:3001,https://<ui-host>:3443

# ============================================================
# Mail (password reset, registration, deletion OTP)
#
# MAIL_MODE is the PRIMARY control for email delivery:
#   AUTO    (default) -- infer from SMTP config: OFFLINE when MAIL_HOST,
#                        MAIL_USERNAME or MAIL_PASSWORD are not all set. When SMTP
#                        is configured but the server is unreachable, delivery
#                        degrades to OFFLINE behaviour at runtime (see below).
#   OFFLINE           -- never send email.
#
# OFFLINE behaviour (chosen explicitly, inferred, or reached at runtime when the
# configured SMTP server is unreachable) degrades gracefully instead of failing:
#   * Create user / reset password: the temporary password is RETURNED in the
#     API response (passwordDelivery=RETURNED) for the admin to hand over
#     out-of-band, instead of being emailed.
#   * User deletion: OTP email confirmation is disabled. The request returns
#     otpRequired=false (no token); the account is deleted in the confirm step,
#     called without an OTP.
#
# RUNTIME FALLBACK: under AUTO with SMTP configured, if the server is unreachable
# or a send fails, both password delivery and user deletion degrade to the
# OFFLINE behaviour above instead of erroring. SMTP connect/read/write timeout is
# fixed at 5s for a prompt fallback.
#
# With the defaults below (empty MAIL_USERNAME/MAIL_PASSWORD) AUTO resolves to
# OFFLINE out of the box -- intended for air-gapped customer installs.
# ============================================================
# Delivery mode: AUTO | OFFLINE
MAIL_MODE=AUTO
# SMTP server hostname (example -- replace with your SMTP host)
MAIL_HOST=smtp.example.com
# SMTP server port (587 for STARTTLS, 465 for SSL)
MAIL_PORT=587
# SMTP auth username, usually the sender address (under AUTO, blank => offline)
MAIL_USERNAME=
# SMTP auth password (under AUTO, blank => offline)
MAIL_PASSWORD=
# Mail transport protocol (smtp or smtps)
MAIL_PROTOCOL=smtp
# Enable SMTP authentication; true/false
MAIL_SMTP_AUTH=true
# Enable STARTTLS encryption for the SMTP connection; true/false
MAIL_SMTP_STARTTLS=true
# Sender address shown in the From header of outgoing messages (example -- replace with yours)
MAIL_FROM=no-reply@example.com
# URL for the "Sign in" button in registration emails. Full absolute URL with
# http:// or https:// scheme. Blank/non-HTTP => the button is replaced by a text hint.
MAIL_LOGIN_URL=

# ============================================================
# Bootstrap Admin
# ============================================================
FT_CONFIGS_BOOTSTRAP_ADMIN_USERNAME=admin
# REQUIRED on first startup. Rotate immediately after calling /auth/first-login
FT_CONFIGS_BOOTSTRAP_ADMIN_PASSWORD=
# Optional admin email
FT_CONFIGS_BOOTSTRAP_ADMIN_EMAIL=
FT_CONFIGS_BOOTSTRAP_ADMIN_LOCALE=

# ============================================================
# Hazelcast
# ============================================================
# Path to Hazelcast client configuration; classpath: uses the JAR-bundled default
CACHE_CONFIG_PATH=classpath:
# Uncomment and set when connecting to an external Hazelcast cluster:
# HZ_MEMBERS=hazelcast-host:5701

# ============================================================
# ACS Integration
# ============================================================
# Hazelcast cache name from which ACS publishes product-class groups, used by the
# dataStoreExclusionModels dropdown. Must match the name ACS publishes to.
# Leave commented to use the built-in default (qoeProductClassGroupCache).
# FT_CONFIGS_ACS_EXCLUSION_MODELS_CACHE_NAME=qoeProductClassGroupCache

# ============================================================
# Audit
# ============================================================
# Days to retain audit events (auto-purge at 03:00 daily). Min 3, max 90; 0 = keep forever.
AUDIT_RETENTION_DAYS=30
# Extra sensitive field names to redact in audit snapshots (comma-separated),
# added on top of the built-in defaults (password, token, secret, apiKey, ...).
# AUDIT_REDACTION_FIELDS=

# ============================================================
# Logging
# ============================================================
LOGGING_LEVEL_COM_FRIENDLY_FTCONFIGSSERVICE=INFO
With MAIL_MODE=AUTO, blank SMTP credentials (MAIL_HOST/MAIL_USERNAME/MAIL_PASSWORD not all set) select offline mode: temporary passwords are returned in the create-user/reset-password API responses instead of being emailed, and account deletion uses a plain confirmation instead of OTP (the account is still deleted in the confirm step). Set MAIL_MODE=OFFLINE to force this for air-gapped installs, or provide real, reachable SMTP credentials for email delivery (a configured-but-unreachable server under AUTO degrades to the same offline behaviour instead of failing).

If the database or Hazelcast cluster runs on the host machine (not in Docker), use one of the following as the hostname in MYSQL_HOST / ORACLE_HOST and HZ_MEMBERS:

  • host.docker.internal — works on Docker Desktop (macOS, Windows) and on Linux with --add-host=host.docker.internal:host-gateway (for docker run). The provided compose.yml already includes extra_hosts for this.

  • The host machine’s real IP address (e.g., 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.

Pre-configured template files are available on FT_DISK and in the source repository under docker/.

Copy the file that matches your database to the working directory:

cp docker/.env.mysql /usr/local/ft-configs-service/.env.mysql
# or
cp docker/.env.oracle /usr/local/ft-configs-service/.env.oracle

TLS Keystore (HTTPS)

To enable HTTPS, provide a PKCS12 keystore file. Place keystore.p12 in the configs/ directory on the host (mounted to /etc/app/ inside the container).

For local development, you can generate a self-signed keystore:

cd /usr/local/ft-configs-service

keytool -genkeypair \
  -alias server \
  -keyalg RSA \
  -keysize 2048 \
  -storetype PKCS12 \
  -keystore configs/keystore.p12 \
  -validity 3650 \
  -storepass <keystore-password> \
  -keypass <key-password> \
  -dname "CN=*.friendly-tech.com, OU=Dev, O=Friendly, L=Local, ST=Local, C=US" \
  -ext "SAN=dns:*.friendly-tech.com,dns:friendly-tech.com,dns:localhost,ip:127.0.0.1"

The SAN (Subject Alternative Name) extension must list every hostname the certificate will be presented for. Modern TLS clients (browsers, Java 7+, curl, OpenSSL >= 1.0.2) ignore the CN and validate against SAN only (RFC 6125, CA/Browser Forum Baseline Requirements).

Recommended values for the friendly-tech.com zone:

  • dns:*.friendly-tech.com — wildcard for any single-label host (e.g. qa22m.friendly-tech.com, prod.friendly-tech.com).

  • dns:friendly-tech.com — apex domain (wildcard does not match the apex).

  • dns:localhost, ip:127.0.0.1 — local access for diagnostics.

Wildcard limitations:

  • .friendly-tech.com matches *exactly one label: name.friendly-tech.com works, but sub.name.friendly-tech.com does not.

  • For multi-level subdomains, add them explicitly: dns:*.qa.friendly-tech.com.

If you generate the certificate for a single concrete host, replace the wildcard with that FQDN in both CN and SAN:

-dname "CN=qa22m.friendly-tech.com, ..." \
-ext "SAN=dns:qa22m.friendly-tech.com,dns:localhost,ip:127.0.0.1"

Then set these values in .env.mysql or .env.oracle:

  • SERVER_SSL_ENABLED=true

  • SERVER_PORT=8443 (HTTPS primary port)

  • SERVER_HTTP_PORT=8080 (plain HTTP secondary port)

  • SERVER_SSL_KEY_STORE_PASSWORD=<keystore-password>

  • SERVER_SSL_KEY_PASSWORD=<key-password>

For production, use a certificate issued by your CA/security team and export it to PKCS12 format (.p12).

Bootstrap Admin (first install)

On the very first startup, ft-configs-service creates a bootstrap administrator account using environment variables:

  • FT_CONFIGS_BOOTSTRAP_ADMIN_USERNAME — defaults to admin

  • FT_CONFIGS_BOOTSTRAP_ADMIN_PASSWORD — must be set or the account will not be created

  • FT_CONFIGS_BOOTSTRAP_ADMIN_EMAIL — admin email address

  • FT_CONFIGS_BOOTSTRAP_ADMIN_LOCALE — locale preference (optional)

After first startup, immediately change the bootstrap admin password by calling the /auth/first-login endpoint. The bootstrap password is stored in the .env file on disk — rotate it and restrict file access (chmod 600 .env.*).

Deployment

Create a compose.yml file in your working directory:

services:
  ft-configs-service:
    image: hub.friendly-tech.com/configs/ft-configs-service:latest
    container_name: ft-configs-service
    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:
      - ./configs:/etc/app
      - ./logs:/app/logs
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/configs-service/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 ft-configs-service

# Stop
docker compose down

To deploy ft-configs-service alongside other Friendly Tech Java services as a single stack, see the Java API Stack Deployment Guide.

Option 2: docker run

cd /usr/local/ft-configs-service

docker run -d \
  --name ft-configs-service \
  --env-file .env.mysql \
  -v $(pwd)/configs:/etc/app \
  -v $(pwd)/logs:/app/logs \
  -p 8080:8080 \
  -p 8443:8443 \
  --add-host=host.docker.internal:host-gateway \
  --restart unless-stopped \
  hub.friendly-tech.com/configs/ft-configs-service: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 ft-configs-service:latest instead of the full Harbor path.

Docker Networking

If the ft-configs-service container must communicate with other containers (database, Hazelcast, etc.), create a shared Docker network:

docker network create ft-configs-net

Add --network ft-configs-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.

Verify Deployment

After starting the container, run the following checks:

# 1. Check container status
docker ps -f name=ft-configs-service
# Expected: container with status "Up" and port 0.0.0.0:8080->8080/tcp

# 2. Health check
curl -s http://localhost:8080/configs-service/actuator/health
# Expected: {"status":"UP"}

# 2b. HTTPS health check (when SERVER_SSL_ENABLED=true)
curl -ks https://localhost:8443/configs-service/actuator/health
# Expected: {"status":"UP"}

# 3. Check application logs
docker logs ft-configs-service --tail 50
# Look for: "Started FtConfigsServiceApplication in XX.XXX seconds"

# 4. Prometheus metrics
curl -s http://localhost:8080/configs-service/actuator/prometheus | head -20
# Expected: lines starting with "# HELP" and "# TYPE" followed by metric values

# 5. Swagger UI (open in browser)
# http://localhost:8080/configs-service/swagger-ui/index.html
# https://localhost:8443/configs-service/swagger-ui/index.html  (when HTTPS enabled)

Environment Variables

Do not commit secrets (*_PASSWORD, JWT_SECRET) into Git. Use .env locally, and use Secret managers (Kubernetes Secrets, Vault, etc.) in shared environments.

HZ_MEMBERS must be reachable from inside the container/pod (Docker and Kubernetes networking apply).

A) Quick start (most common knobs)

Variable Required Default Description

SPRING_PROFILES_ACTIVE

Yes

local

Active Spring profile: local, mysql, or oracle. Controls which application-*.yml is loaded. Must be set explicitly in Docker and Kubernetes.

SERVER_PORT

No

8080

Primary server port. When SERVER_SSL_ENABLED=true, this becomes the HTTPS port.

SERVER_HTTP_PORT

No

8080

Plain HTTP port. In dual-mode (HTTP + HTTPS), set this to a different value than SERVER_PORT (e.g., 8080 for HTTP, 8443 for HTTPS).

HZ_MEMBERS

No

from Hazelcast client config

Comma-separated Hazelcast members (host:port). Required in Docker/Kubernetes. Default port 5701 is added automatically if missing.

CORS_ALLOWED_ORIGINS

No

http://localhost:9002

Comma-separated list of allowed CORS origins. Must match the URL the user sees in the browser address bar (where the frontend is loaded from), never the internal Docker service name. Scheme-sensitive: http:// and https:// are different origins — the UI is served on both HTTP (default port 3001) and HTTPS (default port 3443, both published by Docker), so list both. Examples: http://localhost:9002 (dev), http://<ui-host>:3001,https://<ui-host>:3443 (Docker, HTTP + HTTPS UI), https://management.example.com (production).

LOGGING_LEVEL_COM_FRIENDLY_FTCONFIGSSERVICE

No

INFO

Log level for the application package. Values: ERROR, WARN, INFO, DEBUG. Can also be changed at runtime via Actuator.

B) Database (MySQL profile: SPRING_PROFILES_ACTIVE=mysql)

Provide either MYSQL_JDBC_URL or the decomposed MYSQL_HOST/MYSQL_PORT/MYSQL_SCHEMA inputs.

Variable Required Default Description

MYSQL_JDBC_URL

No

derived if unset

Full MySQL JDBC URL. Recommended for Docker/Kubernetes to avoid host, port, and schema drift.

DB_HOST

No

localhost

Shared DB host fallback used by MySQL and Oracle when profile-specific host is not set.

MYSQL_HOST

No

${DB_HOST:localhost}

MySQL host used if MYSQL_JDBC_URL is not set.

MYSQL_PORT

No

3306

MySQL port used if MYSQL_JDBC_URL is not set.

MYSQL_SCHEMA

No

configs

MySQL schema used if MYSQL_JDBC_URL is not set.

MYSQL_USER

No

ftacs (template: ftacs_configs)

MySQL username for the service. The env templates in docker/ ship with ftacs_configs.

MYSQL_PASSWORD

No

ftacs (template: ftacs_configs)

MySQL password for the service. The env templates in docker/ ship with ftacs_configs. Treat as a secret.

MYSQL_DRIVER_CLASS_NAME

No

com.mysql.cj.jdbc.Driver

Override JDBC driver class if needed.

DB_MAX_POOL_SIZE

No

10

HikariCP max pool size.

DB_MIN_IDLE

No

5

HikariCP minimum idle connections.

DB_CONNECTION_TIMEOUT_MS

No

30000

HikariCP connection timeout in milliseconds.

When both *_JDBC_URL and decomposed variables are set, the explicit JDBC URL always takes precedence.

C) Database (Oracle profile: SPRING_PROFILES_ACTIVE=oracle)

Provide either ORACLE_JDBC_URL or the decomposed ORACLE_HOST/ORACLE_PORT/ORACLE_SERVICE (plus optional ORACLE_SCHEMA) inputs.

Variable Required Default Description

ORACLE_JDBC_URL

No

derived if unset

Full Oracle JDBC URL. Recommended for Docker/Kubernetes to avoid host, port, and service drift.

ORACLE_HOST

No

${DB_HOST:localhost}

Oracle host used if ORACLE_JDBC_URL is not set.

ORACLE_PORT

No

1521

Oracle port used if ORACLE_JDBC_URL is not set.

ORACLE_SERVICE

No

XEPDB1

Oracle service name used if ORACLE_JDBC_URL is not set.

ORACLE_SCHEMA

No

empty

Optional schema mapped to hibernate.default_schema.

ORACLE_USER

No

ftacs

Oracle username for the service.

ORACLE_PASSWORD

No

ftacs

Oracle password for the service. Treat as a secret.

ORACLE_DRIVER_CLASS_NAME

No

oracle.jdbc.OracleDriver

Override Oracle JDBC driver class if needed.

D) Hazelcast / cache

Variable Required Default Description

CACHE_CONFIG_PATH

No

classpath:

Base path for Hazelcast config files. hazelcast-client.yaml is resolved relative to this path.

CACHE_IS_SERVER

No

false

When true, starts an embedded Hazelcast member from ${cache-config.path}hazelcast.yaml. Not used in production.

FT_CONFIGS_ACS_EXCLUSION_MODELS_CACHE_NAME

No

qoeProductClassGroupCache

Name of the Hazelcast cache from which ACS publishes product-class groups, consumed by the dataStoreExclusionModels dropdown. Must match the name ACS publishes to; the default matches the standard ACS deployment.

E) Auth / JWT & cookies

Variable Required Default Description

JWT_SECRET

No

defined in profile

Base64-encoded HMAC-SHA256 signing key. Must decode to at least 32 bytes (256-bit) or startup fails. Generate with openssl rand -base64 32. Must be unique per environment. Never commit to Git.

JWT_EXPIRATION

No

86400000

Access token expiration in milliseconds.

JWT_REFRESH_TOKEN_EXPIRATION

No

604800000

Refresh token expiration in milliseconds.

COOKIE_SECURE

No

true (template: false)

Set true when HTTPS is used end-to-end or via a trusted TLS-terminating proxy. The env templates ship with false for local development.

COOKIE_DOMAIN

No

empty

Optional domain for cross-subdomain cookie sharing.

Mail

Variable Required Default Description

MAIL_MODE

No

AUTO

Email delivery mode (case-insensitive; blank or unknown falls back to AUTO with a warning log). The supported values are AUTO and OFFLINE. AUTO infers the mode from SMTP config: it is treated as OFFLINE when MAIL_HOST, MAIL_USERNAME, or MAIL_PASSWORD are not all set (non-blank); when they are all set it attempts email, but if the SMTP server is unreachable at runtime it degrades to offline behaviour. OFFLINE never sends email. In offline mode (configured or degraded at runtime), create-user and reset-password return the temporary password in the API response (passwordDelivery=RETURNED) instead of emailing it, and user-deletion requests skip OTP (returning otpRequired=false); the account is deleted in the confirm step, called without an OTP.

MAIL_HOST

No

smtp.gmail.com

SMTP host used for registration and deletion flows. Under MAIL_MODE=AUTO, leaving this blank (together with the credentials) selects offline mode.

MAIL_PORT

No

587

SMTP port.

MAIL_USERNAME

No

SMTP username. Under MAIL_MODE=AUTO, leaving this blank selects offline mode (no email is sent; secrets are returned in API responses). The local profile uses an empty default (${MAIL_USERNAME:}).

MAIL_PASSWORD

No

SMTP password. Treat as a secret. Under MAIL_MODE=AUTO, leaving this blank selects offline mode. The local profile uses an empty default (${MAIL_PASSWORD:}).

MAIL_PROTOCOL

No

smtp

Usually smtp.

MAIL_SMTP_AUTH

No

true

SMTP authentication toggle.

MAIL_SMTP_STARTTLS

No

true

STARTTLS toggle.

MAIL_FROM

No

no-reply@friendly.local

From address used in sent emails.

MAIL_LOGIN_URL

No

empty

URL for the "Sign In" call-to-action button in emails. Must start with http:// or https://. If blank or non-HTTP, the CTA button is replaced by a generic text hint. Example: https://management.example.com/configs-service.

The MAIL_FROM_NAME variable has been removed. The sender display name is now localized via the mail.brand message key (mail_messages*.properties), returning "Configuration Center" (EN) or the localized equivalent for other locales.

Audit

Variable Required Default Description

AUDIT_RETENTION_DAYS

No

30

Number of days to retain audit events. Minimum 3, maximum 90. Set to 0 to disable retention. Values outside range are clamped with a warning.

AUDIT_REDACTION_FIELDS

No

empty

Comma-separated list of additional sensitive field names to redact in audit snapshots and change records. Added on top of the 18 built-in defaults (password, token, secret, apiKey, etc.).

Bootstrap admin (first install)

Variable Required Default Description

FT_CONFIGS_BOOTSTRAP_ADMIN_USERNAME

No

admin

Username for first startup when no admin exists.

FT_CONFIGS_BOOTSTRAP_ADMIN_PASSWORD

Yes (first startup)

empty

Password for bootstrap admin. Must be set on first startup or the admin account will not be created. Rotate immediately after bootstrap via /auth/first-login. Treat as a secret.

FT_CONFIGS_BOOTSTRAP_ADMIN_EMAIL

No

empty

Optional email assigned to bootstrap admin.

FT_CONFIGS_BOOTSTRAP_ADMIN_LOCALE

No

empty

Optional locale for bootstrap admin.

JVM / container runtime

Variable Required Default Description

JAVA_OPTS

No

set in Docker image

JVM options passed by docker-entrypoint.sh. Add only needed flags.

TZ

No

UTC

Container timezone.

For comprehensive details on every configuration option, see the Configuration Guide.

Container Management

Command Description

docker compose up -d

Start all services in the background

docker compose down

Stop and remove containers

docker compose logs -f ft-configs-service

Follow application logs

docker compose ps

Show container status

docker compose restart ft-configs-service

Restart the application

docker compose pull

Update images to latest versions

docker exec -it ft-configs-service sh

Enter the application container

docker exec ft-configs-service ls -la /etc/app

Check mounted configuration files

docker logs --tail 100 ft-configs-service

View last 100 log lines

docker stats ft-configs-service --no-stream

View resource usage

docker inspect ft-configs-service --format '{{json .Config.Env}}'

Check environment variables

Updating

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/ft-configs-service
tar -czf ft-configs-backup-$(date +%Y%m%d).tar.gz .env.* configs/

# 2. Pull the latest image
docker compose pull ft-configs-service

# 3. Recreate the container with the new image
docker compose up -d ft-configs-service

# 4. Verify
curl -s http://localhost:8080/configs-service/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.

  1. On a machine with Harbor access, log in:

    # Read-only pull credentials:
    #   Username: readonly
    #   Password: fokxuw-fymte1-taSxyc
    docker login hub.friendly-tech.com
  2. Pull the new image for the target server’s architecture:

    docker pull --platform linux/amd64 hub.friendly-tech.com/configs/ft-configs-service:<new-version>

    An explicit --platform matching the offline target server’s architecture is required. The image in Harbor is multi-arch (linux/amd64, linux/arm64); without --platform, docker pull selects the host architecture, which may not match the target. For example, on an Apple Silicon (arm64) Mac without --platform, the resulting archive will be arm64 and will fail with a platform does not match warning on amd64 servers. The example uses linux/amd64; replace it with the platform of your offline target server (linux/arm64, etc.).

  3. Save the image to a tar archive and compress it:

    docker save hub.friendly-tech.com/configs/ft-configs-service:<new-version> -o ft-configs-service-<new-version>.tar
    gzip ft-configs-service-<new-version>.tar
  4. Transfer ft-configs-service-<new-version>.tar.gz to the offline server (e.g., via scp or removable media).

  5. On the offline server, load the new image and replace the running container:

    # 1. Load the new image from archive
    gzip -dc ft-configs-service-<new-version>.tar.gz | docker load
    
    # 2. Replace the container
    docker stop ft-configs-service
    docker rm ft-configs-service
    
    docker run -d \
      --name ft-configs-service \
      --env-file .env.mysql \
      -v $(pwd)/configs:/etc/app \
      -v $(pwd)/logs:/app/logs \
      -p 8080:8080 \
      -p 8443:8443 \
      --add-host=host.docker.internal:host-gateway \
      --restart unless-stopped \
      hub.friendly-tech.com/configs/ft-configs-service:<new-version>
    
    # 3. Verify
    curl -s http://localhost:8080/configs-service/actuator/health

Rollback

docker stop ft-configs-service
docker rm ft-configs-service

# Re-run with the previous image tag
docker run -d --name ft-configs-service \
  --env-file .env.mysql \
  -v $(pwd)/configs:/etc/app \
  -v $(pwd)/logs:/app/logs \
  -p 8080:8080 \
  -p 8443:8443 \
  --add-host=host.docker.internal:host-gateway \
  --restart unless-stopped \
  hub.friendly-tech.com/configs/ft-configs-service:<previous-version-tag>

Production Checklist

# Item Notes

1

Restart policy

restart: unless-stopped is already configured in the examples

2

Health check

Configured for /configs-service/actuator/health with a 30s interval

3

Database credentials

Replace default values in the .env file (MYSQL_PASSWORD / ORACLE_PASSWORD)

4

JWT secret

Replace empty JWT_SECRET with a secure value: openssl rand -hex 32

5

CORS origins

Replace the <ui-host> placeholder in CORS_ALLOWED_ORIGINS with the host users type in the browser to access the frontend (e.g., https://management.example.com). The UI is served on both HTTP (3001) and HTTPS (3443), so list both origins, not only the http:// one

6

File permissions

Restrict env files: chmod 600 .env.*

7

Log management

Mount /app/logs volume; configure external log rotation (e.g., logrotate)

8

Timezone

Set TZ in .env (defaults to UTC)

9

Resource limits

Add --memory=2g or deploy.resources.limits in compose.yml for production

10

Bootstrap admin password rotated

After first login, change the bootstrap admin password and remove it from .env

Troubleshooting

Start with quick diagnostics:

docker logs ft-configs-service --tail 100
docker inspect ft-configs-service --format '{{json .Config.Env}}'
docker exec ft-configs-service ls -la /etc/app

Common Issues

Env File Not Found

Symptom: Docker fails to start with open .env.mysql: no such file or directory.

Check:

ls -la /usr/local/ft-configs-service/.env.*

Solution:

  • Run docker run from the directory containing the env file, or use an absolute path: --env-file /usr/local/ft-configs-service/.env.mysql.

  • Check file permissions: chmod 640 .env.*.

  • In compose.yml, verify the env_file path is correct relative to the compose file location.

Database Connection Failures (MySQL)

Symptom: Communications link failure or Access denied in logs.

Check:

docker exec ft-configs-service nc -zv <db-host> 3306
docker exec ft-configs-service env | grep MYSQL

Solution:

  • Verify the database host is reachable from the container.

  • Check MYSQL_HOST, MYSQL_PORT, MYSQL_USER, and MYSQL_PASSWORD in your .env.mysql file.

  • If the database runs on the host machine, use host.docker.internal instead of localhost.

  • If using Docker networking, ensure both containers are on the same network:

    docker network inspect ft-configs-net

Database Connection Failures (Oracle)

Symptom: ORA-12514: Cannot connect to Oracle or ORA-01017: invalid username/password in logs.

Check:

docker exec ft-configs-service nc -zv <db-host> 1521
docker exec ft-configs-service env | grep ORACLE

Solution:

  • Verify ORACLE_HOST, ORACLE_PORT, ORACLE_SERVICE, ORACLE_USER, and ORACLE_PASSWORD in .env.oracle.

  • Confirm the Oracle service name matches exactly (case-sensitive): ORACLE_SERVICE=XEPDB1.

  • If the database runs on the host machine, use host.docker.internal instead of localhost.

Hazelcast Connection Failure

Symptom: Unable to connect to any address in logs.

Check:

docker exec ft-configs-service env | grep HZ_MEMBERS
docker exec ft-configs-service env | grep CACHE_CONFIG

Solution:

  • Verify HZ_MEMBERS is set and points to reachable Hazelcast members (e.g., HZ_MEMBERS=hazelcast-host:5701).

  • Confirm Hazelcast members are running and on the same Docker network.

  • Check that CACHE_CONFIG_PATH in the env file is correct. The default classpath: loads the JAR-bundled hazelcast-client.yaml; only set a file: path (e.g. file:/etc/app/) when externalizing the config to a mounted volume.

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.

Container Restarting / Healthcheck Failing

Symptom: docker ps shows the container in a restart loop or health status is unhealthy.

Check:

docker logs ft-configs-service --tail 200
docker inspect ft-configs-service --format '{{json .State.Health}}'

Solution:

  • Review the logs for startup errors (database connection, missing env vars, etc.).

  • The healthcheck has a 60-second start period — wait at least 60 seconds before investigating.

  • Verify the health endpoint is correct: http://localhost:8080/configs-service/actuator/health.

Wrong Profile Selected

Symptom: Application tries to connect to the wrong database type (e.g., MySQL errors when using Oracle).

Check:

docker exec ft-configs-service env | grep SPRING_PROFILES_ACTIVE

Solution:

  • Verify SPRING_PROFILES_ACTIVE is set to mysql or oracle in the correct .env file.

  • Ensure env_file in compose.yml points to the correct file (.env.mysql or .env.oracle).

Liquibase Migration Failure

Symptom: LiquibaseException or Migration failed in application logs during startup.

Check:

docker logs ft-configs-service --tail 200 | grep -i liquibase

Solution:

  • Check database connectivity and credentials.

  • Ensure the database user has sufficient privileges to create/alter tables.

  • If upgrading from a previous version, check that the database schema is not corrupted. Restore from backup if necessary.

Bootstrap Admin Not Created

Symptom: Cannot log in after first startup. No admin account exists.

Check:

docker exec ft-configs-service env | grep FT_CONFIGS_BOOTSTRAP

Solution:

  • FT_CONFIGS_BOOTSTRAP_ADMIN_PASSWORD must be set to a non-empty value before the first startup.

  • If the password was empty, set it in .env, remove the container and its database data, then start fresh:

    docker compose down -v   # WARNING: removes database volume
    docker compose up -d

Build Fails: Permission Denied (gradlew)

Symptom: ./gradlew: Permission denied when building from source.

Check:

ls -la gradlew

Solution:

  • On the host: chmod +x gradlew.

  • Ensure your source checkout preserves execute permissions.

Build Fails: GitHub Credentials / Secret Errors

Symptom: Could not resolve all dependencies or 401 Unauthorized during Gradle build.

Check:

echo $GITHUB_USERNAME
echo $GITHUB_TOKEN | head -c 5

Solution:

  • Set GITHUB_USERNAME and GITHUB_TOKEN environment variables before running ./gradlew bootJar.

  • Verify the GitHub token has read:packages scope and access to private dependencies.

CI Build Fails: JAR Not Found (Dockerfile.simple)

Symptom: COPY failed: file not found in build context when building with Dockerfile.simple in CI/CD.

Check:

ls -la build/libs/ft-configs-service.jar

Solution:

  • Dockerfile.simple is for CI/CD pipelines only — it expects a pre-built JAR at build/libs/ft-configs-service.jar.

  • Run ./gradlew bootJar before docker build -f Dockerfile.simple ..

  • For local development, use the multi-stage Dockerfile instead: docker build -t ft-configs-service:latest .

Port Reference

Port Protocol Description

8080

HTTP

HTTP connector (REST API, Actuator, Swagger UI)

8443

HTTPS

TLS connector port (used when SERVER_SSL_ENABLED=true)

When HTTPS is enabled (SERVER_SSL_ENABLED=true and keystore settings are provided), the application can run with two connectors: HTTPS on SERVER_PORT (typically 8443) and HTTP on SERVER_HTTP_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.

Maintenance

Backup Configuration

cd /usr/local/ft-configs-service
tar -czf ft-configs-backup-$(date +%Y%m%d).tar.gz .env.* configs/

Restart Services

# Restart via compose
docker compose restart ft-configs-service

# Full restart (recreate container)
docker compose down
docker compose up -d

Stop Services

# Stop (keeps data)
docker compose down

# Stop and remove all data
docker compose down -v