Monitoring Exporters and Grafana Installation Guide

Table of Contents

Version 1.6.8 | Updated: July 24, 2026

Monitoring Exporters and Grafana Installation Guide

Introduction

This guide provides step-by-step instructions for setting up monitoring exporters and Grafana for observability across various servers. The setup includes Node Exporter, Process Exporter, cAdvisor, MySQL Exporter, Oracle DB Exporter, ClickHouse (native Prometheus endpoint), PostgreSQL Exporter, Nginx Exporter, Windows Exporter, JMX Exporter for WildFly/ACS, and a Prometheus-Grafana-Alertmanager stack deployment. Hazelcast uses its native Prometheus endpoint (no separate exporter needed). FT UI Services (Angular UI backend, AI Agent, Device Network Service, APIs) expose native Spring Boot Actuator or FastAPI metrics endpoints. Alertmanager handles alert notifications via Email, Telegram, Slack, MS Teams, Webhooks, SMS, and SNMP. Following this guide will ensure proper monitoring, alerting, and visualization of system metrics, database performance, application health, and web service performance.

Recent Updates (DEV-524 - FT UI Services Monitoring):

  • ✅ Nginx Exporter for Angular UI (portals container)

  • ✅ ClickHouse metrics via native Prometheus endpoint (port 9363)

  • ✅ PostgreSQL Exporter for AI agent database

  • ✅ Native metrics from: ft-device-network, ai-agent, ui-backend

  • ✅ Native metrics from APIs: provision-api, northbound-api, service-api

  • ✅ New Grafana dashboard: FT UI Services (comprehensive monitoring for all components)

System Requirements

Minimum per exporter node:

  • OS: Ubuntu 20.04+, Debian 11+, CentOS 7+, Rocky Linux 8+

  • RAM: 2GB (4GB recommended)

  • Disk: 10GB free space

  • Network: Outbound internet access (for Docker image downloads)

Prometheus/Grafana server:

  • CPU: 4 vCPU

  • RAM: 16GB

  • Disk: 100GB+ (see sizing.md for capacity planning)

Documentation:


Quick Start

ONE-COMMAND INSTALL (Recommended):

For monitored servers (exporters):

# Auto-detect services and deploy appropriate exporters:
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- PROMETHEUS_IP

# Example:
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- 65.108.67.167

# With custom instance prefix (default: DevOps):
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- 65.108.67.167 --prefix Prod

Install specific version (dev/testing):

# Install a specific version (required for testing dev branches):
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --branch 1.6.8 PROMETHEUS_IP

# Example:
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --branch 1.6.8 65.108.67.167

For Prometheus/Grafana server (monitoring stack):

# Deploy Prometheus + Grafana + Nginx proxy with interactive configuration:
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack

# Stack with specific version:
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack --branch 1.6.8

Bootstrap options:

Option Description Default Example

--prefix NAME

Instance name prefix for servers.env output

DevOps

--prefix ProdProd-acs1

--token TOKEN

GitHub token for private repository access

-

--token ghp_xxxx

--branch VERSION

Use specific version branch or tag

main

--branch 1.6.4

--version-list, -l

Show all available versions (tags + branches)

-

--version-list

--rollback [MANIFEST]

Rollback deployment changes

latest

--rollback

--list-manifests

List available rollback manifests

-

--list-manifests

--diagnose

Collect system diagnostics into a report file

-

--diagnose

--reset-password [PW]

Reset Grafana admin password

-

--reset-password "newpass"

--reset-prometheus-password [PW]

Reset Prometheus basic auth password

-

--reset-prometheus-password "newpass"

--version, -v

Show bootstrap version

-

--version

--docs, --readme

Show documentation links

-

--docs

Examples:

# List all available versions (releases and dev branches)
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | bash -s -- --version-list

# Show documentation links
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | bash -s -- --docs

# Reset Grafana admin password (if forgotten or DB persisted from previous install)
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --branch 1.6.8 --reset-password "`new_password`"

# Reset Prometheus basic auth password
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --branch 1.6.8 --reset-prometheus-password "`new_password`"

# Install specific version
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack --branch 1.6.4

The script will interactively prompt for all configuration parameters:

  • Server IP (auto-detected)

  • Prometheus/Grafana/Nginx ports

  • Grafana admin credentials

  • SMTP settings for alerts

  • And all other .env parameters

Press Enter to keep current values, or type new values to change them.

What bootstrap does (exporters):

  1. Installs Docker and Docker Compose (if not present)

  2. Clones this repository to /opt/grafana (or updates existing)

  3. Stashes local changes before update (with restore instructions)

  4. Auto-detects running services (Hazelcast, WildFly, MySQL, Oracle, etc.)

  5. Shows deployment summary with 20-second countdown

  6. Prompts for database credentials interactively (MySQL/Oracle)

  7. Deploys appropriate exporters based on detected services

  8. Configures firewall rules for Prometheus access

Auto-detected services:

Service Exporter Directory Ports

WildFly/FTACS HikariCP

jmx-wildfly

5556

WildFly/FTACS JVM (bare-metal only)

jmx-wildfly (profile jvm)

5557

Hazelcast

(native prometheus)

9101

MySQL/MariaDB

mysql

9104

Oracle DB

oracle

9161

ClickHouse

clickhouse

9363

PostgreSQL

postgres

9187

Nginx (Angular UI)

nginx

9113

FT UI Services

(native endpoints)

8383, 8084, 8881

FT APIs

(native endpoints)

8091, 9880, 8085

(always)

base

9100, 9256, 9183

Base exporters include:

  • node-exporter (9100) - system metrics

  • process-exporter (9256) - host process metrics

  • cadvisor (9183) - Docker container metrics

FT UI Services (native Spring Boot Actuator / FastAPI metrics):

  • ft-device-network (8383) - WiFi mesh management service

  • ai-agent (8084) - AI assistant (Python/FastAPI)

  • ui-backend (8881) - Angular UI backend (when actuator enabled)

FT API Services (native Spring Boot Actuator metrics):

  • provision-api (8091) - Provision Portal API

  • northbound-api (9880) - Northbound integration API

  • service-api (8085) - Service management API

Note: Bootstrap does not support Windows. For Windows servers, install Windows Exporter manually (see section 7).

Manual/Offline Installation (air-gapped servers)

For servers without internet access. Requires pre-downloaded files.

Prerequisite: Docker and Docker Compose must already be installed on the air-gapped server — /opt/grafana/misc/install-docker.sh needs internet access (OS package repositories). Use your OS vendor’s offline packages if Docker is missing.

  1. On a machine with internet: download the 1.6.8 version branch as ZIP — https://github.com/Friendly-Technologies/grafana/archive/refs/heads/1.6.8.zip (extracts as grafana-1.6.8). ⚠️ Do NOT use the default main branchmain only receives release merges and lacks the current fixes.

  2. Download all Docker images into a bundle: bash misc/download-docker-images.sh

    • ⚠️ Build the bundle on a host whose Docker uses the classic image store. Docker Desktop and Docker Engine ≥ 28 default to the containerd image store, whose docker save writes an OCI-format archive that older Docker on the target rejects with unrecognized image format on docker load. Use an x86_64 Linux host with the classic (overlay2) store, or disable containerd: /etc/docker/daemon.json{"features":{"containerd-snapshotter":false}} then systemctl restart docker and rebuild.

    • Verify the archive is classic before shipping (must contain manifest.json, not oci-layout): tar tf docker-images-bundle.tar* | grep -m1 manifest.json && echo CLASSIC_OK

    • Images are pulled for linux/amd64 by default (override: PLATFORM=linux/arm64 bash misc/download-docker-images.sh)

    • The bundle also includes the locally-built stack images (snmp-catcher, alertmanager-config-ui)

    • Grafana plugin packages are downloaded into docker-images-bundle-grafana-plugins/ (Grafana installs plugins from grafana.com at container start, which fails air-gapped)

    • Preview without downloading: bash misc/download-docker-images.sh --list-only

  3. Transfer files to the server: scp grafana.zip docker-images-bundle.tar.gz user@server:/tmp/ && scp -r docker-images-bundle-grafana-plugins user@server:/tmp/

  4. On the target server:

# Load Docker images
sudo docker load -i /tmp/docker-images-bundle.tar.gz

# Extract repository and deploy exporters
cd /opt && sudo unzip /tmp/grafana.zip && sudo mv grafana-* grafana
cd grafana && sudo bash /opt/grafana/misc/quick-deploy.sh PROMETHEUS_IP  # Auto-detects services

What this does:

  1. Loads pre-downloaded Docker images into the local Docker storage

  2. Deploys exporters from local images (no internet required)

  3. Configures firewall rules for Prometheus access

Monitoring stack node (Prometheus/Grafana) offline: once the image bundle is loaded (docker load), install the stack — no manual section-8 steps needed. Pick the mode:

# Interactive (recommended for a manual install) — runs the configurator and
# PROMPTS for GRAFANA_DOMAIN, admin/prometheus passwords, SMTP, etc.:
sudo bash /opt/grafana/misc/bootstrap.sh stack

# Non-interactive (for curl|bash / headless) — asks NOTHING. It auto-derives
# GRAFANA_DOMAIN from the server IP (self-signed cert) and generates random
# admin/prometheus passwords, printed in the final summary:
sudo bash /opt/grafana/misc/bootstrap.sh stack -y

Both use localhost for Prometheus. -y never ships the .env CHANGE_ME placeholders — the must-have values (IP/domain, passwords, service token) are filled automatically; only optional bits (SMTP, Business-dashboard DB) stay unset.

With Docker already present and the repo in place, bootstrap skips the internet-only steps (Docker install, git clone, Let’s Encrypt) and does the rest offline: creates .env from .env.example, generates a self-signed cert + htpasswd + the alertmanager placeholder, starts all containers from the pre-loaded images, deploys the base exporters, and generates targets. It also auto-stages the bundled Grafana plugins if docker-images-bundle-grafana-plugins/ sits next to /opt/grafana or in /tmp — the Grafana entrypoint then installs them from disk and skips grafana.com. (If you keep the plugin zips elsewhere, stage them manually first: cp …​/grafana-plugins/*.zip /opt/grafana/prometheus-grafana-stack/grafana/data/offline-plugins/.)

For Oracle deployments, also extract the Oracle datasource plugin (normally downloaded by bootstrap from GitHub):

sudo mkdir -p /opt/grafana/prometheus-grafana-stack/grafana/data/plugins/albertowd-oraclegrafana-datasource
sudo tar -xzf /tmp/docker-images-bundle-grafana-plugins/albertowd-oraclegrafana-datasource.tar.gz \
     -C /opt/grafana/prometheus-grafana-stack/grafana/data/plugins/albertowd-oraclegrafana-datasource

Tips:

  • TROUBLESHOOTING: Run sudo bash /opt/grafana/misc/diagnose.sh to automatically check Docker, containers, ports, firewall, and Prometheus targets

  • Environment Configuration: All deployments use environment variables (.env files) for secure and flexible configuration

Pre-Deployment Checklist

Before starting, gather the following information:

  • Prometheus Server IP - Will be used in firewall rules on all nodes

  • SMTP Credentials (optional) - For Grafana alerting (host, port, user, password)

  • MySQL Credentials - If deploying MySQL exporter (user, password)

  • Oracle Credentials - If deploying Oracle exporter (user, password)

  • Server IPs - All servers that will be monitored

For clarity in these instructions, we use the following servers:

  • acs1, acs2, hc1, hc2, db, iis

In this example, Prometheus is installed on the same server as the DB. You need to know its IP address during setup to whitelist it on the servers where the exporters are installed:

ACS1 - 65.109.58.164
ACS2 - 65.109.58.100
HC1  - 65.109.24.154
HC2  - 65.108.67.167
DB   - 65.109.58.165 <- PROMETHEUS_IP
IIS  - 65.109.49.150

1. Install Docker and Docker Compose on All Linux Servers

Target servers: acs1, acs2, hc1, hc2, db, *-api servers

Before installing exporters, ensure that Docker and Docker Compose are installed on all target servers (except Windows-based servers).

Use the automated installation script:

# If repository is already cloned:
cd /opt/grafana
sudo bash /opt/grafana/misc/install-docker.sh

# Or via one-command bootstrap (installs Docker automatically):
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- PROMETHEUS_IP

The script automatically:

  • ✅ Detects your OS and package manager

  • ✅ Fixes EOL repos (CentOS 7/8)

  • ✅ Installs Docker CE from official repository

  • ✅ Installs Docker Compose plugin (falls back to static binary if needed)

  • ✅ Enables and starts Docker service

  • ✅ Verifies installation

Verify Installation

docker -v
docker compose version

Expected output (versions may vary):

Docker version 28.0.1, build 068a01e
Docker Compose version v2.33.1

Manual Installation (if script fails)

Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update && sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
CentOS/Rocky/AlmaLinux:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker

2. Install Node Exporter and Process Exporter

Target servers: acs1, acs2, hc1, hc2, db, *-api servers

Use the automated deployment script with service auto-detection:

# Auto-detect services and deploy (recommended):
cd /opt/grafana
sudo bash /opt/grafana/misc/quick-deploy.sh PROMETHEUS_IP

# Examples:
sudo bash /opt/grafana/misc/quick-deploy.sh 65.108.67.167

# Or deploy specific exporters manually (--exporter flag):
sudo bash /opt/grafana/misc/quick-deploy.sh 65.108.67.167 --exporter base --exporter jmx-wildfly
sudo bash /opt/grafana/misc/quick-deploy.sh 65.108.67.167 --exporter base --exporter mysql

# Or use one-command bootstrap (installs Docker, clones repo, deploys):
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- 65.108.67.167

--exporter flag: Specifies which exporters to deploy from exporters/ folder. Use base (always required) plus service-specific: jmx-wildfly, mysql, oracle. Note: Hazelcast uses native Prometheus endpoint (port 9101) - no separate exporter needed. The jmx-wildfly directory holds two services: the HikariCP exporter (5556) starts always, while the JVM exporter (5557) sits behind the jvm compose profile. Docker FTACS has a built-in javaagent on 5557, so quick-deploy enables that profile only on bare-metal installations.

Service Auto-Detection: The script automatically detects running services and deploys appropriate exporters:

Detected Service Exporter Directory Detection Method

WildFly/FTACS

jmx-wildfly (JVM exporter via profile jvm)

jboss-modules.jar, -Djboss.home.dir

Hazelcast

(native prometheus)

HazelcastMemberStarter, hazelcast-*.jar

MySQL/MariaDB

mysql

mysqld process, systemd service

Oracle DB

oracle

ora_pmon_* process

ClickHouse

clickhouse

clickhouse-server process, systemd service

PostgreSQL

postgres

postgres process, systemd service

Nginx (Angular UI)

nginx

Docker container portals, nginx process on port 8880

FT Device Network

(native endpoint)

Docker container ft-device-network

AI Agent

(native endpoint)

Docker container ui-ai-agent

UI Backend

(native endpoint)

Docker container ui-backend

Provision API

(native endpoint)

Docker container provision-api

Northbound API

(native endpoint)

Docker container northbound-api

Service API

(native endpoint)

Docker container service-api

(always)

base

Always deployed (node + process + cadvisor)

The script automatically:

  • ✅ Detects running services on the server

  • ✅ Shows deployment summary with confirmation prompt

  • ✅ Prompts for database credentials if needed (MySQL/Oracle)

  • ✅ Deploys Node Exporter (port 9100) - system metrics

  • ✅ Deploys Process Exporter (port 9256) - per-process metrics

  • ✅ Deploys service-specific exporters based on detection

  • ✅ Configures firewall rules with persistence

  • ✅ Verifies deployment

Manual Installation (click to expand)

If you prefer manual installation or need to customize the setup:

2.0 Optional: Change Exporter Ports

All exporters are configured to run in network_mode: host (they bind directly to host network). By default, they use these ports:

Exporter Default Port Purpose

Node Exporter

9100

System metrics

Process Exporter

9256

Host process metrics

cAdvisor

9183

Docker container metrics

MySQL Exporter

9104

MySQL database metrics

Oracle DB Exporter

9161

Oracle database metrics

ClickHouse (native)

9363

ClickHouse database metrics

PostgreSQL Exporter

9187

PostgreSQL database metrics

Nginx Exporter

9113

Nginx web server metrics

JMX Exporter

5556

JMX metrics (Hikari)

JMX Exporter

5557

JVM metrics

If you need to override these ports (e.g., due to conflicts or multi-instance deployment):

  • Uncomment the relevant --web.listen-address=…​ or command: […​] line in docker-compose.yml.

  • Update firewall and Prometheus scrape configs accordingly.

2.1 Get the Repository

We have two options for obtaining the necessary files: download the repository or clone it.

Option A: Download ZIP
  1. Open the repo Friendly-Technologies/grafana in your browser.

  2. Click Code and select Download ZIP.

  3. Copy grafana-main.zip to the /opt directory on the server and extract it:

cd /opt && unzip grafana-main.zip -d /opt/grafana
Option B: Clone with Git

Run the following commands in the shell console:

cd /opt
git clone https://github.com/Friendly-Technologies/grafana.git

Enter your username and password if prompted (make sure you have access to this repo).

2.2 File Structure

After downloading or cloning, you should have the following file structure:

/opt
└── grafana
    ├── exporters/                    # Modular exporter directories
    │   ├── base/                     # Node + Process + cAdvisor (all servers)
    │   │   ├── docker-compose.yml
    │   │   ├── process-exporter.yml
    │   │   └── .env.example
    │   ├── jmx-wildfly/             # HikariCP exporter (5556) + JVM exporter
    │   │                            #   (5557, compose profile 'jvm', bare-metal only)
    │   │   ├── docker-compose.yml
    │   │   ├── config-hikari.yml
    │   │   ├── config-jvm.yml
    │   │   └── .env.example
    │   ├── mysql/                    # MySQL/MariaDB exporter
    │   │   ├── docker-compose.yml
    │   │   └── .env.example
    │   ├── oracle/                   # Oracle DB exporter
    │   │   ├── docker-compose.yml
    │   │   ├── config.yaml.template
    │   │   ├── generate-config.sh
    │   │   └── .env.example
    │   ├── clickhouse/               # ClickHouse native Prometheus endpoint (no exporter container)
    │   │   ├── clickhouse-prometheus.xml   # config.d snippet (port 9363)
    │   │   └── install-native.sh           # installs the config + optional restart
    │   ├── postgres/                 # PostgreSQL exporter
    │   │   ├── docker-compose.yml
    │   │   └── .env.example
    │   └── nginx/                    # Nginx exporter (for Angular UI)
    │       ├── docker-compose.yml
    │       └── .env.example
    ├── prometheus-grafana-stack/     # Monitoring stack
    │   ├── docker-compose.yml
    │   ├── .env.example
    │   └── prometheus/
    │       ├── prometheus.yml
    │       ├── scripts/
    │       │   └── generate-targets.sh
    │       └── targets/
    │           └── servers.env.example
    ├── docs/
    │   └── architecture.md           # Architecture diagrams
    ├── misc/
    │   ├── bootstrap.sh              # One-command installer
    │   ├── quick-deploy.sh           # Auto-detect and deploy exporters
    │   ├── detect-services.sh        # Service auto-detection
    │   ├── diagnose.sh               # Troubleshooting tool
    │   └── install-docker.sh         # Docker installer
    ├── README.md
    └── sizing.md                     # Capacity planning guide

2.3 Deploy Exporters

The deployment automatically detects running services and deploys appropriate exporters:

# Auto-detect services and deploy (recommended)
cd /opt/grafana
sudo bash /opt/grafana/misc/quick-deploy.sh 65.109.58.165

For manual deployment, navigate to the relevant exporter directory:

# Base exporters (Node + Process) - all servers
cd /opt/grafana/exporters/base

# Hazelcast: No exporter needed - uses native Prometheus endpoint (port 9101)
# See section "Prerequisites: Enable Prometheus on Hazelcast Servers"

# JMX exporters for WildFly/FTACS
cd /opt/grafana/exporters/jmx-wildfly
  1. Configure environment variables: Copy the example environment file and edit it with your configuration:

cp .env.example .env
vi .env

The .env file contains configuration for exporter versions, ports, and other settings. Most values have sensible defaults and can be left as-is. Key variables:

  • NODE_EXPORTER_PORT - Default: 9100

  • PROCESS_EXPORTER_PORT - Default: 9256

  • JMX_HIKARI_PORT - Default: 5556 (for ACS servers)

  • JMX_JVM_PORT - Default: 5557 (for ACS servers) Note: If you don’t create a .env file, the default values will be used automatically.

    1. Start the exporters using Docker Compose:

docker compose up -d

Hint: You can check the logs of the started containers:

docker compose logs -f
  1. Enable Prometheus to access exporter ports:

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9100 -j ACCEPT   # node-exporter
iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9256 -j ACCEPT   # process-exporter
iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9183 -j ACCEPT   # cadvisor
# JMX ports (if WildFly detected):
iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 5556 -j ACCEPT   # jmx-hikari
iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 5557 -j ACCEPT   # jmx-jvm
# Hazelcast native Prometheus (if Hazelcast detected):
iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9101 -j ACCEPT   # hazelcast native

In our example we took ip address of prometheus and commands should be looking like that:

iptables -A INPUT -p tcp -s 65.109.58.165 --dport 9100 -j ACCEPT   # node-exporter
iptables -A INPUT -p tcp -s 65.109.58.165 --dport 9256 -j ACCEPT   # process-exporter
iptables -A INPUT -p tcp -s 65.109.58.165 --dport 9183 -j ACCEPT   # cadvisor
# JMX ports (if WildFly detected):
iptables -A INPUT -p tcp -s 65.109.58.165 --dport 5556 -j ACCEPT   # jmx-hikari
iptables -A INPUT -p tcp -s 65.109.58.165 --dport 5557 -j ACCEPT   # jmx-jvm
# Hazelcast native Prometheus (if Hazelcast detected):
iptables -A INPUT -p tcp -s 65.109.58.165 --dport 9101 -j ACCEPT   # hazelcast native

3. Install JMX Exporter (WildFly/ACS Servers)

Target servers:

  • acs1, acs2 - WildFly/FTACS application servers

Note: Hazelcast servers use native Prometheus endpoint (port 9101) - no JMX exporter needed. See “Manual: Enable Prometheus on Hazelcast” below.

Skip this section if: Auto-detection already deployed JMX exporters.

JMX Exporter Types

Server Type Exporter Directory Ports Metrics

WildFly/FTACS

jmx-wildfly (JVM part: profile jvm)

5556 / 5557

HikariCP pool / JVM heap/GC/threads

Hazelcast

(native prometheus)

9101

Cluster members, partitions, maps, JVM

Quick Installation

cd /opt/grafana

# Auto-detect (recommended) - deploys appropriate exporters based on detected services
sudo bash /opt/grafana/misc/quick-deploy.sh 65.108.67.167

# Or deploy specific exporters manually:
# For WildFly/FTACS servers:
sudo bash /opt/grafana/misc/quick-deploy.sh 65.108.67.167 --exporter base --exporter jmx-wildfly

Note: The --exporter flag specifies which exporters to deploy from exporters/ folder.

  • base - always required (node-exporter + process-exporter + cAdvisor)

  • jmx-wildfly, mysql, oracle - service-specific exporters

  • Hazelcast uses native Prometheus endpoint (port 9101) - no separate exporter needed

Verify JMX Exporters

# Check containers are running
docker ps | grep jmx

# Test metrics endpoints
# WildFly:
curl -s http://localhost:5556/metrics | head  # HikariCP
curl -s http://localhost:5557/metrics | head  # JVM

# Hazelcast:
curl -s http://localhost:9101/metrics | head  # Hazelcast

Important: JMX ports are service-specific — do NOT mix them:

  • FTACS/WildFly JMX: port 9999 (configured in standalone.conf)

  • Hazelcast JMX: port 9110 (configured via HZ_JMX_INTERNAL_PORT env var)

  • Hazelcast Prometheus: port 9101 (native endpoint, no JMX exporter needed)

JMX Exporters (ports 5556/5557) connect to FTACS JMX on port 9999 only. Hazelcast uses native Prometheus endpoint (port 9101) — no JMX connection needed.

Manual: Enable JMX on WildFly/ACS (port 9999)

Before JMX exporters can collect metrics, WildFly/ACS (NOT Hazelcast) must be configured to expose JMX on port 9999. One-time configuration on each ACS server.

Step 1: Add to /usr/local/FTACS/bin/standalone.conf:

JAVA_OPTS="$JAVA_OPTS \
    -Dcom.sun.management.jmxremote \
    -Dcom.sun.management.jmxremote.port=9999 \
    -Dcom.sun.management.jmxremote.rmi.port=9999 \
    -Dcom.sun.management.jmxremote.ssl=false \
    -Dcom.sun.management.jmxremote.authenticate=false \
    -Djava.rmi.server.hostname=127.0.0.1"

Step 2: Enable HikariCP MBeans in /usr/local/FTACS/standalone/configuration/ftacs_hikari.properties:

registerMbeans=true

Step 3: Restart ACS:

# For MySQL:
service jboss restart

# For Oracle:
service jboss restartoracle

Tip: Check current DB type: ps aux | grep -o '\-Dftacs.usedDBMS=[^ ]*'

Verify: ss -tlnp | grep 9999

Manual: Enable Prometheus on Hazelcast

Hazelcast uses native Prometheus endpoint on port 9101. The quick-deploy.sh script can configure this automatically for native installations.

Native installation (systemd/init.d):

Step 1: Add to /usr/local/hazelcast-5.0/bin/hz-start:

export PROMETHEUS_PORT=9101

Step 2: Restart Hazelcast: systemctl restart hazelcast5

Hazelcast in Docker:

If Hazelcast runs in a Docker container, add the following to your docker run script:

  -e PROMETHEUS_PORT=9101 \   # Enable native Prometheus metrics
  -p 9101:9101 \              # Expose metrics port to host

Then restart the container.

Note: No separate JMX exporter is needed for Hazelcast — it exposes metrics natively on port 9101. JMX exporters (ports 5556/5557) are only for WildFly/FTACS.

Verify: curl -s http://localhost:9101/metrics | head


4. Install MySQL Exporter

Target server: db

Use the automated deployment script:

# If you already have Docker installed and repository cloned:
cd /opt/grafana
sudo bash /opt/grafana/misc/quick-deploy.sh 65.109.58.165 --exporter base --exporter mysql

# Or use one-command bootstrap (auto-detects MySQL):
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- 65.109.58.165

Important: Before running the script, create the MySQL exporter user (see step 1 below).

Manual Installation (click to expand)

Run the following commands in the shell console on the target server:

4.0 Optional: Change Exporter Port

By default, MySQL Exporter listens on port 9104. To override it (e.g., due to a conflict or multiple exporters):

  1. Open /opt/grafana/exporters/mysql/docker-compose.yml

  2. Uncomment and modify the --web.listen-address=:PORT parameter in the command: block:

# Uncomment to override default port (9104)
# - '--web.listen-address=:19104'

4.1 Setup Steps

  1. Create an exporter user in MySQL: Log in as root or admin and run the following commands (generate a secure password and replace StR0nG_PassW0rD_hErE accordingly):

CREATE USER IF NOT EXISTS 'exporter'@'%' IDENTIFIED BY 'StR0nG_PassW0rD_hErE' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
  1. Clone or download this repo into /opt/grafana, in the same way as described in step 2.

  2. Configure environment variables: Navigate to the MySQL exporter directory and create the .env file:

cd /opt/grafana/exporters/mysql/
cp .env.example .env
vi .env

Update the following variables with your MySQL credentials:

# MySQL Connection Details (REQUIRED)
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=exporter
MYSQL_PASSWORD=StR0nG_PassW0rD_hErE  # Replace with your password

Optional variables (can be left as default):

  • MYSQLD_EXPORTER_VERSION - Default: latest

  • MYSQLD_EXPORTER_PORT - Default: 9104

    1. Start the exporter using Docker Compose:

docker compose up -d
  1. Enable Prometheus to access exporter ports:

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9104 -j ACCEPT

5. Install Oracle DB Exporter

Target server: oracle-db or any server with Oracle Database access

Use the automated deployment script:

# If you already have Docker installed and repository cloned:
cd /opt/grafana
sudo bash /opt/grafana/misc/quick-deploy.sh 65.109.58.165 --exporter base --exporter oracle

# Or use one-command bootstrap (auto-detects Oracle):
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- 65.109.58.165

Note: Bootstrap and quick-deploy will prompt for Oracle credentials interactively.

Manual Installation (click to expand)

Run the following commands in the shell console on the target server:

5.1 Setup Oracle DB Exporter

  1. Clone or download this repo into /opt/grafana, in the same way as described in step 2.

  2. Navigate to the Oracle exporter directory:

cd /opt/grafana/exporters/oracle/
  1. Configure environment variables: Create the .env file with your Oracle Database credentials:

cp .env.example .env
vi .env

Update the following variables:

# Oracle Database Connection (REQUIRED)
ORACLE_HOST=localhost
ORACLE_PORT=1521
ORACLE_SERVICE_NAME=ORCL
ORACLE_USER=system
ORACLE_PASSWORD=your_oracle_password

Optional variables (can be left as default):

  • ORACLE_EXPORTER_VERSION - Default: 2.2.0

  • ORACLE_EXPORTER_PORT - Default: 9161

    1. Generate config.yaml from template:

./generate-config.sh

This script reads .env and generates config.yaml with substituted values. . Start the exporter using Docker Compose:

docker compose up -d

Hint: You can check the logs of the started container:

docker compose logs -f
  1. Verify the exporter is working:

curl http://localhost:9161/metrics

You should see Oracle Database metrics including:

  • oracledb_sessions_value - Database sessions

  • oracledb_tablespace_bytes - Tablespace usage

  • oracledb_activity_* - Database activity metrics

  • oracledb_resource_* - Resource utilization

    1. Enable Prometheus to access exporter port:

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9161 -j ACCEPT

In our example:

iptables -A INPUT -p tcp -s 65.109.58.165 --dport 9161 -j ACCEPT

Remote Database Monitoring

If your database runs on a separate server (e.g., Windows server, cloud RDS, or a different network segment) where you cannot install Docker exporters, you can run the exporter on any Linux server that has network access to the database.

Step 1: On the Linux server (where exporter will run), set the host to the remote database IP:

cd /opt/grafana/exporters/oracle
cp .env.example .env
vi .env
ORACLE_HOST=<database-server-ip>    # Remote database server IP (not localhost)
ORACLE_PORT=1521
ORACLE_SERVICE_NAME=<service-name>
ORACLE_USER=<username>
ORACLE_PASSWORD=<password>

Step 2: Generate config and start:

cd /opt/grafana/exporters/oracle
bash /opt/grafana/exporters/oracle/generate-config.sh
docker compose up -d

Step 3: Verify exporter connects to remote database:

curl http://localhost:9161/metrics | grep oracledb_up
# Expected: oracledb_up 1

Step 4: Add to Prometheus targets. Since the exporter runs locally, use 127.0.0.1:

cd /opt/grafana/prometheus-grafana-stack/prometheus/targets
vi servers.env
# Add line:
ORACLE 127.0.0.1:9161 <instance-name>

# Regenerate targets:
bash /opt/grafana/prometheus-grafana-stack/prometheus/scripts/generate-targets.sh

Note: This approach works for any database exporter (MySQL, PostgreSQL, ClickHouse, Oracle). The exporter connects to the database over the network — only the database port (e.g., 1521, 3306) needs to be open between the exporter and the database server. No Docker or exporter installation is needed on the database server itself.

Firewall: Ensure the database port is open from the exporter server to the database server. No need to open exporter port (9161) externally — Prometheus scrapes it locally.

Mixed Deployment (Some Services on Same Server as Grafana)

When some monitored services run on the same server as Grafana/Prometheus but in a separate Docker Compose stack, those containers may be in a different Docker network. Prometheus runs in network_mode: host and cannot always reach containers in bridge networks through docker-proxy (connections hang/timeout).

Services on other servers are not affected — standard port mapping works fine over the network.

How to identify the problem:

Solution: Switch only the affected service to network_mode: host. Services on other servers keep their normal networking.

Example: Hazelcast on same server as Grafana

Before (bridge network — metrics not accessible from Prometheus):

hazelcast:
    image: hub.friendly-tech.com/components/hazelcast:ft.5.5.0
    ports:
      - "5701:5701"
      - "5711:5711"
      - "9101:9101"
    environment:
      PROMETHEUS_PORT: 9101

After (host network — metrics accessible on localhost):

hazelcast:
    image: hub.friendly-tech.com/components/hazelcast:ft.5.5.0
    network_mode: host
    environment:
      PROMETHEUS_PORT: 9101

Important changes when switching a service to network_mode: host:

  • Remove ports: section for that service (not needed — all ports are on host directly)

  • Replace container hostname references with 127.0.0.1 in environment variables:

  • HZ_MEMBERS=hazelcast:5701HZ_MEMBERS=127.0.0.1:5701

  • HZ_PUBLIC_ADDRESS=hazelcast:5711HZ_PUBLIC_ADDRESS=127.0.0.1:5711

  • Other containers that connect to this service by Docker DNS name must also be updated to use 127.0.0.1 or the host IP

Prometheus target configuration:

For services on the same server — use 127.0.0.1:

JMX_HC 127.0.0.1:9101 <instance-name>

For services on other servers — use their IP as usual:

JMX_HC <server-ip>:9101 <instance-name>

Verify:

# Should return metrics immediately (no hang)
curl -s http://localhost:9101/metrics | head

Note: Only services co-located with Grafana/Prometheus need this change. Services on separate servers work with standard bridge networking and port mapping.

Note: This issue occurs because Prometheus uses network_mode: host and docker-proxy between host and bridge networks can be unreliable on some kernel/Docker versions.


6. Install FT UI Services Monitoring (DEV-524)

Target server: hc1 (65.109.24.154) - Angular UI services server

This section covers monitoring for the Angular UI services stack deployed via Docker Compose.

Components Monitored

Service Type Port Metrics Path Description

portals (nginx)

Web server

8880/8843

via exporter

Angular UI frontend

ui-backend

Spring Boot

8881

/actuator/prometheus

Angular backend API

ft-device-network

Spring Boot

8383

/api/actuator/prometheus

WiFi mesh service

ui-ai-agent

Python/FastAPI

8084

/metrics

AI assistant

clickhouse

Database

9363

/metrics (native endpoint)

Analytics database

postgres

Database

5432

via exporter

AI agent database

The bootstrap script automatically detects FT UI services when run on HC1:

# Auto-detect and deploy all exporters (recommended):
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- PROMETHEUS_IP

# Or if repository already cloned:
cd /opt/grafana
sudo bash /opt/grafana/misc/quick-deploy.sh PROMETHEUS_IP

The script will automatically deploy:

  • nginx-exporter (port 9113) - for Angular UI (portals container)

  • ClickHouse native Prometheus endpoint (port 9363) - for ClickHouse database

  • postgres-exporter (port 9187) - for PostgreSQL database

Native endpoints (no exporter needed):

  • ft-device-network (port 8383) - Spring Boot Actuator

  • ai-agent (port 8084) - FastAPI with prometheus_client

  • ⚠️ ui-backend (port 8881) - Requires Spring Security configuration (see below)

Manual Installation

Nginx Exporter (for Angular UI portals)

Step 1: Enable nginx stub_status in portals container

The portals nginx must expose /nginx_status endpoint. Add the following block to the nginx config inside the portals container:

location /nginx_status {
    stub_status on;
    access_log off;
    allow 127.0.0.1;
    deny all;
}

How to find and edit the config:

# Check where nginx config is located inside the container
docker exec portals nginx -T 2>/dev/null | head -5

# Common locations:
#   /etc/nginx/conf.d/default.conf
#   /etc/nginx/nginx.conf

# View the current config
docker exec portals cat /etc/nginx/conf.d/default.conf

# Edit: copy config out, add the location block, copy back
docker cp portals:/etc/nginx/conf.d/default.conf ./default.conf
# ... add the location /nginx_status block inside the server { } section ...
docker cp ./default.conf portals:/etc/nginx/conf.d/default.conf
docker exec portals nginx -s reload

Verify stub_status is working:

docker exec portals curl -s http://127.0.0.1/nginx_status
# Expected output:
#   Active connections: 5
#   server accepts handled requests
#
1234 1234 5678
#   Reading: 0 Writing: 1 Waiting: 4

Note: This change needs to be persisted in the portals Docker image or compose volume mount, otherwise it will be lost on container restart.

Step 2: Deploy nginx-exporter

cd /opt/grafana/exporters/nginx
cp .env.example .env
vi .env

Update:

NGINX_HOST=localhost  # or container name if using Docker network
NGINX_PORT=8880
NGINX_EXPORTER_PORT=9113

Step 3: Start exporter

docker compose up -d

Step 4: Configure firewall

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9113 -j ACCEPT

Verify:

curl http://localhost:9113/metrics | grep nginx_

ClickHouse metrics (native Prometheus endpoint)

ClickHouse exposes Prometheus metrics natively — no exporter container. The old f1yegor/clickhouse-exporter is not used: it crash-loops on modern ClickHouse (21.8+) because it does not sanitise block-device async metric names (clickhouse_block_discard_ops_dm-4 is not a valid Prometheus metric name).

Step 1: Enable the native endpoint on the ClickHouse host (port 9363)

sudo bash /opt/grafana/exporters/clickhouse/install-native.sh --restart

This drops clickhouse-prometheus.xml into /etc/clickhouse-server/config.d/ and restarts clickhouse-server. Omit --restart to install the config and restart the DB yourself later. The endpoint binds to ClickHouse’s configured <listen_host> — if ClickHouse already answers on the server IP (HTTP 8123), 9363 binds there too.

Step 2: Configure firewall

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9363 -j ACCEPT

Step 3: Add to the Prometheus server’s servers.env, then run generate-targets.sh

CLICKHOUSE  <server-ip>:9363  <instance>  role=database

Verify (native metric names, not clickhouse_*):

curl -s http://localhost:9363/metrics | grep -E '^ClickHouse(Metrics|ProfileEvents|AsyncMetrics)_'

PostgreSQL Exporter

Step 1: Create exporter user in PostgreSQL

CREATE USER exporter WITH PASSWORD 'your_secure_password';
GRANT pg_monitor TO exporter;
GRANT CONNECT ON DATABASE ftl_ai TO exporter;

Step 2: Ensure PostgreSQL accepts connections

In docker-compose.yml for ft-ui-services, expose port 5432:

postgres:
  ports:
    - "5432:5432"

Step 3: Deploy postgres-exporter

cd /opt/grafana/exporters/postgres
cp .env.example .env
vi .env

Update:

POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=exporter
POSTGRES_PASSWORD=your_secure_password
POSTGRES_DB=ftl_ai
POSTGRES_EXPORTER_PORT=9187

Step 4: Start exporter

docker compose up -d

Step 5: Configure firewall

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 9187 -j ACCEPT

Verify:

curl http://localhost:9187/metrics | grep pg_

Native Metrics Endpoints

These services expose Prometheus metrics natively - no separate exporter needed:

FT Device Network Service (Spring Boot Actuator)

Port: 8383 Path: /api/actuator/prometheus Type: Spring Boot Actuator

Metrics are enabled by default with micrometer-prometheus dependency.

Verify:

curl http://localhost:8383/api/actuator/prometheus | grep jvm_

Add to Prometheus (servers.env):

FT_DEVICE_NETWORK 65.109.24.154:8383 DevOps-HC1

Configure firewall:

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 8383 -j ACCEPT

AI Agent (Python/FastAPI)

Port: 8084 Path: /metrics Type: FastAPI with prometheus_client middleware

Metrics are exposed using prometheus_client library.

Expose the metrics port — the default ft-ui-services compose does not publish the AI Agent port to the host. Add a port mapping to the ui-ai-agent service in the ft-ui-services compose file and recreate the container:

  ui-ai-agent:
    ports:
      - "8084:8080"
docker compose up -d ui-ai-agent

Verify:

curl http://localhost:8084/metrics | grep python_

Add to Prometheus (servers.env):

AI_AGENT 65.109.24.154:8084 DevOps-HC1

Configure firewall:

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 8084 -j ACCEPT

UI Backend (Spring Boot Actuator) - Optional

Port: 8881 Path: /actuator/prometheus Type: Spring Boot Actuator

⚠️ Important: UI Backend requires Spring Security configuration to allow /actuator/** endpoints.

Prerequisites:

Add to Spring Security configuration:

.requestMatchers("/actuator/**").permitAll()

Or update application.properties:

management.endpoints.web.exposure.include=prometheus,health,info

Verify:

curl http://localhost:8881/actuator/prometheus | grep jvm_

Add to Prometheus (servers.env):

UI_BACKEND 65.109.24.154:8881 DevOps-HC1

Configure firewall:

iptables -A INPUT -p tcp -s $PROMETHEUS_IP --dport 8881 -j ACCEPT

Grafana Dashboard

The FT UI Services dashboard (ft-ui-services.json) provides monitoring for all components:

  • Nginx (Portals) - Active connections, requests/sec, response times

  • ClickHouse - Queries, connections, disk usage, replication lag

  • PostgreSQL - Connections, transactions, cache hit ratio, replication

  • FT Device Network - JVM heap, threads, HTTP requests, API latency

  • AI Agent - Python memory, request rate, response times

  • UI Backend - JVM metrics, HTTP requests, database connections (when enabled)

Dashboard is auto-provisioned at: Dashboards → Extended → FT UI Services


7. Install Windows Exporter

Target server: iis

Note: Windows Exporter installation is manual only (no automated script available).

Download and Install

  1. Download from Windows Exporter Releases

  2. Run in Command Prompt (as Administrator):

lodctr.exe /E:Lsa
lodctr.exe /E:PerfProc
lodctr.exe /R

msiexec /i "C:\Users\Administrator\windows_exporter-0.31.3-amd64.msi" ENABLED_COLLECTORS=ad,adfs,cache,cpu,cpu_info,container,dfsr,dhcp,dns,fsrmquota,iis,logical_disk,memory,msmq,mssql,net,os,process,remote_fx,service,tcp,time,vmware LISTEN_PORT=9100 EXTRA_FLAGS="--collector.process.include=""(w3wp|java.*)"" --collector.process.iis --collector.process.counter-version=2"

Configure Firewall

netsh advfirewall firewall add rule name="Allow Windows Exporter" dir=in action=allow protocol=TCP localport=9100

Verify

sc query windows_exporter

Add to Monitoring

After Windows Exporter is running, add the server to servers.env on the Prometheus server:

# === IIS SERVERS ===
NODE <server-ip>:9100 <Prefix>-IIS service=IIS,role=webserver,node_ip=<server-ip>

Then regenerate targets:

cd /opt/grafana/prometheus-grafana-stack/prometheus
/opt/grafana/prometheus-grafana-stack/prometheus/scripts/generate-targets.sh

Note: Windows Exporter uses the NODE type since it exposes metrics on the same port (9100) in a compatible format. The service=IIS label is used by the Windows/IIS Grafana dashboard for filtering.


8. Configure and Deploy Prometheus with Grafana

Target server: db (or separate server dedicated for Grafana only)

In this example, we are using the db server to host Grafana and Prometheus, but there is no strict requirement—it can be any server that suits your environment.

Hardware requirements: For small-scale setups (up to 10 server nodes), 4 vCPU and 8 GB RAM is sufficient. For larger deployments, see Grafana sizing guidelines.

Use the one-command bootstrap:

curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack

The script will interactively prompt for all configuration parameters in 8 phases:

Bootstrap Stack Flow
Figure 1. Bootstrap Stack Flow
Phase What is configured

1. Prerequisites

Docker, Docker Compose, clone repository

2. .env Configuration

SERVER_IP, SMTP_HOST, ALERT_EMAIL, retention, ports, FTACS_DB_TYPE (mysql/oracle), FTACS_DB_HOST/PORT/USER/PASSWORD/NAME

3. SSL/HTTPS

GRAFANA_DOMAIN + certificate type (Let’s Encrypt / self-signed / existing)

4. Alert Channels

Email, Telegram, Slack, MS Teams, Webhook, SMS, SNMP (each can be skipped)

5. UI Portal Integration

UI_PORTAL_ORIGINS — portal URLs for /grafana-ro/ embedding (skip = 403 for external)

6. Credentials

Grafana admin password, Prometheus password (auto-generate or enter)

7. Deploy

docker compose up, generate service token, verify health

8. Summary

Access URLs, credentials, servers.env entries

  • Press Enter to keep current/default value

  • Type new value to change it

  • All parameters are read from /opt/grafana/prometheus-grafana-stack/.env

The script automatically:

  • ✅ Installs Docker if not present

  • ✅ Clones/updates repository to /opt/grafana

  • ✅ Creates .env from .env.example if missing

  • ✅ Configures all parameters interactively

  • ✅ Deploys Prometheus, Grafana, and Nginx via Docker Compose

  • ✅ Shows access URLs and credentials

After deployment:

  1. Configure Prometheus targets (see section 8.1 below)

  2. Verify /grafana-ro/ works if you configured UI Portal Origins in phase 5

Manual Installation (click to expand)

  1. Clone or download this repo into /opt/grafana, as described previously.

  2. Configure environment variables: Navigate to the Prometheus-Grafana stack directory and create the .env file:

cd /opt/grafana/prometheus-grafana-stack
cp .env.example .env
vi .env

Update the following REQUIRED variables:

# Server Configuration (REQUIRED)
SERVER_IP=65.109.58.165  # Replace with your server's IP

# Grafana Admin Credentials (REQUIRED)
# NOTE: Password in .env is used only on FIRST deployment.
# After that, database is the source of truth.
# To change password later, use: docker exec grafana grafana cli admin reset-admin-password NEW_PASS
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=your_secure_password_here

# SMTP Configuration (REQUIRED for alerting)
SMTP_ENABLED=true
SMTP_HOST=smtp.example.com:587
SMTP_USER=your_smtp_user
SMTP_PASSWORD=your_smtp_password

# Grafana Service Token (REQUIRED for nginx proxy)
GRAFANA_SERVICE_TOKEN=your_token_here  # Will be generated in section 8.2

Optional variables (can be left as default):

  • PROMETHEUS_VERSION, GRAFANA_VERSION, NGINX_VERSION

  • PROMETHEUS_PORT=9090, GRAFANA_PORT=3000

  • PROMETHEUS_RETENTION=90d

  • GRAFANA_PLUGINS - Comma-separated list of plugins Full environment variables reference

Variable

Description

Required

SERVER_IP

Your server’s external IP address or hostname

✅ Yes

GRAFANA_ADMIN_USER

Admin username for Grafana authentication

✅ Yes

GRAFANA_ADMIN_PASSWORD

Admin password for Grafana authentication

✅ Yes

SMTP_HOST

SMTP server address and port for email alerts (e.g., smtp.gmail.com:587)

✅ Yes

SMTP_USER

SMTP username (usually an email address)

✅ Yes

SMTP_PASSWORD

SMTP password or API key

✅ Yes

GRAFANA_SERVICE_TOKEN

Service account token for nginx proxy (generated in section 8.2)

✅ Yes

PROMETHEUS_RETENTION

Data retention period (default: 90d)

❌ No

PROMETHEUS_PORT

Prometheus port (default: 9090)

❌ No

GRAFANA_PORT

Grafana port (default: 3000)

❌ No

For a complete list of all variables, see .env.example in each directory. Grafana Internal Environment Variables Reference: These variables are set automatically by docker-compose based on your .env file:

Variable Description

GF_SERVER_ROOT_URL

Root URL of Grafana instance, used to generate correct links in the UI

GF_SERVER_ALLOW_ORIGIN

Allowed origin for CORS (Cross-Origin Resource Sharing)

GF_LIVE_ALLOWED_ORIGINS

Allowed origins for WebSocket connections (Grafana Live)

GF_SECURITY_ADMIN_USER

Admin username for Grafana authentication

GF_SECURITY_ADMIN_PASSWORD

Admin password for Grafana authentication

GF_SMTP_ENABLED

Enables SMTP email notifications (true = enabled)

GF_SMTP_HOST

SMTP server address and port for sending alerts

GF_SMTP_USER

SMTP username (usually an email address)

GF_SMTP_PASSWORD

SMTP password or API key

GF_SECURITY_ALLOW_EMBEDDING

Allows embedding dashboards in iframes (true = enabled)

GF_DASHBOARDS_JSON_ENABLED

Enables JSON-based dashboard provisioning

GF_DASHBOARDS_CUSTOM_CSS

Custom CSS file path for UI styling

  1. Prepare generated files (the bootstrap script creates these automatically — a manual/offline installation must create them before the first start, otherwise Docker turns the missing bind-mount files into directories and the containers fail with “Are you trying to mount a directory onto a file”):

cd /opt/grafana/prometheus-grafana-stack

# Alertmanager config placeholder (regenerated by the container entrypoint)
cat > alertmanager/alertmanager.yml << 'EOF'
# Placeholder - will be regenerated by alertmanager entrypoint.sh
global:
  resolve_timeout: 5m
route:
  receiver: 'default'
receivers:
  - name: 'default'
EOF
chmod 666 alertmanager/alertmanager.yml

# TLS certificate for the nginx proxy (self-signed; replace with a real
# certificate later if you have one — files: ssl/certificate.crt, ssl/private.key).
# ssl/ is git-ignored, so it is NOT in the ZIP — create it first.
mkdir -p ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout ssl/private.key -out ssl/certificate.crt \
  -subj "/C=US/ST=State/L=City/O=FriendlyTech/CN=${GRAFANA_DOMAIN:-$(hostname -f)}"

# Prometheus basic-auth file for nginx (user: prometheus). Also git-ignored, so
# nginx would otherwise mount a directory onto it and fail. htpasswd may be
# absent on RHEL — fall back to openssl apr1; write the password back into .env.
PROM_PASS=$(openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | head -c16)
if command -v htpasswd >/dev/null 2>&1; then
  htpasswd -bc .htpasswd prometheus "$PROM_PASS"
else
  echo "prometheus:$(openssl passwd -apr1 "$PROM_PASS")" > .htpasswd
fi
grep -q '^PROMETHEUS_PASSWORD=' .env \
  && sed -i "s/^PROMETHEUS_PASSWORD=.*/PROMETHEUS_PASSWORD=$PROM_PASS/" .env \
  || echo "PROMETHEUS_PASSWORD=$PROM_PASS" >> .env
echo "Prometheus UI login: prometheus / $PROM_PASS"

# nginx ACME webroot (bind-mounted; keep it a directory)
mkdir -p acme-challenge

Also make sure GRAFANA_DOMAIN in .env is set to the server’s domain or IP — nginx serves Grafana only for that hostname. . Start the Prometheus-Grafana stack:

cd /opt/grafana/prometheus-grafana-stack
docker compose up -d
  1. Enable access to Grafana and Prometheus ports:

iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# If server has IPv6 address, also add ip6tables rules:
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT

Note: Ports 9090 (Prometheus) and 3000 (Grafana) bind to 127.0.0.1 and are only accessible through nginx reverse proxy. No need to open them in firewall.

  1. Test the setup:

  2. Configure Alerting (optional): Update the email in /opt/grafana/prometheus-grafana-stack/grafana/provisioning/alerting/alert_resources.yaml with your email(s). Multiple emails can be separated by ;, \n, or ,.


Accessing Web Interfaces

After deployment, the monitoring stack provides two web interfaces accessible via nginx reverse proxy:

Service URL Username Password

Grafana

https://<domain>/

admin

.envGRAFANA_ADMIN_PASSWORD

Prometheus

https://<domain>/prometheus

prometheus

.envPROMETHEUS_PASSWORD

Note: Both services bind to 127.0.0.1 and are only accessible through nginx. Direct access via IP:3000 or IP:9090 is not available.

Note: The “Business metrics” dashboard requires connection to your FTACS application database (MySQL or Oracle) for tenant/domain lists. Configure FTACS_DB_* variables in .env (set FTACS_DB_TYPE to mysql or oracle) — this is the ACS database, not the monitoring database.

How it works:

All external traffic goes through nginx reverse proxy (ports 80/443). Nginx handles SSL, authentication, and forwards requests to internal services:

Browser → https://<domain>/          → nginx (port 443) → Grafana (127.0.0.1:3000)
Browser → https://<domain>/prometheus → nginx (port 443) → Prometheus (127.0.0.1:9090)

Grafana and Prometheus listen only on 127.0.0.1 (localhost) — they are not reachable directly from outside. This is why only ports 80 and 443 need to be opened in the firewall, not 3000 or 9090.

How to find your passwords:

cd /opt/grafana/prometheus-grafana-stack

# View Grafana password
grep GRAFANA_ADMIN_PASSWORD .env

# View Prometheus password
grep PROMETHEUS_PASSWORD .env

# Reset Grafana password if forgotten
docker exec grafana grafana cli admin reset-admin-password NEW_PASSWORD

Grafana UI pages:

Prometheus UI pages:


8.1 Configure Prometheus Targets

After the stack is running, configure which servers Prometheus should monitor.

Tip: When you deploy exporters using quick-deploy.sh or bootstrap.sh, the script outputs ready-to-use configuration lines for servers.env. Just copy and paste them.

Step 1: Edit the server inventory

cd /opt/grafana/prometheus-grafana-stack/prometheus/targets
vi servers.env

First time setup:

  1. Run /opt/grafana/prometheus-grafana-stack/prometheus/scripts/generate-targets.sh — creates servers.env from template

  2. Edit servers.env — add your servers

  3. Run /opt/grafana/prometheus-grafana-stack/prometheus/scripts/generate-targets.sh again — generates JSON target files

Your servers.env is git-ignored and won’t be overwritten by repository updates.

Format: TYPE IP:PORT INSTANCE [EXTRA_LABELS]

Field Description Example

TYPE

Exporter type (see supported types below)

NODE, MYSQL, JMX_HIKARI

IP:PORT

Server IP and exporter port

65.109.58.165:9100

INSTANCE

Unique instance name for Grafana

DevOps-DB, Prod-ACS1

EXTRA_LABELS

Optional comma-separated Prometheus labels

service=mysql,role=database

EXTRA_LABELS are additional labels attached to all metrics from this target. Useful for:

  • Filtering in Grafana dashboards (service=mysql)

  • Grouping servers by role (role=database, role=application)

  • Environment tagging (env=prod, env=staging)

Instance Naming Convention (recommended):

Instance names are flexible - you can use any naming scheme that works for your organization. The recommended format is {Environment}-{ServerType}{Number}:

Prefix Server Role Examples

ACS

Application servers (WildFly/FTACS)

DevOps-ACS1, Prod-ACS2

DB

Database servers (MySQL, Oracle)

DevOps-DB, Prod-OracleDB

HC

Hazelcast cache servers (standalone)

DevOps-HC1, Prod-HC

API

Microservices (Provision, Northbound, Service, Subscription)

DevOps-APIs, Prod-API1

IIS

Windows/IIS web servers

DevOps-IIS, Prod-IIS1

💡 Tip: Use the role label for server grouping: role=database, role=application, role=cache, role=api, role=webserver, role=monitoring

Which exporters for which server type:

Server Type Required Exporters Optional Exporters

Any server

NODE, PROCESS, CADVISOR

-

MySQL DB

MYSQL

-

Oracle DB

ORACLE

-

ClickHouse

CLICKHOUSE

-

PostgreSQL

POSTGRES

-

WildFly/ACS

JMX_HIKARI, JMX_JVM, JMX_HC

ACS_METRICS, FT_SYSTEM

Hazelcast (standalone)

JMX_HC (native Prometheus)

-

Angular UI (HC1)

NGINX_PORTALS, FT_DEVICE_NETWORK, AI_AGENT

UI_BACKEND (requires actuator)

API servers

PROVISION_API, NORTHBOUND_API, SERVICE_API

-

FTACS servers

SUBSCRIPTION_API (native on port 8080)

-

Windows/IIS

NODE (Windows Exporter on port 9100)

-

Minimal example (one server of each type):

# MySQL database server
NODE 10.0.0.10:9100 Prod-DB
PROCESS 10.0.0.10:9256 Prod-DB
CADVISOR 10.0.0.10:9183 Prod-DB
MYSQL 10.0.0.10:9104 Prod-DB

# WildFly/ACS application server
NODE 10.0.0.20:9100 Prod-ACS1
PROCESS 10.0.0.20:9256 Prod-ACS1
CADVISOR 10.0.0.20:9183 Prod-ACS1
JMX_HIKARI 10.0.0.20:5556 Prod-ACS1
JMX_JVM 10.0.0.20:5557 Prod-ACS1

# Hazelcast cache server (standalone)
NODE 10.0.0.30:9100 Prod-HC1 role=cache
PROCESS 10.0.0.30:9256 Prod-HC1
CADVISOR 10.0.0.30:9183 Prod-HC1
JMX_HC 10.0.0.30:9101 Prod-HC1

# API server (microservices)
NODE 10.0.0.35:9100 Prod-API1 role=api
PROCESS 10.0.0.35:9256 Prod-API1
CADVISOR 10.0.0.35:9183 Prod-API1
PROVISION_API 10.0.0.35:8091 Prod-API1
NORTHBOUND_API 10.0.0.35:9880 Prod-API1
SERVICE_API 10.0.0.35:8085 Prod-API1
SUBSCRIPTION_API 10.0.0.35:8080 Prod-API1

# Oracle database server
NODE 10.0.0.40:9100 Prod-OracleDB
PROCESS 10.0.0.40:9256 Prod-OracleDB
CADVISOR 10.0.0.40:9183 Prod-OracleDB
ORACLE 10.0.0.40:9161 Prod-OracleDB

# Windows/IIS web server
NODE 10.0.0.50:9100 Prod-IIS service=IIS,role=webserver

Full example with labels and API endpoints

# === DATABASE SERVERS ===
NODE 65.109.58.165:9100 DevOps-DB service=mysql,role=database
PROCESS 65.109.58.165:9256 DevOps-DB
CADVISOR 65.109.58.165:9183 DevOps-DB
MYSQL 65.109.58.165:9104 DevOps-DB

# === ACS SERVERS ===
NODE 65.109.58.164:9100 DevOps-ACS1 service=ACS,role=application
PROCESS 65.109.58.164:9256 DevOps-ACS1
CADVISOR 65.109.58.164:9183 DevOps-ACS1
JMX_HIKARI 65.109.58.164:5556 DevOps-ACS1
JMX_JVM 65.109.58.164:5557 DevOps-ACS1
# Optional ACS endpoints:
FT_SYSTEM 65.109.58.164:8090 DevOps-ACS1
ACS_METRICS 65.109.58.164:8080 DevOps-ACS1
JMX_HC 65.109.58.164:9101 DevOps-ACS1

# === HAZELCAST SERVERS (standalone) ===
NODE 65.109.24.154:9100 DevOps-HC1 service=hazelcast,role=cache
PROCESS 65.109.24.154:9256 DevOps-HC1
CADVISOR 65.109.24.154:9183 DevOps-HC1
JMX_HC 65.109.24.154:9101 DevOps-HC1

# === API SERVERS (microservices) ===
NODE 65.109.24.155:9100 DevOps-API1 service=api,role=api
PROCESS 65.109.24.155:9256 DevOps-API1
CADVISOR 65.109.24.155:9183 DevOps-API1
PROVISION_API 65.109.24.155:8091 DevOps-API1
NORTHBOUND_API 65.109.24.155:9880 DevOps-API1
SERVICE_API 65.109.24.155:8085 DevOps-API1
SUBSCRIPTION_API 65.109.24.155:8080 DevOps-API1

# === ORACLE SERVERS ===
NODE 65.109.20.174:9100 DevOps-OracleDB service=oracle,role=database
PROCESS 65.109.20.174:9256 DevOps-OracleDB
CADVISOR 65.109.20.174:9183 DevOps-OracleDB
ORACLE 65.109.20.174:9161 DevOps-OracleDB

# === IIS SERVERS ===
NODE 65.109.49.150:9100 devops-ui.friendly-tech.com service=IIS,role=webserver,node_ip=65.109.49.150

Step 2: Generate Prometheus target files

cd /opt/grafana/prometheus-grafana-stack/prometheus
/opt/grafana/prometheus-grafana-stack/prometheus/scripts/generate-targets.sh

The script automatically:

  • ✅ Parses servers.env

  • ✅ Generates JSON files in targets/ folder (one per exporter type)

  • ✅ Adds exporter_type label to each target (e.g., node, hazelcast, hikari)

  • ✅ Validates JSON syntax

  • ✅ Checks target reachability

  • ✅ Shows summary of generated targets

Note: The script validates all entries before generating targets. If there are format errors (wrong TYPE, missing instance name, labels in instance field), it will show the exact line, what’s wrong, and how to fix it.

How it works: prometheus.yml uses file_sd_configs to read JSON files from targets/ folder. Prometheus auto-reloads within 30 seconds — no restart needed!

Adding new servers later:

  1. Edit servers.env — add new server lines

  2. Run /opt/grafana/prometheus-grafana-stack/prometheus/scripts/generate-targets.sh

  3. Done! Prometheus picks up changes automatically

Benefits:

  • Zero downtime - No Prometheus restart required

  • 🛡️ Error-proof - Cannot break YAML syntax

  • 📝 Simple format - Easy to read and edit

  • 🔄 Auto-reload - Prometheus picks up changes automatically

  • 🔒 Safe updates - Your servers.env is git-ignored and won’t be overwritten

Supported target types (TYPE field reference)

The TYPE field in servers.env determines which Prometheus job will scrape the target. Each type maps to a specific exporter and port:

TYPE Port Description Prometheus Job exporter_type

NODE

9100

System metrics (CPU, memory, disk)

node-exporter

node

PROCESS

9256

Host process metrics

process-exporter

process

CADVISOR

9183

Docker container metrics

cadvisor

cadvisor

MYSQL

9104

MySQL/MariaDB database metrics

mysql

mysql

ORACLE

9161

Oracle database metrics

oracledb

oracle

CLICKHOUSE

9363

ClickHouse database metrics (native endpoint)

clickhouse

clickhouse

POSTGRES

9187

PostgreSQL database metrics

postgres

postgres

NGINX_PORTALS

9113

Nginx web server metrics (Angular UI)

nginx

nginx

JMX_HIKARI

5556

HikariCP connection pool (WildFly)

JmxExporter

hikari

JMX_JVM

5557

JVM heap, GC, threads (WildFly)

JmxExporter

jvm

JMX_HC

9101

Hazelcast native Prometheus

JmxExporter

hazelcast

FT_SYSTEM

8090

FT System Spring Boot metrics (WildFly)

ft-system-metrics

ft-system

ACS_METRICS

8080

ACS application metrics

acs-metrics

acs-metrics

FT_DEVICE_NETWORK

8383

FT Device Network Service (Actuator)

ft-device-network

ft-device-network

AI_AGENT

8084

AI Agent Python/FastAPI metrics

ai-agent

ai-agent

UI_BACKEND

8881

UI Backend Spring Boot Actuator

ui-backend

ui-backend

PROVISION_API

8091

Provision Portal API metrics

provision-api

provision-api

NORTHBOUND_API

9880

Northbound API metrics

northbound-api

northbound-api

SERVICE_API

8085

Service API metrics

service-api

service-api

SUBSCRIPTION_API

8080

Subscription API metrics

subscription-api

subscription-api

Note: The port in the table is the default. Always specify the actual port in your servers.env line: TYPE IP:PORT INSTANCE

How IP addresses appear in Grafana dashboards

No configuration neededprometheus.yml already includes relabel_configs that extract IP addresses from target addresses and store them in the node_ip label.

Grafana dashboards use this label to display server IPs in tables and filters.

How it works (FYI):

# prometheus.yml already has this for each job:
relabel_configs:
  - source_labels: [__address__]
    target_label: node_ip
    regex: (.+):.*      # extracts IP from "IP:PORT"
    replacement: $1

8.2 Configure Nginx Proxy for Embedding

Enable a read-only reverse proxy for embedding Grafana dashboards in external applications (e.g., Angular UI portal).

The /grafana-ro/ endpoint requires two things configured in .env:

  • GRAFANA_SERVICE_TOKEN — Grafana service account token (auto-generated by bootstrap, or create manually — see below)

  • UI_PORTAL_ORIGINS — comma-separated list of portal URLs allowed to access /grafana-ro/ (IP whitelist)

Step 1: Configure allowed portal origins

Set UI_PORTAL_ORIGINS in .env to the URLs of applications that will embed Grafana:

cd /opt/grafana/prometheus-grafana-stack
vi .env

# Example: allow access from Angular UI portal
UI_PORTAL_ORIGINS=https://portal.example.com,https://10.0.0.50:8880

Without this setting, /grafana-ro/ returns 403 Forbidden for all external requests.

Step 2: Verify service token

Check that GRAFANA_SERVICE_TOKEN is set (not CHANGE_ME):

grep GRAFANA_SERVICE_TOKEN .env

If it shows CHANGE_ME, generate a token — see “Manual token generation” below.

Step 3: Restart containers

docker compose down && docker compose up -d

Test the Nginx reverse proxy:

Open https://grafana_domain/grafana-ro/ — you should see Grafana dashboards without needing to log in, with read-only permissions.

Manual token generation (if bootstrap failed or manual installation)

Run the automated script:

cd /opt/grafana
sudo bash /opt/grafana/misc/generate-grafana-token.sh --restart

The script creates a Grafana service account (ReadOnlySA) with Viewer role, generates a token, and updates .env automatically.

If the script fails, generate the token manually:

  1. Open Grafana: http://your_server_ip:3000

  2. Go to: AdministrationUsers and accessService accounts

  3. Click “Add service account” → Name: ReadOnlySA, Role: ViewerAdd

  4. Click “Add service account token” → Name: nginx_roCopy the token

  5. Update .env:

cd /opt/grafana/prometheus-grafana-stack
vi .env
# Set: GRAFANA_SERVICE_TOKEN=glsa_YourActualTokenHere_12345678
  1. Restart: docker compose down && docker compose up -d

How nginx proxy works

The nginx container uses a template file (nginx.conf.template) with ${GRAFANA_SERVICE_TOKEN} placeholder. On startup, the entrypoint script:

  1. Reads the template

  2. Replaces placeholder with value from .env

  3. Generates final nginx.conf

  4. Starts nginx

All services run in network_mode: host. Nginx connects to Grafana via http://127.0.0.1:3000 and serves HTTPS on ports 80/443.

8.3 Embedding Dashboards in iframe

The /grafana-ro/ path provides read-only access without authentication, making it perfect for embedding dashboards in external applications.

How to get dashboard URL for embedding:

  1. Open the dashboard in Grafana (via port 3000 with authentication)

  2. Copy the dashboard path (e.g., /d/abc123/my-dashboard)

  3. Add parameters:

    • ?orgId=1 - Organization ID (required)

    • &kiosk - Kiosk mode (hides Grafana menus)

    • &refresh=30s - Auto-refresh interval (optional)

Example iframe code:

<iframe
  src="/d/business-metrics/business-metrics?orgId=1&kiosk&refresh=30s"
  width="100%"
  height="600"
  frameborder="0">
</iframe>

Available kiosk modes:

  • &kiosk - Full kiosk mode (no top nav, no side menu)

  • &kiosk=tv - TV mode (hides top nav, shows only dashboard)

Common parameters:

  • &from=now-6h&to=now - Time range

  • &var-instance=ServerName - Dashboard variable values

  • &theme=dark - Dark theme

  • &theme=light - Light theme

Important notes:

  • Do NOT use “Share externally” button in Grafana UI (requires additional permissions)

  • Service account with Viewer role cannot create shared dashboards

  • Always use direct dashboard URLs through /grafana-ro/ path

  • Test the URL in browser before embedding to ensure it works

Verify nginx proxy is working:

# Should return 200 OK without authentication
curl -kI https://YOUR_SERVER_DOMAIN/grafana-ro/

9. Managing Grafana Dashboards Provisioning

By default, Grafana automatically provisions all dashboards located under:

/opt/grafana/prometheus-grafana-stack/grafana/provisioning/dashboards/definitions/

Sometimes it is not necessary (or desirable) to install all dashboards at once. You have two options:

9.1 Skip Dashboards During Installation

  1. Before running the stack, remove or move unwanted dashboard JSON files from the definitions folder:

mkdir -p /opt/grafana/prometheus-grafana-stack/grafana/provisioning/dashboards/disabled
mv /opt/grafana/prometheus-grafana-stack/grafana/provisioning/dashboards/definitions/<dashboard-to-skip>.json \
/opt/grafana/prometheus-grafana-stack/grafana/provisioning/dashboards/disabled/

Grafana will only load dashboards present in the definitions folder during startup.

  1. Start the stack as usual:

cd /opt/grafana/prometheus-grafana-stack
docker compose up -d

9.2 Add Dashboards Later

If later you decide to enable some dashboards:

  1. Move the desired JSON file(s) back into the definitions folder:

mv /opt/grafana/prometheus-grafana-stack/grafana/provisioning/dashboards/disabled/<dashboard>.json \
/opt/grafana/prometheus-grafana-stack/grafana/provisioning/dashboards/definitions/
  1. Restart Grafana container to apply changes:

cd /opt/grafana/prometheus-grafana-stack
docker compose restart grafana

Tip: You don’t need to restart the whole stack, only the Grafana container.


10. Alertmanager Configuration

Alertmanager handles alert notifications from Prometheus. It supports multiple notification channels.

API Documentation: See Alertmanager API Guide for application integration (send alerts, manage silences, query status).

SNMP Integration: See SNMP Notifier Guide for NMS integration (custom OIDs, MIB file, SNMPv3 configuration).

Supported Channels

Channel Required Settings Description

Email

SMTP + ALERT_EMAIL

Uses SMTP settings from Grafana configuration

Telegram

Bot Token + Chat ID

Auto-detection supported

Slack

Webhook URL

Incoming Webhook from Slack App

MS Teams

Webhook URL

Power Automate Workflows

Webhook

URL

Custom HTTP endpoint

SMS

Gateway URL + Token

HTTP gateway (Twilio, Vonage)

SNMP

Trap Address + Version

SNMP v2c/v3 traps to NMS

Interactive Setup

Alertmanager is configured during stack deployment:

curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack

# Configure alert notifications? [y/N]: y
# Select channels: 1,2  (Email + Telegram)

The bootstrap script will:

  • Guide you through each channel configuration

  • Offer testing options (MailPit for email, SNMP Trap Catcher for SNMP)

  • Auto-detect Telegram chat_id when possible

  • Validate input format

  • Save settings to .env

Manual Configuration

Edit .env and restart:

cd /opt/grafana/prometheus-grafana-stack
vi .env

Email

ALERT_EMAIL=alerts@company.com

During bootstrap, you have 3 options for Email configuration:

  1. Use Grafana SMTP settings - Uses SMTP already configured for Grafana

  2. Configure new SMTP settings - Enter custom SMTP host, user, password

  3. Use MailPit for testing - Auto-configures fake SMTP (no real emails sent)

Testing with MailPit (fake SMTP):

# Start MailPit (auto-started when option 3 is selected)
docker compose --profile testing up -d mailpit

# Web UI to view emails (opens automatically in bootstrap output)
open http://<server-ip>:8025
  • MailPit captures all emails without sending them

  • Modern HTML email templates with severity colors

  • View email history in web interface

Telegram

TELEGRAM_BOT_TOKEN=123456789:ABCdefGHI...
TELEGRAM_CHAT_ID=-1001234567890

Slack

SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00/B00/XXXX
SLACK_CHANNEL=#alerts

MS Teams

MSTEAMS_WEBHOOK_URL=https://outlook.office.com/webhook/...

Custom Webhook

APP_WEBHOOK_URL=https://your-app.com/api/alerts

SMS Gateway

SMS_GATEWAY_URL=https://api.twilio.com/...
SMS_API_TOKEN=your-token

SNMP Traps

During bootstrap, you have 2 options for SNMP configuration:

  1. Configure external NMS - Enter NMS address, SNMP version, community/credentials

  2. Use SNMP Trap Catcher for testing - Auto-configures local trap receiver with web UI

# SNMPv2c
SNMP_TRAP_ADDRESS=nms.company.com:162
SNMP_VERSION=V2c
SNMP_COMMUNITY=public

# SNMPv3 (authentication + encryption)
SNMP_VERSION=V3
SNMP_AUTH_USERNAME=snmpuser
SNMP_AUTH_PASSWORD=authpassword
SNMP_PRIV_PASSWORD=privpassword

Testing with SNMP Trap Catcher:

# Start SNMP services (auto-started when option 2 is selected)
docker compose --profile snmp up -d

# Web UI to view traps (opens automatically in bootstrap output)
open http://<server-ip>:8089
  • Captures all SNMP traps locally

  • Real-time web interface showing trap history

  • Supports SNMPv2c (for testing)

SNMP Documentation:

  • SNMP Notifier Guide — full setup guide, OID tables per protocol (TR-069, USP, LWM2M), SNMPv3 configuration, device limit alarms, infrastructure resource alarms

  • Architecture: SNMP Trap Flow — diagram showing Alert → Alertmanager → snmp-notifier → SNMP trap → NMS, component descriptions, OID structure

  • OID Mapping Reference — complete OID-to-alert mapping table with numeric OIDs, alert names, and descriptions

  • MIB File — ASN.1 MIB definition (FRIENDLY-TECH-ACS-MIB v2.0.0) for import into NMS systems; on an installed server: /opt/grafana/prometheus-grafana-stack/alertmanager/mibs/FRIENDLY-TECH-ACS-MIB.txt

  • PROD-468 Implementation Report — detailed report of granular OID mapping implementation, testing results, acceptance criteria coverage

  • Thresholds UIhttps://<domain>/alertmanager-config/thresholds — web interface for editing alert thresholds without restart (auto-discovers all configurable alerts)

  • Alert Rules Editorhttps://<domain>/alertmanager-config/alerts — web interface for full CRUD on Prometheus alert rules (add/edit/delete alerts with OID labels)

Apply Changes
docker compose restart alertmanager

Web Interface

Testing Services

If you selected testing options during bootstrap, these UIs are available:

Service URL Purpose

MailPit

http://<server>:8025

View captured emails

SNMP Trap Catcher

http://<server>:8089

View captured SNMP traps

Testing Notification Channels

When you send a test alert to Alertmanager, it routes to ALL configured channels:

Test Alert → Alertmanager (9093) → Email (MailPit if testing)
                                 → Telegram
                                 → Slack
                                 → MS Teams
                                 → Webhook
                                 → SNMP Notifier (9464) → NMS / Trap Catcher (162)
Step 1: Send Test Alert
curl -X POST http://localhost:9093/api/v2/alerts \
  -H "Content-Type: application/json" \
  -d '[{
    "labels": {"alertname": "TestAlert", "severity": "warning", "instance": "test"},
    "annotations": {"summary": "Test alert - please ignore"}
  }]'
Step 2: Verify Each Channel
Channel How to Verify Expected Result

Email

Check inbox of ALERT_EMAIL

Email with alert details

Telegram

Check configured chat/group

Message with 🟠 WARNING

Slack

Check configured channel

Message in #alerts

MS Teams

Check configured channel

Adaptive Card message

Webhook

Check your app logs

POST request with JSON

SNMP

Check NMS or snmp-catcher

Trap with OID 1.3.6.1.4.1.65666.2.0.99

SNMP Testing
# 1. Check SNMP services are running
docker ps | grep -E "snmp-notifier|snmp-catcher"

# 2. Send test alert
curl -X POST http://localhost:9093/api/v2/alerts \
  -H "Content-Type: application/json" \
  -d '[{"labels":{"alertname":"DbUnreachable","severity":"critical"}}]'

# 3. Check snmp-notifier logs
docker logs snmp-notifier --tail 10

# 4. View traps in web UI (if using SNMP Trap Catcher)
open http://<server>:8089

SNMP Trap Catcher features:

  • Web UI at port 8089 showing all received traps

  • Real-time updates

  • Trap details: timestamp, OID, variables, source IP


11. Downgrade / Rollback

Bootstrap Command Reference

# Show help
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --help

# Show available versions
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --version-list

Installing a Previous Version

To install a specific version, use the --branch parameter:

curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack --branch 1.6.6

⚠️ WARNING: When specifying --branch, you are installing a specific version instead of the latest stable release (main). Make sure this is intentional.

Before Downgrade

  1. Check current version:

cat /opt/grafana/VERSION
  1. List available versions:

curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --version-list
  1. Backup data (recommended):

cd /opt/grafana/prometheus-grafana-stack
docker compose down
cp -r prometheus_data /backup/prometheus_data_$(date +%Y%m%d)
cp -r grafana_data /backup/grafana_data_$(date +%Y%m%d)

Important Notes

  • Data compatibility: Downgrading may cause issues if Prometheus or Grafana database schemas changed between versions

  • Dashboards: Custom dashboards created in newer version may not work in older version

  • Configuration: .env settings are preserved, but new options added in later versions will be ignored

Clean Downgrade (Full Reset)

If you experience issues after downgrade or need a guaranteed clean state, perform a full reset. This involves two parts:

  1. Stack cleanup (Prometheus/Grafana server) - removes metrics history, dashboards, alert states

  2. Exporters cleanup (target nodes: ACS, MySQL, etc.) - removes exporter containers


Part 1: Stack (Prometheus/Grafana Server)

Step 1: Stop all services

cd /opt/grafana/prometheus-grafana-stack
docker compose down

Step 2: Backup current data (optional)

mkdir -p /backup/grafana-$(date +%Y%m%d)
cp -r prometheus_data grafana_data alertmanager_data /backup/grafana-$(date +%Y%m%d)/
cp .env /backup/grafana-$(date +%Y%m%d)/

Step 3: Remove data directories

cd /opt/grafana/prometheus-grafana-stack
rm -rf prometheus_data grafana_data alertmanager_data

Step 4: Remove repository and reinstall

rm -rf /opt/grafana

# Install latest stable version
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack

# Or install specific version (downgrade)
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack --branch 1.6.6

Note: Without --branch, the latest stable version from main is installed. Use --branch X.Y.Z only when you need a specific older version.

What gets removed:

Data Location Effect

Prometheus metrics

prometheus_data/

All historical metrics lost

Grafana database

grafana_data/

Dashboards, users, API keys reset

Alertmanager state

alertmanager_data/

Silences, notification history reset

What is preserved:

Data Location Notes

Configuration

.env

Backed up in Step 2, restore manually if needed

Target definitions

targets/*.json

Recreated by bootstrap based on detected services


Part 2: Exporters (Target Nodes)

Run these steps on each monitored server (ACS, MySQL, etc.):

Step 1: Stop all exporter containers

cd /opt/grafana/exporters
for dir in */; do
  [ -f "$dir/docker-compose.yml" ] && docker compose -f "$dir/docker-compose.yml" down
done

Step 2: Remove exporters directory

rm -rf /opt/grafana/exporters

Step 3: Reinstall exporters

# Install latest stable version
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- exporters

# Or install specific version (downgrade)
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- exporters --branch 1.6.6

What gets removed on exporter nodes:

Component Location Effect

Base exporters (node, process, cAdvisor)

exporters/base/

System/container metrics collection stops

JMX exporters

exporters/jmx-wildfly/

Java/WildFly metrics collection stops

MySQL exporter

exporters/mysql/

Database metrics collection stops

Note: Exporter clean install does not affect historical data - metrics are stored on the Prometheus server, not on exporter nodes.

⚠️ WARNING: Clean downgrade erases all monitoring history. Use only when:

  • Standard downgrade causes errors

  • Grafana/Prometheus fails to start after version change

  • You need a guaranteed working state


Bootstrap Session Logging

Every bootstrap run automatically creates a detailed log file in /opt/grafana/.bootstrap-logs/.

Log file format: bootstrap-<run_number>-<YYYYMMDD-HHMMSS>.log

What is logged:

  • Full command that initiated the bootstrap

  • All terminal output with millisecond timestamps

  • User input at every interactive prompt (passwords are masked as **)

  • Host and user information

  • Duration of the run

Example log:

[2026-04-20 14:32:05.123] === Bootstrap Run #7 ===
[2026-04-20 14:32:05.124] Command: /opt/grafana/misc/bootstrap.sh 37.27.117.79
[2026-04-20 14:32:05.125] Host: qa22o
[2026-04-20 14:32:05.126] User: root
[2026-04-20 14:32:05.830] ▶ Step 1: Installing Docker and Docker Compose...
[2026-04-20 14:32:06.012] ✓ Docker already installed: Docker version 29.2.1
[2026-04-20 14:32:15.230] [USER INPUT] <enter>
[2026-04-20 14:32:22.500] [USER INPUT] ********
[2026-04-20 14:33:55.100] ✓ Deployment Complete!
[2026-04-20 14:33:55.101] === Run #7 finished (duration: 1m 50s) ===

Usage: When reporting issues, send the log file to the development team:

ls /opt/grafana/.bootstrap-logs/
# Send the latest bootstrap-*.log file

Note: Log files are created only for real bootstrap runs (not for --version, --help, --docs). Run counter auto-increments across runs.


Troubleshooting

Quick Diagnostics: Run sudo bash /opt/grafana/misc/diagnose.sh to automatically check Docker, containers, ports, firewall, and Prometheus targets.

Common Issues and Solutions

Prometheus cannot connect to exporter

Symptoms: Target is DOWN in Prometheus UI, curl hangs

Step 1: Is exporter listening?

ss -ltpn | grep <port>

Step 2: Is container running?

docker ps
docker logs -f <CONTAINER_ID>

Step 3: Is exporter accessible from Prometheus?

docker exec $(docker ps | grep prom/prometheus | awk '{print $1}') \
  wget http://<EXPORTER_IP>:<PORT>/metrics -O /tmp/test

If connection hangs — check firewall allows access from Prometheus host.


Cannot Login to Grafana

Symptom: Grafana login page loads but admin password is rejected (Invalid username or password)

Cause: Grafana stores the admin password in its SQLite database on first startup. Changing GRAFANA_ADMIN_PASSWORD in .env and restarting the container does not update the existing password — it only applies when creating a new admin user.

Solution: Reset password via CLI:

# On the Grafana server:
bash /opt/grafana/misc/bootstrap.sh --reset-password "new_password"

# Or directly:
docker exec grafana grafana cli admin reset-admin-password "new_password"

Spaces in .env Files

Wrong: MYSQL_USER = exporter (spaces around =)

Correct: MYSQL_USER=exporter (no spaces)


Forgetting to Create .env File

Symptom: Containers start but use default/empty values

Solution: Always run cp .env.example .env before docker compose up


Firewall Rules Not Persistent

Problem: Rules disappear after reboot

Solution: Our scripts auto-save rules. Manual save:

# Ubuntu/Debian
netfilter-persistent save

# CentOS/Rocky
service iptables save

Wrong Prometheus IP in Firewall

Symptom: Prometheus shows targets as “DOWN”

Solution: Verify Prometheus IP and update firewall rules.


Conflicting Container Names

Problem: “The container name is already in use”

Solution:

docker compose down
docker compose up -d

MySQL exporter shows mysql_up 0

Solution:

systemctl status mysql
mysql -u exporter -p -e "SHOW GRANTS;"

Nginx 502 Bad Gateway

Solution:

docker logs nginx_proxy
docker exec nginx_proxy env | grep GRAFANA_SERVICE_TOKEN
docker compose restart nginx

cAdvisor shows no container metrics (Debian 13+ / Docker 28+)

Symptom: cAdvisor returns only root container (id="/"), no individual Docker containers visible. System Dashboard Basic shows no FT UI Services. cAdvisor logs show:

Failed to create existing container: failed to identify the read-write layer ID
open /rootfs/var/lib/docker/image/overlayfs/layerdb/mounts/.../mount-id: no such file or directory

Cause: Docker on Debian 13 (trixie) and newer kernels uses overlayfs storage driver instead of overlay2. cAdvisor does not support the overlayfs driver — it cannot read container layer metadata.

Check: docker info | grep 'Storage Driver' — if it shows overlayfs, this is the problem.

Solution — switch to overlay2:

# 1. Update Docker daemon config
cat /etc/docker/daemon.json   # check existing config

# Add "storage-driver": "overlay2" to daemon.json:
python3 -c "
import json
with open('/etc/docker/daemon.json') as f: cfg = json.load(f)
cfg['storage-driver'] = 'overlay2'
with open('/etc/docker/daemon.json','w') as f: json.dump(cfg, f, indent=2)
"

# 2. Restart Docker (containers will stop)
systemctl restart docker

# 3. Verify
docker info | grep 'Storage Driver'   # should show: overlay2

# 4. Restart all containers
cd /opt/grafana/exporters/base && docker compose up -d
# ... restart your application containers

Note: Bootstrap automatically detects overlayfs and offers to switch to overlay2 during installation.


Grafana alerts not sending emails

Solution:

docker logs grafana | grep -i smtp
# Test in UI: Alerting > Contact points > email > Test

Operations

Updating Monitoring Stack

curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack

Select “Keep current configuration” to preserve your .env settings.

For exporters:

cd /opt/grafana
sudo bash /opt/grafana/misc/quick-deploy.sh PROMETHEUS_IP

Updating Configuration

vi .env
docker compose up -d
docker compose restart nginx  # if nginx config changed

Updating Docker Images

vi .env  # Change version, e.g., PROMETHEUS_VERSION=v3.6.0
docker compose pull prometheus
docker compose up -d prometheus

Rollback Deployment

The deployment script creates a manifest tracking all changes. To rollback:

# Rollback to previous state (latest manifest)
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --rollback

# Or directly:
sudo /opt/grafana/misc/quick-deploy.sh --rollback

# List available manifests
sudo /opt/grafana/misc/quick-deploy.sh --list-manifests

# Rollback specific deployment
sudo /opt/grafana/misc/quick-deploy.sh --rollback /opt/grafana/.deploy-manifests/manifest-20260201-143022

What gets rolled back:

  • Stops deployed containers

  • Removes added firewall rules

  • Restores original configuration files (WildFly, Hazelcast)

Note: Services like WildFly/ACS may need manual restart after rollback.

Offline/Air-gapped Installation

Docker must already be installed on the air-gapped server (install-docker.sh needs internet).

Prepare (machine with internet) — use the 1.6.8 branch ZIP (not main), and build on a host with the classic Docker image store (Docker Desktop / Engine ≥ 28 use the containerd store → docker save writes OCI → old Docker on the target fails with unrecognized image format):

bash misc/download-docker-images.sh   # linux/amd64 by default; includes built stack images + Grafana plugins
tar tf docker-images-bundle.tar* | grep -m1 manifest.json && echo CLASSIC_OK   # NOT oci-layout
scp grafana.zip docker-images-bundle.tar.gz user@server:/tmp/
scp -r docker-images-bundle-grafana-plugins user@server:/tmp/

Install exporters (air-gapped server):

sudo docker load -i /tmp/docker-images-bundle.tar.gz
cd /opt && sudo unzip /tmp/grafana.zip && sudo mv grafana-* grafana
sudo bash /opt/grafana/misc/quick-deploy.sh PROMETHEUS_IP

Install monitoring stack (air-gapped server): load the bundle and the repository as above, then run bootstrap. Run it plain for the interactive configurator, or add -y to skip every prompt:

sudo docker load -i /tmp/docker-images-bundle.tar.gz
sudo bash /opt/grafana/misc/bootstrap.sh stack       # interactive: prompts for domain, passwords, SMTP
sudo bash /opt/grafana/misc/bootstrap.sh stack -y    # non-interactive: no questions; auto IP-as-domain + random passwords

With Docker and the repo already present, bootstrap runs fully offline — self-signed cert, htpasswd, alertmanager placeholder, containers from pre-loaded images, base exporters, targets — and auto-stages the bundled Grafana plugins if docker-images-bundle-grafana-plugins/ is next to /opt/grafana or in /tmp. No manual section-8 steps needed (see the Quick Start offline section for details and the Oracle datasource plugin).

Security Best Practices

  • Never commit .env files to git

  • Use strong passwords (16+ characters)

  • Restrict permissions: chmod 600 .env

  • Rotate passwords every 90 days

  • Use dedicated users with minimal grants

Deployment Checklist

Before Starting:

  • Docker installed, repository cloned to /opt/grafana

  • Prometheus IP address known

  • Credentials gathered (MySQL/Oracle/SMTP)

For Each Server:

  • .env configured, containers deployed

  • Metrics endpoints working (curl localhost:PORT/metrics)

  • Firewall rules saved

On Prometheus/Grafana:

  • Targets show “UP” in Prometheus UI

  • Dashboards loading, alerts configured

  • Nginx proxy working (/grafana-ro/)


For Developers

Documentation

This repository automatically maintains Antora-formatted documentation alongside the Markdown README.

Automatic Conversion

When you commit changes to README.md, a pre-commit hook automatically converts it to AsciiDoc format for Antora documentation. The converted file is placed in docs/modules/ROOT/pages/index.adoc and staged for the commit.

Setting Up Git Hooks

After cloning this repository, install the Git hooks:

./misc/install-hooks.sh

This installs a pre-commit hook that:

  • Detects when README.md is staged for commit

  • Converts it to AsciiDoc using pandoc

  • Stages the converted index.adoc file automatically

Requirements:

  • pandoc must be installed:

# macOS
brew install pandoc

# Ubuntu/Debian
sudo apt-get install pandoc

# CentOS/RHEL
sudo yum install pandoc
Manual Conversion

To manually convert the documentation:

./misc/md2adoc.sh
Antora Structure
docs/
├── antora.yml                    # Antora component descriptor
└── modules/
    └── ROOT/
        ├── nav.adoc              # Navigation menu
        └── pages/
            └── index.adoc        # Main documentation (auto-generated)

Development & Releases

The project uses version branches for development and releases.

Workflow:

1. Work on version branch (e.g., 1.6.5)
2. When ready: ./misc/release.sh [patch|minor|major]
   → Merges to main
   → Creates GitHub Release with changelog
   → Creates next version branch (e.g., 1.6.6)
3. Continue development on new branch

Version branch naming:

  • Branches: 1.6.5 (without v prefix)

  • Tags: 1.6.5 (without v prefix)

  • Releases: 1.6.5 (without v prefix)

Using specific versions:

# List available versions
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- --version-list

# Install specific version
curl -fsSL https://hub.friendly-tech.com/bootstrap/install.sh | sudo bash -s -- stack --branch 1.6.4