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