Docker Compose installation
For custom deployments and development environments.
Prerequisites​
- Docker 20.10+
- Docker Compose v2
- PostgreSQL 14+ target database
Quick start​
# Clone the repository
git clone https://gitlab.com/postgres-ai/postgresai.git
cd postgresai
# Configure target database
cp .env.example .env
# Edit .env with your database connection
# Start the stack
docker compose up -d
Configuration​
Environment variables​
Create a .env file or set these environment variables:
# Required
PGAI_TAG=0.15.0
REPLICATOR_PASSWORD=<generated-secret>
VM_AUTH_USERNAME=vmauth
VM_AUTH_PASSWORD=<generated-secret>
# Target databases are defined in instances.yml
# Use postgres_ai_mon by default after running prepare-db.
# Optional - Grafana admin password (REQUIRED for production!)
# Default is 'demo' - always change this in production
GF_SECURITY_ADMIN_PASSWORD=your_secure_password
# Optional - Retention
VM_RETENTION_PERIOD=336h
docker-compose.yml excerpt​
This excerpt omits the config-init and sources-generator helper services that
the pgwatch collectors depend on (see depends_on below); both are defined in the
full docker-compose.yml in the repository. sources-generator renders the pgwatch
source files from instances.yml.
services:
grafana:
image: grafana/grafana:latest
ports:
- "${GRAFANA_BIND_HOST:-}3000:3000"
volumes:
- grafana-data:/var/lib/grafana
- ./config/grafana/provisioning:/etc/grafana/provisioning
environment:
- GF_SECURITY_ADMIN_USER=monitor
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:-demo}
sink-postgres:
image: postgres:17
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
sink-prometheus:
image: victoriametrics/victoria-metrics:v1.140.0
ports:
- "${BIND_HOST:-}59090:9090"
volumes:
- vm-data:/victoria-metrics-data
environment:
- VM_AUTH_USERNAME=${VM_AUTH_USERNAME:-}
- VM_AUTH_PASSWORD=${VM_AUTH_PASSWORD:-}
- VM_RETENTION_PERIOD=${VM_RETENTION_PERIOD:-336h}
pgwatch-postgres:
image: postgresai/pgwatch:${PGAI_TAG}
command:
- "--sources=/postgres_ai_configs/pgwatch/sources.yml"
- "--metrics=/postgres_ai_configs/pgwatch/metrics.yml"
- "--sink=postgresql://pgwatch@sink-postgres:5432/measurements?sslmode=disable"
- "--web-addr=:8080"
depends_on:
- sources-generator
- sink-postgres
pgwatch-prometheus:
image: postgresai/pgwatch:${PGAI_TAG}
command:
- "--sources=/postgres_ai_configs/pgwatch-prometheus/sources.yml"
- "--metrics=/postgres_ai_configs/pgwatch-prometheus/metrics.yml"
- "--sink=prometheus://0.0.0.0:9091/pgwatch"
- "--web-addr=:8089"
depends_on:
- sources-generator
- sink-prometheus
monitoring_flask_backend:
image: postgresai/monitoring-flask-backend:${PGAI_TAG}
ports:
- "8000:8000"
environment:
- PROMETHEUS_URL=http://sink-prometheus:9090
- POSTGRES_SINK_URL=postgresql://pgwatch@sink-postgres:5432/measurements
volumes:
grafana-data:
vm-data:
Access Grafana​
After starting the stack, open Grafana at localhost:3000 in your browser.
Credentials:
- Username:
monitor - Password: Value of
GF_SECURITY_ADMIN_PASSWORD(default:demo)
The default password demo is for local testing only. Always set a strong GF_SECURITY_ADMIN_PASSWORD in your .env file before any production or exposed deployment.
Service details​
Grafana​
Pre-configured with:
- postgres_ai dashboards (14 dashboards)
- VictoriaMetrics data source
- Anonymous access disabled
Customization:
grafana:
environment:
- GF_SECURITY_ADMIN_PASSWORD=your_secure_password
- GF_AUTH_ANONYMOUS_ENABLED=false
- GF_SERVER_ROOT_URL=https://grafana.example.com
VictoriaMetrics​
Single-node time-series database optimized for Prometheus metrics.
Performance tuning:
sink-prometheus:
environment:
- VM_RETENTION_PERIOD=336h
- VM_QUERY_DURATION=30s
- VM_MAX_CONCURRENT_REQUESTS=16
pgwatch​
Metrics collectors for PostgreSQL. The 0.15 stack runs separate postgresai/pgwatch services for PostgreSQL and Prometheus-compatible sinks.
Prometheus sink options:
pgwatch-prometheus:
command:
- "--sources=/postgres_ai_configs/pgwatch-prometheus/sources.yml"
- "--metrics=/postgres_ai_configs/pgwatch-prometheus/metrics.yml"
- "--sink=prometheus://0.0.0.0:9091/pgwatch"
- "--web-addr=:8089"
- "--log-level=error"
Flask backend​
Provides query text lookup for Grafana dashboards (joining pg_stat_statements queryid with actual SQL).
Health check:
curl http://localhost:8000/health
Directory structure​
postgresai/
├── docker-compose.yml
├── .env
├── config/
│ ├── grafana/
│ │ ├── provisioning/
│ │ │ ├── dashboards/
│ │ │ │ └── postgres_ai/ # Dashboard JSON files
│ │ │ └── datasources/
│ │ │ └── default.yaml # VictoriaMetrics datasource
│ │ └── grafana.ini
│ └── prometheus/
│ └── prometheus.yml # Scrape configuration
└── monitoring_flask_backend/
├── app.py
└── Dockerfile
Operations​
Start stack​
docker compose up -d
Stop stack​
docker compose down
View logs​
# All services
docker compose logs -f
# Specific services
docker compose logs -f pgwatch-postgres pgwatch-prometheus
Restart service​
docker compose restart grafana
Update images​
docker compose pull
docker compose up -d
Adding multiple databases​
To monitor multiple PostgreSQL instances, add them to instances.yml; sources-generator renders both pgwatch source files from that list:
- name: prod-primary
conn_str: postgresql://postgres_ai_mon:pass@prod-db:5432/app
preset_metrics: full
is_enabled: true
group: production
custom_tags:
env: production
cluster: prod
node_name: primary
- name: staging-primary
conn_str: postgresql://postgres_ai_mon:pass@staging-db:5432/app
preset_metrics: full
is_enabled: true
group: staging
custom_tags:
env: staging
cluster: staging
node_name: primary
Use the cluster and node_name custom tags in Grafana to switch between environments.
Troubleshooting​
Grafana shows "No data"​
-
Check pgwatch is collecting metrics:
docker compose logs pgwatch-postgres pgwatch-prometheus | grep -i error -
Verify VictoriaMetrics has data:
curl 'http://localhost:59090/api/v1/query?query=pg_stat_user_tables_n_tup_ins' -
Check Grafana datasource:
- Go to Configuration — Data Sources
- Test the VictoriaMetrics connection
High memory usage​
VictoriaMetrics memory is proportional to active time series. It sizes its caches
from the container memory limit, so cap memory by lowering that limit (or set
SINK_PROMETHEUS_MEM in .env) rather than overriding the service command:
sink-prometheus:
mem_limit: 1073741824 # 1 GiB (default is 1.5 GiB / SINK_PROMETHEUS_MEM)
Container networking issues​
If the pgwatch collectors can't reach your database, add the host mapping to both collector services:
pgwatch-postgres:
extra_hosts:
- "host.docker.internal:host-gateway" # For host machine access
pgwatch-prometheus:
extra_hosts:
- "host.docker.internal:host-gateway"
Next steps​
- Helm Installation - Kubernetes deployment
- Configuration - Advanced settings
- Alerting - Set up alerts