Enhanced CI workflows by adding an Alpine-based smoke test for the backend with PostgreSQL 16. Updated the Docker build process to support dynamic base images and added provenance, SBOM, and labels to Docker builds. Extended branch compatibility checks and refined backend configurations for broader usage scenarios.
NexaPG
NexaPG is a full-stack PostgreSQL monitoring platform for multiple remote targets. It combines FastAPI, React, and PostgreSQL in a Docker Compose stack with RBAC, polling collectors, query insights, alerting, and target-owner email notifications.
Table of Contents
- Quick Deploy (Prebuilt Images)
- Prerequisites
- Make Commands
- Configuration Reference (
.env) - Core Functional Areas
- Service Information
- Target Owner Notifications
- API Overview
- API Error Format
pg_stat_statementsRequirement- Reverse Proxy / SSL Guidance
- PostgreSQL Compatibility Smoke Test
- Troubleshooting
- Security Notes
Highlights
- Multi-target monitoring for remote PostgreSQL instances
- Optional one-click target onboarding for "all databases" discovery on an instance
- PostgreSQL compatibility support:
14,15,16,17,18 - JWT auth (
access+refresh) and RBAC (admin,operator,viewer) - Polling collector for metrics, locks, activity, and optional
pg_stat_statements - Target detail overview (instance, storage, replication, core performance metrics)
- Alerts system:
- standard built-in alerts
- custom SQL alerts (admin/operator)
- warning + alert severities
- real-time UI updates + toast notifications
- Target owners: alert emails are sent only to responsible users assigned to a target
- SMTP settings in admin UI (send-only) with test mail support
- Structured backend logs + audit logs
UI Preview
Dashboard
Targets Management
Query Insights
Alerts
Admin Settings
Target Detail (DBA Mode)
Target Detail (Easy Mode)
Repository Layout
backend/FastAPI app, SQLAlchemy async models, Alembic migrations, collector servicesfrontend/React + Vite UIops/helper files/scriptsdocker-compose.ymlfull local stack.env.examplecomplete environment templateMakefilecommon commands
Prerequisites
- Docker Engine
24+ - Docker Compose
v2+ - GNU Make (optional but recommended)
- Open host ports (or custom values in
.env):FRONTEND_PORT(default5173)BACKEND_PORT(default8000)DB_PORT(default5433)
Optional:
psqlfor manual DB checks
Quick Deploy (Prebuilt Images)
If you only want to run NexaPG from published Docker Hub images, use the bootstrap script:
mkdir -p /opt/NexaPG
cd /opt/NexaPG
wget -O bootstrap-compose.sh https://git.nesterovic.cc/nessi/NexaPG/raw/branch/main/ops/scripts/bootstrap-compose.sh
chmod +x bootstrap-compose.sh
./bootstrap-compose.sh
This downloads:
docker-compose.yml.env.exampleMakefile
Then:
# generate JWT secret
python -c "import secrets; print(secrets.token_urlsafe(64))"
# generate Fernet encryption key
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# put both values into .env (JWT_SECRET_KEY / ENCRYPTION_KEY)
# note: .env is auto-created by bootstrap if it does not exist
make up
Manual download alternative:
mkdir -p /opt/NexaPG
cd /opt/NexaPG
wget https://git.nesterovic.cc/nessi/NexaPG/raw/branch/main/docker-compose.yml
wget https://git.nesterovic.cc/nessi/NexaPG/raw/branch/main/.env.example
wget https://git.nesterovic.cc/nessi/NexaPG/raw/branch/main/Makefile
cp .env.example .env
make up pulls nesterovicit/nexapg-backend:latest and nesterovicit/nexapg-frontend:latest, then starts the stack.
Open the application:
- Frontend:
http://<SERVER_IP>:<FRONTEND_PORT> - API base:
http://<SERVER_IP>:<BACKEND_PORT>/api/v1 - OpenAPI:
http://<SERVER_IP>:<BACKEND_PORT>/docs
Initial admin bootstrap user (created from .env if missing):
- Email: value from
INIT_ADMIN_EMAIL - Password: value from
INIT_ADMIN_PASSWORD
Make Commands
make up # pull latest images and start all services
make down # stop all services
make logs # follow compose logs
make migrate # optional/manual: run alembic upgrade head in backend container
Note: Migrations run automatically when the backend container starts (entrypoint.sh).
Configuration Reference (.env)
Application
| Variable | Description |
|---|---|
APP_NAME |
Application display name |
ENVIRONMENT |
Runtime environment (dev, staging, prod, test) |
LOG_LEVEL |
Backend log level (DEBUG, INFO, WARNING, ERROR) |
Core Database
| Variable | Description |
|---|---|
DB_NAME |
Core metadata database name |
DB_USER |
Core database user |
DB_PASSWORD |
Core database password |
DB_PORT |
Host port mapped to internal PostgreSQL 5432 |
Backend / Security
| Variable | Description |
|---|---|
BACKEND_PORT |
Host port mapped to backend container port 8000 |
JWT_SECRET_KEY |
JWT signing secret |
JWT_ALGORITHM |
JWT algorithm (default HS256) |
JWT_ACCESS_TOKEN_MINUTES |
Access token lifetime in minutes |
JWT_REFRESH_TOKEN_MINUTES |
Refresh token lifetime in minutes |
ENCRYPTION_KEY |
Fernet key for target credentials and SMTP password encryption |
CORS_ORIGINS |
Allowed CORS origins (comma-separated or * for dev only) |
POLL_INTERVAL_SECONDS |
Collector polling interval |
INIT_ADMIN_EMAIL |
Bootstrap admin email |
INIT_ADMIN_PASSWORD |
Bootstrap admin password |
Alert Noise Tuning
| Variable | Description |
|---|---|
ALERT_ACTIVE_CONNECTION_RATIO_MIN_TOTAL_CONNECTIONS |
Minimum total sessions required before evaluating active-connection ratio |
ALERT_ROLLBACK_RATIO_WINDOW_MINUTES |
Time window for rollback ratio evaluation |
ALERT_ROLLBACK_RATIO_MIN_TOTAL_TRANSACTIONS |
Minimum transaction volume before rollback ratio is evaluated |
ALERT_ROLLBACK_RATIO_MIN_ROLLBACKS |
Minimum rollback count before rollback ratio is evaluated |
Frontend
| Variable | Description |
|---|---|
FRONTEND_PORT |
Host port mapped to frontend container port 80 |
Core Functional Areas
Targets
- Create, list, edit, delete targets
- Test target connection before save
- Optional "discover all databases" mode (creates one monitored target per discovered DB)
- Configure SSL mode per target
- Toggle
pg_stat_statementsusage per target - Assign responsible users (target owners)
Target Details
- Database Overview section with instance, role, uptime, size, replication, and core metrics
- Metric charts with range selection and live mode
- Locks and activity tables
Query Insights
- Uses collected
pg_stat_statementsdata - Ranking and categorization views
- Search and pagination
- Disabled automatically for targets where query insights flag is off
Alerts
- Warning and alert severity split
- Expandable alert cards with details and recommended actions
- Custom alert definitions (SQL + thresholds)
- Real-time refresh and in-app toast notifications
Admin Settings
- User management (RBAC)
- SMTP settings for outgoing alert mails:
- enable/disable
- host/port/auth
- STARTTLS / SSL mode
- from email + from name
- recipient test mail
Service Information
- Sidebar entry for runtime and system details
- Displays current version, latest known version, uptime, host, and platform
- "Check for Updates" against the latest published release in the official upstream repository (
git.nesterovic.cc/nessi/NexaPG) - Version/update source are read-only in UI (maintainer-controlled in code/release flow)
- Local displayed version is code-defined in
backend/app/core/config.py(NEXAPG_VERSION) and not configurable via.env
Target Owner Notifications
Email alert routing is target-specific:
- only users assigned as owners for a target receive that target's alert emails
- supports multiple owners per target
- notification sending is throttled to reduce repeated alert spam
API Overview
Health
GET /api/v1/healthzGET /api/v1/readyz
Auth
POST /api/v1/auth/loginPOST /api/v1/auth/refreshPOST /api/v1/auth/logoutGET /api/v1/me
Targets
GET /api/v1/targetsPOST /api/v1/targetsPOST /api/v1/targets/test-connectionGET /api/v1/targets/{id}PUT /api/v1/targets/{id}DELETE /api/v1/targets/{id}GET /api/v1/targets/{id}/ownersPUT /api/v1/targets/{id}/ownersGET /api/v1/targets/owner-candidatesGET /api/v1/targets/{id}/metricsGET /api/v1/targets/{id}/locksGET /api/v1/targets/{id}/activityGET /api/v1/targets/{id}/top-queriesGET /api/v1/targets/{id}/overview
Alerts
GET /api/v1/alerts/statusGET /api/v1/alerts/definitionsPOST /api/v1/alerts/definitionsPUT /api/v1/alerts/definitions/{id}DELETE /api/v1/alerts/definitions/{id}POST /api/v1/alerts/definitions/test
Admin
GET /api/v1/admin/usersPOST /api/v1/admin/usersPUT /api/v1/admin/users/{user_id}DELETE /api/v1/admin/users/{user_id}GET /api/v1/admin/settings/emailPUT /api/v1/admin/settings/emailPOST /api/v1/admin/settings/email/test
Service Information
GET /api/v1/service/infoPOST /api/v1/service/info/check
API Error Format
All 4xx/5xx responses use a consistent JSON payload:
{
"code": "validation_error",
"message": "Request validation failed",
"details": [],
"request_id": "c8f0f888-2365-4b86-a5de-b3f0e9df4a4b"
}
Common fields:
code: stable machine-readable error codemessage: human-readable summarydetails: optional extra context (validation list, debug context, etc.)request_id: request correlation ID (also returned inX-Request-IDheader)
Common error codes:
bad_request(400)unauthorized(401)forbidden(403)not_found(404)conflict(409)validation_error(422)target_unreachable(503)internal_error(500)
pg_stat_statements Requirement
Query Insights requires pg_stat_statements on the monitored target:
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
If unavailable, disable it per target in target settings.
Reverse Proxy / SSL Guidance
For production, serve frontend and API under the same public origin via reverse proxy.
- Frontend URL example:
https://monitor.example.com - Proxy API path
/api/to backend service - Route
/api/v1to the backend service
This prevents mixed-content and CORS issues.
PostgreSQL Compatibility Smoke Test
Run manually against one DSN:
PG_DSN='postgresql://postgres:postgres@127.0.0.1:5432/compatdb?sslmode=disable' \
python backend/scripts/pg_compat_smoke.py
Run with DSN candidates (CI style):
PG_DSN_CANDIDATES='postgresql://postgres:postgres@postgres:5432/compatdb?sslmode=disable,postgresql://postgres:postgres@127.0.0.1:5432/compatdb?sslmode=disable' \
python backend/scripts/pg_compat_smoke.py
Troubleshooting
Backend container keeps restarting during make migrate
Most common reason: failed migration. Check logs:
docker compose logs --tail=200 backend
docker compose logs --tail=200 db
CORS or mixed-content issues behind SSL proxy
- Ensure proxy forwards
/api/(or/api/v1) to backend - Set correct frontend origin(s) in
CORS_ORIGINS
rejected SSL upgrade for a target
Target likely does not support SSL with current settings.
Set target sslmode to disable (or correct SSL config on target DB).
Query Insights empty
- Check target has
Use pg_stat_statementsenabled - Verify extension exists on target (
CREATE EXTENSION ...)
Security Notes
- No secrets hardcoded in repository
- Passwords hashed with Argon2
- Sensitive values encrypted at rest (Fernet)
- RBAC enforced on protected endpoints
- Audit logs for critical actions
- Collector error logging includes throttling to reduce repeated noise






