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 - 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 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: