Complete reference for every .env variable in Changemaker Lite.
material/file-cog
reference
getting-started
operator
configuration
Environment Variables
!!! tip "Need help getting set up?"
Bunker Operations provides managed infrastructure and hands-on setup assistance for organizations running Changemaker Lite. We handle domains, tunnels, SMTP, and servers so you can focus on your campaign. Get in touch:bnkops.com | admin@bnkops.ca
Changemaker Lite uses a single .env file at the project root to configure all services. Copy the example file to get started:
cp .env.example .env
!!! danger "Security Essentials"
- Change everyREQUIRED_STRONG_PASSWORD_CHANGE_THIS value before starting services
- Generate secrets with openssl rand -hex 32 (or -hex 16 where noted)
- Never commit .env to version control
- Use unique values for each secret — do not reuse JWT secrets as encryption keys
Quick Reference
Variables are grouped by service. Each table marks whether a variable is required for a basic deployment or optional (has a sensible default or only needed for specific features).
Symbol
Meaning
:material-alert-circle:{ .text-red }
Must be set before first run
:material-tune-variant:
Has a working default; change for production
:material-flask:
Feature flag — opt-in
General
Variable
Default
Description
NODE_ENV
development
Set to production for production deployments. Controls logging, error detail, and security checks.
DOMAIN
cmlite.org
Root domain. Used for nginx subdomain routing (app.DOMAIN, api.DOMAIN, etc.). The root domain serves the MkDocs documentation site; all application routes live under app.DOMAIN.
USER_ID
1000
UID for container file ownership. Match your host user's UID (id -u).
GROUP_ID
1000
GID for container file ownership. Match your host user's GID (id -g).
DOCKER_GROUP_ID
984
GID of the docker group on the host. Needed for containers that access the Docker socket. Find with getent group docker.
The primary database for both the Express API and the Fastify Media API (shared).
Variable
Default
Description
V2_POSTGRES_USER
changemaker
Database username.
V2_POSTGRES_PASSWORD
—
:material-alert-circle:{ .text-red } Must change. Database password.
V2_POSTGRES_DB
changemaker_v2
Database name.
V2_POSTGRES_PORT
5433
Host port mapping. The container listens on 5432 internally.
!!! tip "Connection string"
The DATABASE_URL is constructed automatically inside Docker. If running locally, set:
DATABASE_URL=postgresql://changemaker:YOUR_PASSWORD@localhost:5433/changemaker_v2
JWT Authentication :material-alert-circle:
Variable
Default
Description
JWT_ACCESS_SECRET
—
:material-alert-circle:{ .text-red } Secret for signing access tokens. Generate with openssl rand -hex 32.
JWT_REFRESH_SECRET
—
:material-alert-circle:{ .text-red } Secret for signing refresh tokens. Must differ from the access secret.
JWT_INVITE_SECRET
—
:material-alert-circle:{ .text-red } Secret for signing volunteer invite tokens. Must differ from access and refresh secrets. Generate with openssl rand -hex 32.
JWT_ACCESS_EXPIRY
15m
Access token lifetime. Short-lived by design.
JWT_REFRESH_EXPIRY
7d
Refresh token lifetime. Tokens are rotated atomically on each refresh.
Encryption Key :material-alert-circle:
Variable
Default
Description
ENCRYPTION_KEY
—
:material-alert-circle:{ .text-red } AES key for encrypting secrets stored in the database (SMTP passwords, API keys, etc.). Generate with openssl rand -hex 32. Must not reuse a JWT secret. Required in all environments (no longer falls back to JWT secret in development).
Security Extras :material-tune-variant:
Additional secrets for key separation. These fall back to JWT_ACCESS_SECRET if empty, but setting unique values is strongly recommended for production.
Variable
Default
Description
GITEA_SSO_SECRET
(empty)
Cookie signing secret for Gitea SSO integration. Falls back to JWT_ACCESS_SECRET if empty. Generate with openssl rand -hex 32.
SERVICE_PASSWORD_SALT
(empty)
Salt for deriving deterministic service passwords (Gitea, Rocket.Chat user provisioning). Falls back to JWT_ACCESS_SECRET if empty — rotating JWT_ACCESS_SECRET would then invalidate all provisioned service passwords. Generate with openssl rand -hex 32.
CSP_ENABLED
false
Enable Content Security Policy headers in API responses.
!!! warning "Key separation"
If GITEA_SSO_SECRET and SERVICE_PASSWORD_SALT are left empty, the API logs security warnings on every startup. Set unique values to isolate secret rotation and prevent one compromised key from affecting other subsystems.
Initial Admin Account :material-alert-circle:
These credentials create the first super-admin user during database seeding (npx prisma db seed).
Variable
Default
Description
INITIAL_ADMIN_EMAIL
admin@cmlite.org
Email address for the initial admin.
INITIAL_ADMIN_PASSWORD
—
:material-alert-circle:{ .text-red } Must change. Must be 12+ characters with uppercase, lowercase, and a digit. Change this password after first login.
API Server
Variable
Default
Description
API_PORT
4000
Host port for the Express API.
API_URL
http://localhost:4000
Public URL of the API. Used for generating links in emails and QR codes.
CORS_ORIGINS
http://localhost:3000,http://localhost
Comma-separated list of allowed CORS origins. Add your production domain (e.g., https://app.yourdomain.org) for production.
!!! warning "Production CORS"
If you deploy behind a tunnel (Pangolin, Cloudflare) and API requests fail with CORS errors, add your production app. subdomain here:
CORS_ORIGINS=https://app.betteredmonton.org,http://localhost:3000,http://localhost
Then restart the API: docker compose restart api
Admin GUI
Variable
Default
Description
ADMIN_PORT
3000
Host port for the React admin dashboard.
ADMIN_URL
http://localhost:3000
Public URL of the admin GUI.
Rate Limiting :material-tune-variant:
Variable
Default
Description
RATE_LIMIT_WINDOW_MS
900000
Rate limit window in milliseconds (default: 15 minutes).
RATE_LIMIT_MAX
500
Maximum requests per window per IP. Auth endpoints have a stricter limit (10/min).
Nginx Reverse Proxy
Variable
Default
Description
NGINX_HTTP_PORT
80
HTTP port. All subdomains route through nginx.
NGINX_HTTPS_PORT
443
HTTPS port. SSL is typically handled by the tunnel provider (Pangolin/Cloudflare).
Redis :material-alert-circle:
Shared by rate limiting, BullMQ job queues, geocoding cache, and session data.
Variable
Default
Description
REDIS_PASSWORD
—
:material-alert-circle:{ .text-red } Must change. Redis requires authentication.
REDIS_URL
redis://:${REDIS_PASSWORD}@redis-changemaker:6379
Full connection URL. Uses the password variable automatically.
Payments (Stripe) :material-flask:
Variable
Default
Description
ENABLE_PAYMENTS
false
:material-flask: Set to true to enable the payments feature (memberships, products, donations). Stripe API keys are stored encrypted in the database via the admin settings page.
Email / SMTP :material-tune-variant:
Variable
Default
Description
SMTP_HOST
mailhog-changemaker
SMTP server. Default points to the MailHog dev container.
SMTP_PORT
1025
SMTP port. 1025 for MailHog, 587 for most production SMTP.
SMTP_USER
(empty)
SMTP username. Not needed for MailHog.
SMTP_PASS
(empty)
SMTP password.
SMTP_FROM
noreply@cmlite.org
"From" address on outgoing emails.
SMTP_FROM_NAME
Changemaker Lite
Display name for the "From" header.
EMAIL_TEST_MODE
true
When true, all emails go to MailHog instead of real SMTP. Set to false in production.
TEST_EMAIL_RECIPIENT
admin@cmlite.org
Catch-all recipient when test mode is on.
!!! info "Development email"
With EMAIL_TEST_MODE=true, all outgoing email is captured in MailHog at http://localhost:8025. No real emails are sent.
Listmonk (Newsletters) :material-flask:
Listmonk handles newsletter/marketing campaigns. Sync with the main platform is opt-in.
Variable
Default
Description
LISTMONK_PORT
9001
Listmonk web UI port.
LISTMONK_DB_PORT
5434
Listmonk's own PostgreSQL port (separate from the main DB). Uses 5434 to avoid conflict with the main PostgreSQL (5432 internal / 5433 host).
Admin panel token (access at /admin). Generate with openssl rand -hex 32.
VAULTWARDEN_DOMAIN
https://vault.cmlite.org
Public-facing URL. Must use HTTPS — Bitwarden web vault enforces HTTPS for account creation. Set to your Pangolin tunnel URL.
VAULTWARDEN_SIGNUPS_ALLOWED
false
Allow new user self-registration. Keep false and use admin panel invites.
VAULTWARDEN_WEBSOCKET_ENABLED
true
Enable WebSocket notifications for real-time sync.
VAULTWARDEN_SMTP_SECURITY
off
SMTP security mode: off for MailHog, starttls or force_tls for production. Uses the main SMTP_* variables for host/credentials.
!!! info "Initial setup"
The vaultwarden-init container automatically invites the INITIAL_ADMIN_EMAIL user when starting. Check MailHog (or your SMTP) for the invitation email.
Rocket.Chat (Team Chat) :material-flask:
Self-hosted team chat for volunteer coordination. Requires MongoDB (auto-configured).
Variable
Default
Description
ENABLE_CHAT
false
:material-flask: Set to true to enable the Rocket.Chat integration. The initial default; once saved in admin Settings, the DB value is authoritative.
:material-flask: Set to true to enable automatic shift → Gancio event synchronization.
Jitsi Meet (Video Conferencing) :material-flask:
Self-hosted video conferencing with JWT authentication. Integrates with Rocket.Chat for in-channel video calls.
Variable
Default
Description
ENABLE_MEET
false
:material-flask: Set to true to enable the Jitsi Meet integration. The initial default; once saved in admin Settings, the DB value is authoritative.
JITSI_APP_ID
changemaker
JWT application ID. Must match across Jitsi Prosody, Rocket.Chat app settings, and JWT_ACCEPTED_ISSUERS/JWT_ACCEPTED_AUDIENCES.
JITSI_APP_SECRET
—
:material-alert-circle:{ .text-red } JWT secret for signing Jitsi tokens. Generate with openssl rand -hex 32. Shared between Jitsi Prosody, Rocket.Chat, and the API.
JITSI_JICOFO_AUTH_PASSWORD
—
Internal XMPP password for Jicofo (conference focus). Generate with openssl rand -hex 16.
JITSI_JVB_AUTH_PASSWORD
—
Internal XMPP password for JVB (video bridge). Generate with openssl rand -hex 16.
JITSI_EMBED_PORT
8893
Port for iframe embedding in admin.
JITSI_URL
http://jitsi-web-changemaker:80
Internal container URL.
JVB_ADVERTISE_IP
(empty)
Server's public IP address. Required in production for NAT traversal so remote participants can connect.
JVB_PORT
10000
UDP port for media traffic. Must be open in your firewall.
!!! warning "Production requirements"
- JVB_ADVERTISE_IP must be set to your server's public IP for calls to work outside the local network.
- Port 10000/udp must be open in your firewall for media traffic.
- Calls must go through the production domain (not localhost) for SSL/JWT to work.
Send SMS messages via an Android phone running the Termux API server. The phone acts as an SMS gateway.
Variable
Default
Description
ENABLE_SMS
false
:material-flask: Set to true to enable SMS campaigns. The initial default; once saved in admin Settings, the DB value is authoritative.
TERMUX_API_URL
http://10.0.0.193:5001
URL of the Termux API server running on the Android phone.
TERMUX_API_KEY
(empty)
API key for authenticating with the Termux server (HMAC auth via X-API-Key header).
SMS_DELAY_BETWEEN_MS
3000
Delay between sending individual SMS messages (ms). Prevents carrier throttling.
SMS_MAX_RETRIES
3
Maximum retry attempts for failed SMS sends.
SMS_RESPONSE_SYNC_INTERVAL_MS
30000
How often to poll the phone's inbox for responses (ms).
SMS_DEVICE_MONITOR_INTERVAL_MS
30000
How often to check device health — battery, connectivity (ms).
!!! tip "GUI configuration"
The Termux API URL and API key can also be configured from Admin → Settings → SMS. Database values override these env vars when set.
Canadian address data import for geographic canvassing.
Variable
Default
Description
NAR_DATA_DIR
/data
Path to extracted NAR data inside the container. Expects YYYYMM/Addresses/ and YYYYMM/Locations/ subdirectories. Mount via ./data:/data:ro in Docker Compose.
Multi-provider geocoding for address resolution. Works out of the box with free providers; optional paid providers improve accuracy.
Variable
Default
Description
MAPBOX_API_KEY
(empty)
Mapbox API key for improved geocoding accuracy. Free tier: 100k requests/month. Sign up.
GEOCODING_RATE_LIMIT_MS
1100
Delay between requests to free providers (ms). Respects rate limits.
GEOCODING_CACHE_ENABLED
true
Enable Redis-backed geocoding cache.
GEOCODING_CACHE_TTL_HOURS
24
Cache lifetime in hours.
GOOGLE_MAPS_API_KEY
(empty)
Google Maps API key. Most accurate but $0.005/request after free tier.
GOOGLE_MAPS_ENABLED
false
Enable Google Maps as a geocoding provider.
GEOCODING_PARALLEL_ENABLED
true
Enable parallel geocoding for bulk imports (~10x speedup).
GEOCODING_BATCH_SIZE
10
Number of concurrent geocoding requests during bulk operations.
BULK_GEOCODE_ENABLED
true
Enable bulk re-geocoding from the admin UI.
BULK_GEOCODE_MAX_BATCH
5000
Maximum locations per bulk geocoding run.
Overpass / Area Import :material-tune-variant:
OpenStreetMap data import for map enrichment.
Variable
Default
Description
OVERPASS_API_URL
https://overpass-api.de/api/interpreter
Overpass API endpoint. Use a private instance for heavy usage.
OVERPASS_MIN_DELAY_MS
30000
Minimum delay between requests (ms). The public API requires 30 seconds.
AREA_IMPORT_MAX_GRID_POINTS
500
Maximum reverse-geocode grid points per area import.
Pangolin Tunnel :material-tune-variant:
Expose services to the internet without port forwarding, using a self-hosted Pangolin instance.
Variable
Default
Description
PANGOLIN_API_URL
https://api.bnkserve.org/v1
Pangolin server API endpoint.
PANGOLIN_API_KEY
(empty)
API key for Pangolin management.
PANGOLIN_ORG_ID
(empty)
Organization ID in Pangolin.
PANGOLIN_SITE_ID
(empty)
Site ID (populated after setup via admin GUI).
PANGOLIN_ENDPOINT
https://pangolin.bnkserve.org
Pangolin tunnel endpoint.
PANGOLIN_NEWT_ID
(empty)
Newt client ID (populated after setup).
PANGOLIN_NEWT_SECRET
(empty)
Newt client secret (populated after setup).
!!! tip "Setup flow"
Configure the tunnel from Admin → Settings → Pangolin. The setup wizard walks you through creating a site, copying credentials, and connecting the Newt container. See Deployment for the full guide.
Monitoring :material-flask:
These services are behind the monitoring Docker Compose profile. Start them with:
docker compose --profile monitoring up -d
Variable
Default
Description
PROMETHEUS_PORT
9090
Prometheus web UI / query port.
GRAFANA_PORT
3005
Grafana dashboard port.
GRAFANA_ADMIN_PASSWORD
admin
:material-tune-variant: Change in production.
GRAFANA_ROOT_URL
http://localhost:3005
Public URL for Grafana (used in links).
CADVISOR_PORT
8086
cAdvisor container metrics port.
NODE_EXPORTER_PORT
9100
Prometheus node exporter port.
REDIS_EXPORTER_PORT
9121
Redis metrics exporter port.
ALERTMANAGER_PORT
9093
Alertmanager web UI port.
GOTIFY_PORT
8889
Gotify push notification port.
GOTIFY_ADMIN_USER
admin
Gotify admin username.
GOTIFY_ADMIN_PASSWORD
admin
:material-tune-variant: Change in production.
GRAFANA_EMBED_PORT
8894
Port for iframe embedding Grafana in admin.
ALERTMANAGER_EMBED_PORT
8895
Port for iframe embedding Alertmanager in admin.
Bunker Ops (Fleet Management) :material-flask:
Remote metrics push for managing multiple Changemaker Lite instances from a central monitoring server.
Variable
Default
Description
INSTANCE_LABEL
(empty)
Unique label for this instance (used as a Prometheus metric label). Falls back to DOMAIN if empty.
BUNKER_OPS_ENABLED
false
:material-flask: Enable remote metrics push to a central VictoriaMetrics server.
Feature flags for the social graph, CRM people module, and analytics dashboard.
Variable
Default
Description
ENABLE_SOCIAL
false
:material-flask: Enable the social module (friendships, challenges, spotlights, referrals). The initial default; once saved in admin Settings, the DB value is authoritative.
ENABLE_PEOPLE
false
:material-flask: Enable the CRM people module. The initial default; once saved in admin Settings, the DB value is authoritative.
ENABLE_ANALYTICS
false
:material-flask: Enable the analytics dashboard with visitor tracking and geographic insights. The initial default; once saved in admin Settings, the DB value is authoritative.
GeoIP (MaxMind GeoLite2) :material-flask:
Geographic IP lookup for analytics visitor location tracking. Requires a free MaxMind account.
MaxMind license key. When set, the GeoLite2-City database auto-downloads at startup.
GEOIP_DB_PATH
/data/geoip/GeoLite2-City.mmdb
Path to the GeoLite2 database file inside the container.
Control Panel Agent (CCP) :material-flask:
Remote management agent for the Changemaker Control Panel — enables centralized multi-instance management.
Variable
Default
Description
ENABLE_CCP_AGENT
false
:material-flask: Enable the CCP remote management agent.
CCP_URL
(empty)
URL of the Changemaker Control Panel server.
CCP_INVITE_CODE
(empty)
One-time invite code for agent registration with the control panel.
CCP_AGENT_URL
(empty)
How the CCP can reach this agent (must be externally accessible).
CCP_AGENT_PORT
7443
Agent listener port.
Container Registry :material-tune-variant:
Settings for pulling pre-built production images from the Gitea container registry.
Variable
Default
Description
GITEA_REGISTRY
gitea.bnkops.com/admin
Registry hostname and namespace for pulling images.
IMAGE_TAG
(empty)
Image tag to pull. Set to a commit SHA or latest for pre-built images. Leave empty (defaults to local) to build from source.
COMPOSE_PROFILES
(empty)
Docker Compose profiles to activate. Set to monitoring to include Prometheus/Grafana/Alertmanager in every docker compose up -d.
GITEA_REGISTRY_USER
admin
Registry username for docker login and the registry status API endpoint.
GITEA_REGISTRY_PASS
(empty)
Registry password for the status API endpoint. For docker push/pull, use docker login gitea.bnkops.com.
GITEA_REGISTRY_API_TOKEN
(empty)
API token for the remote registry (gitea.bnkops.com). Used by build-release.sh --upload to publish release tarballs. Create at Gitea → User Settings → Applications. Not the same as GITEA_API_TOKEN.
Internal settings for the admin dashboard's service status panel and container management.
Variable
Default
Description
DOCKER_NETWORK_NAME
changemaker-lite
Docker bridge network name. Used by the dashboard to auto-discover containers.
DOCKER_PROXY_URL
http://docker-socket-proxy:2375
Read-only Docker socket proxy URL for container inspection.
NEWT_CONTAINER_NAME
newt-changemaker
Newt tunnel container name (for restart/status checks).
NEWT_COMPOSE_SERVICE
newt
Docker Compose service name for the Newt container.
Embed Proxy Ports :material-tune-variant:
Dedicated nginx ports for iframe embedding services in the admin dashboard without requiring DNS/subdomains. Change these to avoid port conflicts when running multiple instances on one host.
Variable
Default
Description
NOCODB_EMBED_PORT
8881
NocoDB iframe port.
N8N_EMBED_PORT
8882
n8n iframe port.
GITEA_EMBED_PORT
8883
Gitea iframe port.
MAILHOG_EMBED_PORT
8884
MailHog iframe port.
MINI_QR_EMBED_PORT
8885
Mini QR iframe port.
EXCALIDRAW_EMBED_PORT
8886
Excalidraw iframe port.
HOMEPAGE_EMBED_PORT
8887
Homepage iframe port.
VAULTWARDEN_EMBED_PORT
8890
Vaultwarden iframe port.
ROCKETCHAT_EMBED_PORT
8891
Rocket.Chat iframe port.
GANCIO_EMBED_PORT
8892
Gancio iframe port.
JITSI_EMBED_PORT
8893
Jitsi iframe port.
GRAFANA_EMBED_PORT
8894
Grafana iframe port.
ALERTMANAGER_EMBED_PORT
8895
Alertmanager iframe port.
Gitea Docs Version History :material-tune-variant:
Settings for the documentation version history feature (backed by Gitea repository commits).
Variable
Default
Description
GITEA_DOCS_REPO
admin/changemaker.lite
Gitea repository path for docs version history.
GITEA_DOCS_PREFIX
mkdocs/docs
Path prefix within the repository where documentation files live.
GITEA_DOCS_BRANCH
v2
Git branch to query for version history.
GITEA_ADMIN_PASSWORD
(empty)
Gitea admin password. Used once during initial setup to create an API token, then can be cleared.
Full PostgreSQL connection string. Only used when running Prisma CLI on the host (npx prisma migrate dev). Docker containers resolve the database hostname internally via Docker Compose environment variables.
Generating Secrets
Use these commands to generate all required secrets at once:
!!! tip
Copy the output and paste the values into your .env file. The INITIAL_ADMIN_PASSWORD uses base64 encoding to ensure it contains uppercase, lowercase, and digits (meeting the password policy).
Minimal vs Full Deployment
=== "Minimal (Core Only)"
For a basic deployment with campaigns, map, and admin:
```bash title="Required variables"
V2_POSTGRES_PASSWORD=...
REDIS_PASSWORD=...
JWT_ACCESS_SECRET=...
JWT_REFRESH_SECRET=...
JWT_INVITE_SECRET=...
ENCRYPTION_KEY=...
INITIAL_ADMIN_PASSWORD=...
```
```bash title="Start services"
docker compose up -d v2-postgres redis api admin
```
=== "Full Stack"
For the complete platform including media, newsletters, monitoring, and all services:
```bash title="Additional variables needed"
# Everything above, plus:
ENABLE_MEDIA_FEATURES=true
ENABLE_PAYMENTS=true
ENABLE_CHAT=true
ENABLE_MEET=true
ENABLE_SMS=true
LISTMONK_SYNC_ENABLED=true
GANCIO_SYNC_ENABLED=true
LISTMONK_DB_PASSWORD=...
LISTMONK_WEB_ADMIN_PASSWORD=...
LISTMONK_API_TOKEN=...
NC_ADMIN_PASSWORD=...
GITEA_DB_PASSWD=...
GITEA_DB_ROOT_PASSWORD=...
N8N_ENCRYPTION_KEY=...
N8N_USER_PASSWORD=...
VAULTWARDEN_ADMIN_TOKEN=...
ROCKETCHAT_ADMIN_PASSWORD=...
MONGO_ROOT_PASSWORD=...
GANCIO_ADMIN_PASSWORD=...
JITSI_APP_SECRET=...
JITSI_JICOFO_AUTH_PASSWORD=...
JITSI_JVB_AUTH_PASSWORD=...
JVB_ADVERTISE_IP=your.public.ip.here
EMAIL_TEST_MODE=false
SMTP_HOST=smtp.your-provider.com
SMTP_PORT=587
SMTP_USER=you@example.com
SMTP_PASS=your-smtp-password
```
```bash title="Start services"
docker compose up -d
docker compose --profile monitoring up -d
```