GitHub Dashboard →

Configuration

Overview

All configuration is passed via environment variables. In local development, copy .env.example to .env and edit as needed. In production, secrets are pulled from HashiCorp Vault via the External Secrets Operator and injected into pods automatically — no secrets ever live in Kubernetes manifests or Docker images.

Never commit secrets to git. Use .env (gitignored) locally, and Vault in production. Never reuse secrets across environments.

API Server

Variable Default Required Description
DATABASE_URL Yes PostgreSQL connection string, e.g. postgres://user:pass@host:5432/db
NATS_URL nats://localhost:4222 Yes NATS server URL
BIND_ADDR 0.0.0.0:8090 No HTTP listen address
JWT_SECRET Yes HS256 signing secret. Generate: openssl rand -base64 32
CREDENTIAL_ENCRYPTION_KEY Yes AES-GCM key for encrypting APNs/FCM credentials. Generate: openssl rand -base64 32
CORS_ORIGINS * No Comma-separated allowed origins, e.g. https://app.visionpush.io
OAUTH_ISSUER_URL http://localhost:8090 No Public OIDC issuer URL (must match the URL clients use)
APP_URL Optional Public URL of the admin panel (used in invite/reset emails)
ANALYTICS_DATA_DIR /data/analytics No Directory for Parquet analytics files
OTLP_ENDPOINT Optional OpenTelemetry gRPC endpoint, e.g. http://tempo:4317
RUST_LOG info No Tracing filter, e.g. visionpush_api=debug,sqlx=warn
DB_MAX_CONNECTIONS 20 No PostgreSQL connection pool max size
DB_MIN_CONNECTIONS 2 No PostgreSQL connection pool min size

Worker

Variable Default Required Description
DATABASE_URL Yes Same as API
NATS_URL nats://localhost:4222 Yes Same as API
CREDENTIAL_ENCRYPTION_KEY Yes Same as API
ANALYTICS_DATA_DIR /data/analytics No Must point to the same volume as the API in Kubernetes
ANALYTICS_FLUSH_THRESHOLD 1000 No Events buffered before auto-flush to Parquet
HEALTH_ADDR 0.0.0.0:9090 No Worker health server (GET /healthz)
OTLP_ENDPOINT Optional OpenTelemetry gRPC endpoint
APNS_CONCURRENCY 500 No Parallel APNs connections per worker pod
FCM_CONCURRENCY 200 No Parallel FCM requests per worker pod
WEB_CONCURRENCY 200 No Parallel Web Push requests per worker pod
BATCH_SIZE 500 No Devices per push batch
APNS_HOST_OVERRIDE Optional Override APNs host (load tests / mock server)
FCM_SEND_URL_OVERRIDE Optional Override FCM URL template, use {project_id} as placeholder
DB_MAX_CONNECTIONS 20 No PostgreSQL connection pool max size

Blob Storage

VisionPush supports two storage backends for media uploads (images attached to push notifications). Both backends use content-addressed storage (BLAKE3 hash) for automatic deduplication.

Variable Default Required Description
STORAGE_BACKEND filesystem No filesystem or s3
STORAGE_PUBLIC_URL Yes Public base URL for serving blobs, e.g. https://cdn.example.com/media

Filesystem backend (default)

Variable Default Description
STORAGE_DATA_DIR ./data/blobs Local directory for blob files

S3 backend (STORAGE_BACKEND=s3)

Compatible with AWS S3, Cloudflare R2, MinIO, and Backblaze B2. Uses manual AWS SigV4 signing — no AWS SDK dependency.

Variable Required Description
S3_BUCKET Yes Bucket name
S3_REGION Yes AWS region, or auto for Cloudflare R2
S3_ENDPOINT Optional Custom endpoint for R2/MinIO/B2, e.g. https://account.r2.cloudflarestorage.com
AWS_ACCESS_KEY_ID Yes S3 access key
AWS_SECRET_ACCESS_KEY Yes S3 secret key
S3_KEY_PREFIX No Key prefix within the bucket (default: media)

SMTP

Required for password reset, team invites, and email verification. Compatible providers: Resend, Postmark, AWS SES, Mailgun, SendGrid.

Variable Default Description
SMTP_HOST SMTP server hostname
SMTP_PORT 587 Port (587 = STARTTLS, 465 = SMTPS)
SMTP_USER SMTP username
SMTP_PASSWORD SMTP password
SMTP_FROM From address, e.g. VisionPush <noreply@example.com>

PostgreSQL Backup (WAL-G)

Used by the Docker Compose backup service. In Kubernetes, CloudNativePG handles backups via its own configuration.

Variable Description
WALG_S3_PREFIX S3 path, e.g. s3://my-bucket/visionpush/
AWS_ACCESS_KEY_ID S3 access key
AWS_SECRET_ACCESS_KEY S3 secret key
AWS_REGION S3 region (auto for R2)
AWS_ENDPOINT Custom S3 endpoint (omit for real AWS)
BACKUP_SCHEDULE Cron for full base backups, default 0 2 * * *

Observability (Docker Compose)

Variable Default Description
GF_ADMIN_USER admin Grafana admin username
GF_ADMIN_PASSWORD admin Grafana admin password
GF_SERVER_ROOT_URL Public Grafana URL

Load Test Binary

Variable Default Description
NATS_URL nats://localhost:4222 NATS connection URL
DEVICES_PER_BATCH 100 Synthetic devices per batch
NUM_BATCHES 20 Total batches to publish
RATE_MS 200 Delay between batch publishes (ms)
FCM_PROJECT_ID loadtest-project FCM project ID in the worker pool
WAIT_SECS 60 Seconds to wait for feedback after last batch

Generating Secrets

bash
# JWT signing secret
openssl rand -base64 32

# Credential encryption key
openssl rand -base64 32

# PostgreSQL password
openssl rand -base64 24
Never reuse secrets across environments. Never commit secrets to git — use .env (gitignored) locally or Vault in production.

Kubernetes — Vault Keys

In production all secrets are stored in Vault at secret/visionpush/production and injected into pods via the External Secrets Operator. Required Vault keys:

  • DATABASE_URL
  • JWT_SECRET
  • CREDENTIAL_ENCRYPTION_KEY
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • S3_BUCKET
  • DB_PASSWORD
  • ALERTMANAGER_SMTP_PASSWORD (if alerting.enabled=true)
  • ALERTMANAGER_SLACK_WEBHOOK_URL (if alerting.slack.enabled=true)

Write them with:

bash
vault kv put secret/visionpush/production \
  DATABASE_URL="postgresql://visionpush:PASS@visionpush-postgres-rw:5432/visionpush" \
  JWT_SECRET="$(openssl rand -base64 32)" \
  CREDENTIAL_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
  AWS_ACCESS_KEY_ID="..." \
  AWS_SECRET_ACCESS_KEY="..." \
  S3_BUCKET="visionpush-media" \
  DB_PASSWORD="$(openssl rand -base64 24)"