new-site/docker-compose.yml
justin 15f5c267e7 Fix dashboard stale series + enable Prometheus admin API
Dashboard queries now use max() to pick UP value when old stale
probe targets coexist with new ones. Prometheus admin API enabled
for future TSDB cleanup of stale series.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:43:42 -05:00

407 lines
14 KiB
YAML

services:
# ── Core Application ────────────────────────────────────────────────
site:
build: ./site
ports:
- "4322:80"
restart: unless-stopped
api:
build: ./api
ports:
- "3001:3001"
env_file: .env
environment:
- NODE_ENV=production
- PORT=3001
- DOMAIN=performancewest.net
- DATABASE_URL=postgresql://pw:${DB_PASSWORD:-pw_dev_2026}@api-postgres:5432/performancewest
- ERPNEXT_URL=http://erpnext:8000
- ERPNEXT_API_KEY=${ERPNEXT_API_KEY}
- ERPNEXT_API_SECRET=${ERPNEXT_API_SECRET}
- ERPNEXT_SITE_NAME=performancewest.net
- WORKER_URL=http://workers:8090
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
- STRIPE_TEST_SECRET_KEY=${STRIPE_TEST_SECRET_KEY}
- STRIPE_TEST_WEBHOOK_SECRET=${STRIPE_TEST_WEBHOOK_SECRET}
- STRIPE_TEST_IDENTITY_WEBHOOK_SECRET=${STRIPE_TEST_IDENTITY_WEBHOOK_SECRET}
- CUSTOMER_JWT_SECRET=${CUSTOMER_JWT_SECRET}
- ADMIN_JWT_SECRET=${ADMIN_JWT_SECRET}
- ADMIN_EMAIL=${ADMIN_EMAIL:-ops@performancewest.net}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USER=${SMTP_USER}
- SMTP_PASS=${SMTP_PASS}
- SMTP_FROM=${SMTP_FROM}
- MINIO_ENDPOINT=minio
- MINIO_PORT=9000
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
- PAYPAL_CLIENT_ID=${PAYPAL_CLIENT_ID}
- PAYPAL_CLIENT_SECRET=${PAYPAL_CLIENT_SECRET}
- PAYPAL_API_URL=https://api-m.paypal.com
- SHKEEPER_URL=${SHKEEPER_URL:-http://127.0.0.1:5000}
- SHKEEPER_API_KEY=${SHKEEPER_API_KEY}
- SHKEEPER_PUBLIC_URL=${SHKEEPER_PUBLIC_URL:-https://crypto.performancewest.net}
- WEBHOOK_SECRET=${WEBHOOK_SECRET}
- LISTMONK_URL=http://listmonk:9000
- LISTMONK_USER=${LISTMONK_USER:-admin}
- LISTMONK_PASSWORD=${LISTMONK_PASSWORD}
depends_on:
- api-postgres
restart: unless-stopped
api-postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=pw
- POSTGRES_PASSWORD=${DB_PASSWORD:-pw_dev_2026}
- POSTGRES_DB=performancewest
ports:
- "5432:5432"
volumes:
- api-pgdata:/var/lib/postgresql/data
restart: unless-stopped
workers:
build:
context: .
dockerfile: scripts/Dockerfile
env_file: .env
environment:
- DATABASE_URL=postgresql://pw:${DB_PASSWORD:-pw_dev_2026}@api-postgres:5432/performancewest
- NODE_ENV=production
- DOMAIN=performancewest.net
- ERPNEXT_URL=http://erpnext:8000
- ERPNEXT_API_KEY=${ERPNEXT_API_KEY}
- ERPNEXT_API_SECRET=${ERPNEXT_API_SECRET}
- ERPNEXT_SITE_NAME=performancewest.net
- MINIO_ENDPOINT=minio
- MINIO_PORT=9000
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
- MINIO_BUCKET=performancewest
- MINIO_SECURE=false
- ADMIN_EMAIL=${ADMIN_EMAIL:-ops@performancewest.net}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USER=${SMTP_USER}
- SMTP_PASS=${SMTP_PASS}
- SMTP_FROM=${SMTP_FROM}
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
- CUSTOMER_JWT_SECRET=${CUSTOMER_JWT_SECRET}
- OLLAMA_HOST=http://ollama:11434
- USE_DOCSERVER=true
- DOCSERVER_TIMEOUT=120
- FCC_CORES_USERNAME=${FCC_CORES_USERNAME}
- FCC_CORES_PASSWORD=${FCC_CORES_PASSWORD}
- OPS_IMAP_HOST=${OPS_IMAP_HOST:-mail.performancewest.net}
- OPS_IMAP_PORT=${OPS_IMAP_PORT:-993}
- OPS_IMAP_USER=${OPS_IMAP_USER}
- OPS_IMAP_PASS=${OPS_IMAP_PASS}
- FROM_EMAIL=Performance West <noreply@performancewest.net>
- CRYPTO_SWEEP_ADMIN_EMAIL=${ADMIN_EMAIL:-ops@performancewest.net}
- USAC_USERNAME=${USAC_USERNAME}
- USAC_PASSWORD=${USAC_PASSWORD}
- ANYTIME_MAILBOX_SIGNUP_EMAIL=${ANYTIME_MAILBOX_SIGNUP_EMAIL:-noreply@performancewest.net}
- ANYTIME_MAILBOX_SIGNUP_PHONE=${ANYTIME_MAILBOX_SIGNUP_PHONE}
- ANYTIME_MAILBOX_DEFAULT_PASSWORD=${ANYTIME_MAILBOX_DEFAULT_PASSWORD}
- ANYTIME_MAILBOX_IMAP_HOST=${ANYTIME_MAILBOX_IMAP_HOST:-co.carrierone.com}
- ANYTIME_MAILBOX_IMAP_PORT=${ANYTIME_MAILBOX_IMAP_PORT:-993}
- ANYTIME_MAILBOX_IMAP_USER=${ANYTIME_MAILBOX_IMAP_USER:-noreply@performancewest.net}
- ANYTIME_MAILBOX_IMAP_PASS=${ANYTIME_MAILBOX_IMAP_PASS}
- ANYTIME_MAILBOX_IMAP_SSL=${ANYTIME_MAILBOX_IMAP_SSL:-true}
- ANYTIME_MAILBOX_IMAP_FOLDER=${ANYTIME_MAILBOX_IMAP_FOLDER:-INBOX}
- ANYTIME_MAILBOX_OTP_SENDER_HINT=${ANYTIME_MAILBOX_OTP_SENDER_HINT:-anytimemailbox}
- ANYTIME_MAILBOX_OTP_CODE=${ANYTIME_MAILBOX_OTP_CODE}
- ANYTIME_MAILBOX_OTP_POLL_SECONDS=${ANYTIME_MAILBOX_OTP_POLL_SECONDS:-6}
- ANYTIME_MAILBOX_OTP_TIMEOUT_SECONDS=${ANYTIME_MAILBOX_OTP_TIMEOUT_SECONDS:-180}
volumes:
- worker-data:/app/data
depends_on:
- api-postgres
restart: unless-stopped
# ── ERPNext CRM ─────────────────────────────────────────────────────
erpnext:
image: performancewest-erpnext:latest
build:
context: ./erpnext
dockerfile: Dockerfile
ports:
- "8080:8000"
environment:
- DB_HOST=erpnext-mariadb
- DB_PORT=3306
- DB_NAME=erpnext
- DB_PASSWORD=${ERPNEXT_DB_PASSWORD:-d5Webu5n0LLW2GrmKDHCf5xPliVKO1Kd9XErpWRP}
- REDIS_CACHE=redis://erpnext-redis:6379/0
- REDIS_QUEUE=redis://erpnext-redis:6379/1
- REDIS_SOCKETIO=redis://erpnext-redis:6379/2
- SOCKETIO_PORT=9000
volumes:
- erpnext-frappe-public:/home/frappe/frappe-bench/apps/frappe/frappe/public
- erpnext-erpnext-public:/home/frappe/frappe-bench/apps/erpnext/erpnext/public
- erpnext-logs:/home/frappe/frappe-bench/logs
- erpnext-sites:/home/frappe/frappe-bench/sites
depends_on:
- erpnext-mariadb
- erpnext-redis
restart: unless-stopped
erpnext-mariadb:
image: mariadb:10.6
environment:
- MYSQL_ROOT_PASSWORD=${ERPNEXT_DB_PASSWORD:-d5Webu5n0LLW2GrmKDHCf5xPliVKO1Kd9XErpWRP}
volumes:
- erpnext-mariadb-data:/var/lib/mysql
restart: unless-stopped
erpnext-redis:
image: redis:7-alpine
restart: unless-stopped
erpnext-scheduler:
image: performancewest-erpnext:latest
command: bench schedule
volumes:
- erpnext-sites:/home/frappe/frappe-bench/sites
- erpnext-logs:/home/frappe/frappe-bench/logs
environment:
- DB_HOST=erpnext-mariadb
- DB_PORT=3306
- DB_NAME=erpnext
- DB_PASSWORD=${ERPNEXT_DB_PASSWORD:-d5Webu5n0LLW2GrmKDHCf5xPliVKO1Kd9XErpWRP}
- REDIS_CACHE=redis://erpnext-redis:6379/0
- REDIS_QUEUE=redis://erpnext-redis:6379/1
- REDIS_SOCKETIO=redis://erpnext-redis:6379/2
depends_on:
- erpnext-mariadb
- erpnext-redis
restart: unless-stopped
erpnext-worker-default:
image: performancewest-erpnext:latest
command: bench worker --queue default
volumes:
- erpnext-sites:/home/frappe/frappe-bench/sites
- erpnext-logs:/home/frappe/frappe-bench/logs
environment:
- DB_HOST=erpnext-mariadb
- DB_PORT=3306
- DB_NAME=erpnext
- DB_PASSWORD=${ERPNEXT_DB_PASSWORD:-d5Webu5n0LLW2GrmKDHCf5xPliVKO1Kd9XErpWRP}
- REDIS_CACHE=redis://erpnext-redis:6379/0
- REDIS_QUEUE=redis://erpnext-redis:6379/1
- REDIS_SOCKETIO=redis://erpnext-redis:6379/2
depends_on:
- erpnext-mariadb
- erpnext-redis
restart: unless-stopped
erpnext-worker-short:
image: performancewest-erpnext:latest
command: bench worker --queue short
volumes:
- erpnext-sites:/home/frappe/frappe-bench/sites
- erpnext-logs:/home/frappe/frappe-bench/logs
environment:
- DB_HOST=erpnext-mariadb
- DB_PORT=3306
- DB_NAME=erpnext
- DB_PASSWORD=${ERPNEXT_DB_PASSWORD:-d5Webu5n0LLW2GrmKDHCf5xPliVKO1Kd9XErpWRP}
- REDIS_CACHE=redis://erpnext-redis:6379/0
- REDIS_QUEUE=redis://erpnext-redis:6379/1
- REDIS_SOCKETIO=redis://erpnext-redis:6379/2
depends_on:
- erpnext-mariadb
- erpnext-redis
restart: unless-stopped
# ── Supporting Services ─────────────────────────────────────────────
ollama:
image: ollama/ollama:latest
volumes:
- ollama-data:/root/.ollama
restart: unless-stopped
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
ports:
- "127.0.0.1:9000:9000"
- "127.0.0.1:9001:9001"
environment:
- MINIO_ROOT_USER=${MINIO_ACCESS_KEY}
- MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY}
volumes:
- minio-data:/data
restart: unless-stopped
listmonk:
image: listmonk/listmonk:latest
ports:
- "9100:9000"
environment:
- LISTMONK_app__address=0.0.0.0:9000
- LISTMONK_db__host=api-postgres
- LISTMONK_db__port=5432
- LISTMONK_db__user=pw
- LISTMONK_db__password=${DB_PASSWORD:-pw_dev_2026}
- LISTMONK_db__database=listmonk
- LISTMONK_db__ssl_mode=disable
- LISTMONK_db__max_open=25
- LISTMONK_db__max_idle=25
- LISTMONK_db__max_lifetime=300s
- LISTMONK_ADMIN_USER=${LISTMONK_USER:-admin}
- LISTMONK_ADMIN_PASSWORD=${LISTMONK_PASSWORD}
- TZ=America/New_York
volumes:
- listmonk-uploads:/listmonk/uploads
depends_on:
- api-postgres
restart: unless-stopped
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
ports:
- "3100:3000"
environment:
- DATABASE_URL=postgresql://umami:umami_dev@umami-postgres:5432/umami
- APP_SECRET=${UMAMI_APP_SECRET:-0MnMHB71wtSYMF33Pm0L+MWLKezVtLGNwbkg++PZi5c=}
depends_on:
- umami-postgres
restart: unless-stopped
umami-postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=umami
- POSTGRES_PASSWORD=umami_dev
- POSTGRES_DB=umami
volumes:
- umami-pgdata:/var/lib/postgresql/data
restart: unless-stopped
# ── Monitoring Stack ────────────────────────────────────────────────
prometheus:
image: prom/prometheus:latest
ports:
- "127.0.0.1:9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./monitoring/alert_rules.yml:/etc/prometheus/alert_rules.yml:ro
- prometheus-data:/prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=90d
- --web.enable-lifecycle
- --web.enable-admin-api
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
grafana:
image: grafana/grafana:latest
ports:
- "127.0.0.1:3200:3000"
environment:
- GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-admin}
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-pw_grafana_2026}
- GF_SERVER_ROOT_URL=https://monitoring.performancewest.net
- GF_SERVER_DOMAIN=monitoring.performancewest.net
- GF_SMTP_ENABLED=true
- GF_SMTP_HOST=${SMTP_HOST}:${SMTP_PORT}
- GF_SMTP_USER=${SMTP_USER}
- GF_SMTP_PASSWORD=${SMTP_PASS}
- GF_SMTP_FROM_ADDRESS=noreply@performancewest.net
- GF_USERS_ALLOW_SIGN_UP=false
- GF_AUTH_ANONYMOUS_ENABLED=false
- GF_SECURITY_DISABLE_BRUTE_FORCE_LOGIN_PROTECTION=true
volumes:
- grafana-data:/var/lib/grafana
- ./monitoring/grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml:ro
depends_on:
- prometheus
restart: unless-stopped
alertmanager:
image: prom/alertmanager:latest
ports:
- "127.0.0.1:9093:9093"
volumes:
- ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
command:
- --config.file=/etc/alertmanager/alertmanager.yml
- --storage.path=/alertmanager
environment:
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
restart: unless-stopped
node-exporter:
image: prom/node-exporter:latest
command:
- --path.rootfs=/host
- --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)
volumes:
- /:/host:ro,rslave
pid: host
restart: unless-stopped
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
devices:
- /dev/kmsg
privileged: true
restart: unless-stopped
postgres-exporter:
image: prometheuscommunity/postgres-exporter:latest
environment:
- DATA_SOURCE_NAME=postgresql://pw:${DB_PASSWORD:-pw_dev_2026}@api-postgres:5432/performancewest?sslmode=disable
depends_on:
- api-postgres
restart: unless-stopped
nginx-exporter:
image: nginx/nginx-prometheus-exporter:latest
command:
- -nginx.scrape-uri=http://host.docker.internal:8888/nginx_status
- -nginx.timeout=5s
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "127.0.0.1:9113:9113"
restart: unless-stopped
blackbox-exporter:
image: prom/blackbox-exporter:latest
volumes:
- ./monitoring/blackbox.yml:/etc/blackbox_exporter/config.yml:ro
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
volumes:
api-pgdata:
worker-data:
ollama-data:
minio-data:
erpnext-frappe-public:
erpnext-erpnext-public:
erpnext-logs:
erpnext-sites:
erpnext-mariadb-data:
listmonk-uploads:
umami-pgdata:
prometheus-data:
grafana-data: