Observability

The portals container is a static-asset server (Nginx) plus a startup entrypoint and a set of reverse proxies. Observability is therefore mostly about container health, Nginx logs and the stub_status endpoint; application behaviour is observed in the browser and, for data operations, in the UI Backend.

Container health

The Compose healthcheck curls a portal path inside the container. A healthy status means Nginx is serving the apps:

NAME       STATUS     PORTS
portals    healthy    0.0.0.0:8880->80/tcp, 0.0.0.0:8843->443/tcp
healthcheck:
  test: [ "CMD", "curl", "-f", "http://127.0.0.1/support-portal/index.html" ]
  interval: 10s
  timeout: 5s
  retries: 50

Metrics

/nginx_status exposes Nginx stub_status for the Prometheus nginx-exporter. Access is restricted to 127.0.0.1, 10.0.0.0/8, 172.16.0.0/12 and the CIDR in METRICS_ALLOW_IP (default 0.0.0.0/0 — narrow it, see Security Notes).

curl http://<host>:8880/nginx_status

Logs

# Container (entrypoint + Nginx)
docker compose logs -f portals

# Nginx access / error logs on the host, when /var/log/nginx is mounted.
# DATA_FOLDER is set in .env for Compose, not in your shell -- use the install root itself.
tail -f /usr/local/ft-system/portals/nginx/logs/access.log
tail -f /usr/local/ft-system/portals/nginx/logs/error.log

The entrypoint logs the rendered upstreams, the runtime-config patch and the permissions upload — grep those lines to confirm startup succeeded:

docker compose logs portals | grep -iE "generated|patched|upload|WARN"

Browser-side

For data-operation issues, inspect the browser DevTools Network/Console and correlate with the UI Backend logs. Because every API call now travels through the portals origin, a failing request shows up as a 502/504 from the /iot-webservice/ proxy when the backend is the problem, and in the Nginx error log on the host.