--- title: Environment Variables description: Complete reference for every .env variable in Changemaker Lite. icon: material/file-cog --- # Environment Variables Changemaker Lite uses a single `.env` file at the project root to configure all services. Copy the example file to get started: ```bash cp .env.example .env ``` !!! danger "Security Essentials" - **Change every** `REQUIRED_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`. | --- ## PostgreSQL (Main Database) :material-alert-circle:{ .text-red } 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:{ .text-red } | 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_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:{ .text-red } | 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 production (`NODE_ENV=production`). | --- ## Initial Admin Account :material-alert-circle:{ .text-red } 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. | --- ## 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:{ .text-red } 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). | | `LISTMONK_DB_USER` | `listmonk` | Listmonk database user. | | `LISTMONK_DB_PASSWORD` | — | :material-alert-circle:{ .text-red } Listmonk database password. | | `LISTMONK_DB_NAME` | `listmonk` | Listmonk database name. | | `LISTMONK_WEB_ADMIN_USER` | `admin` | Login for the Listmonk web dashboard. | | `LISTMONK_WEB_ADMIN_PASSWORD` | — | :material-alert-circle:{ .text-red } Password for the Listmonk web dashboard. | | `LISTMONK_API_USER` | `v2-api` | API user for programmatic access (auto-created by init container). | | `LISTMONK_API_TOKEN` | — | Token for API user. Generate with `openssl rand -hex 16`. | | `LISTMONK_ADMIN_USER` | `v2-api` | Same as `LISTMONK_API_USER` (used by the sync service). | | `LISTMONK_ADMIN_PASSWORD` | — | Same as `LISTMONK_API_TOKEN`. | | `LISTMONK_SYNC_ENABLED` | `false` | :material-flask: Set to `true` to sync participants/locations/users to Listmonk lists. | | `LISTMONK_WEBHOOK_SECRET` | *(empty)* | Shared secret for Listmonk webhook callbacks. | | `LISTMONK_PROXY_PORT` | `9002` | Nginx proxy port for Listmonk. | ??? example "Listmonk SMTP settings" Listmonk has its own SMTP configuration, separate from the main platform's: | Variable | Default | Description | |----------|---------|-------------| | `LISTMONK_SMTP_HOST` | `mailhog-changemaker` | SMTP host for Listmonk. | | `LISTMONK_SMTP_PORT` | `1025` | SMTP port. | | `LISTMONK_SMTP_USER` | *(empty)* | SMTP username. | | `LISTMONK_SMTP_PASSWORD` | *(empty)* | SMTP password. | | `LISTMONK_SMTP_TLS_TYPE` | `none` | TLS mode: `none`, `STARTTLS`, or `TLS`. | | `LISTMONK_SMTP_FROM` | `Changemaker Lite ` | From address for newsletters. | --- ## Represent API (Canadian Electoral Data) | Variable | Default | Description | |----------|---------|-------------| | `REPRESENT_API_URL` | `https://represent.opennorth.ca` | OpenNorth Represent API endpoint. Used for postal code → representative lookups. No API key required. | --- ## NocoDB (Data Browser) :material-tune-variant: Read-only database browser. Useful for inspecting data without SQL. | Variable | Default | Description | |----------|---------|-------------| | `NOCODB_V2_PORT` / `NOCODB_PORT` | `8091` | Host port for the NocoDB web UI. | | `NOCODB_URL` | `http://changemaker-v2-nocodb:8080` | Internal Docker URL. | | `NC_ADMIN_EMAIL` | `admin@cmlite.org` | NocoDB admin email. | | `NC_ADMIN_PASSWORD` | — | :material-alert-circle:{ .text-red } NocoDB admin password. | --- ## Media Manager :material-flask: Video library with upload, analytics, scheduling, and a public gallery. | Variable | Default | Description | |----------|---------|-------------| | `ENABLE_MEDIA_FEATURES` | `false` | :material-flask: Set to `true` to enable the media system. | | `MEDIA_API_PORT` | `4100` | Fastify media API port. | | `MEDIA_API_PUBLIC_URL` | `http://media-api:4100` | Internal URL for the media API container. | | `MEDIA_ROOT` | `/media/library` | Path to the video library inside the container. | | `MEDIA_UPLOADS` | `/media/uploads` | Path for upload processing. | | `MAX_UPLOAD_SIZE_GB` | `10` | Maximum single-file upload size in gigabytes. | | `PUBLIC_MEDIA_PORT` | `3100` | Public media gallery server port. | | `VIDEO_PLAYER_DEBUG` | `false` | Enable verbose video player logging. | ??? example "Analytics & scheduling settings" | Variable | Default | Description | |----------|---------|-------------| | `VIDEO_ANALYTICS_RETENTION_DAYS` | `90` | Days to retain analytics data. GDPR-compliant with IP hashing. | | `VIDEO_ANALYTICS_IP_HASHING_ENABLED` | `true` | Hash viewer IPs for privacy. | | `VIDEO_SCHEDULE_DEFAULT_TIMEZONE` | `UTC` | Default timezone for scheduled publishing. | | `VIDEO_SCHEDULE_NOTIFICATION_ENABLED` | `true` | Notify on scheduled publish/unpublish. | | `VIDEO_PREVIEW_LINK_EXPIRY_HOURS` | `24` | Preview link JWT expiry (hours). | --- ## Gitea (Git Hosting) :material-tune-variant: Self-hosted Git repository. Optional service. | Variable | Default | Description | |----------|---------|-------------| | `GITEA_URL` | `http://gitea-changemaker:3000` | Internal container URL for Gitea. | | `GITEA_PORT` / `GITEA_WEB_PORT` | `3030` | Gitea web UI port. | | `GITEA_SSH_PORT` | `2222` | Gitea SSH port for git operations. | | `GITEA_DB_TYPE` | `mysql` | Database type (Gitea uses its own MySQL). | | `GITEA_DB_HOST` | `gitea-db:3306` | Internal database host. | | `GITEA_DB_NAME` | `gitea` | Database name. | | `GITEA_DB_USER` | `gitea` | Database user. | | `GITEA_DB_PASSWD` | — | :material-alert-circle:{ .text-red } Gitea database password. | | `GITEA_DB_ROOT_PASSWORD` | — | :material-alert-circle:{ .text-red } MySQL root password for Gitea. | | `GITEA_ROOT_URL` | `https://git.cmlite.org` | Public-facing URL for Gitea. | | `GITEA_DOMAIN` | `git.cmlite.org` | Domain used in git clone URLs. | ??? example "Gitea Docs Comments" Enable comments on MkDocs documentation pages, backed by Gitea Issues. | Variable | Default | Description | |----------|---------|-------------| | `GITEA_COMMENTS_ENABLED` | `false` | :material-flask: Enable comments on MkDocs pages. | | `GITEA_API_TOKEN` | *(empty)* | Personal access token with repo write scope. Create in Gitea → Settings → Applications. | | `GITEA_COMMENTS_REPO_OWNER` | *(empty)* | Gitea username that owns the docs-comments repo. | | `GITEA_COMMENTS_REPO_NAME` | `docs-comments` | Repository name (auto-created via admin setup). | | `GITEA_OAUTH_CLIENT_ID` | *(empty)* | OAuth2 application client ID (create in Gitea → Settings → Applications → OAuth2). | | `GITEA_OAUTH_CLIENT_SECRET` | *(empty)* | OAuth2 application client secret. | --- ## n8n (Workflow Automation) :material-tune-variant: | Variable | Default | Description | |----------|---------|-------------| | `N8N_PORT` | `5678` | n8n web UI port. | | `N8N_HOST` | `n8n.cmlite.org` | Public hostname for n8n. | | `N8N_ENCRYPTION_KEY` | — | :material-alert-circle:{ .text-red } Encryption key for n8n credentials storage. | | `N8N_USER_EMAIL` | `admin@example.com` | Initial n8n admin email. | | `N8N_USER_PASSWORD` | — | :material-alert-circle:{ .text-red } Initial n8n admin password. | | `GENERIC_TIMEZONE` | `UTC` | Timezone for n8n cron triggers. | --- ## MkDocs (Documentation) | Variable | Default | Description | |----------|---------|-------------| | `MKDOCS_PORT` | `4003` | MkDocs dev server port (live preview). | | `MKDOCS_SITE_SERVER_PORT` | `4004` | MkDocs static site server port. | | `BASE_DOMAIN` | `https://cmlite.org` | Base URL for generated documentation links. | | `MKDOCS_PREVIEW_URL` | `http://mkdocs:8000` | Internal container URL. | | `MKDOCS_DOCS_PATH` | `/mkdocs/docs` | Documentation source directory inside the container. | --- ## Code Server (Web IDE) | Variable | Default | Description | |----------|---------|-------------| | `CODE_SERVER_PORT` | `8888` | Code Server web UI port. | | `CODE_SERVER_URL` | `http://code-server:8080` | Internal container URL. | | `USER_NAME` | `coder` | User account inside the Code Server container. | --- ## Homepage (Service Dashboard) | Variable | Default | Description | |----------|---------|-------------| | `HOMEPAGE_PORT` | `3010` | Homepage web UI port. | | `HOMEPAGE_EMBED_PORT` | `8887` | Port for iframe embedding in admin. | | `HOMEPAGE_VAR_BASE_URL` | `http://localhost` | Base URL used in Homepage service links. | --- ## Mini QR (QR Code Generator) | Variable | Default | Description | |----------|---------|-------------| | `MINI_QR_PORT` | `8089` | Mini QR direct access port. | | `MINI_QR_URL` | `http://mini-qr:8080` | Internal container URL. | | `MINI_QR_EMBED_PORT` | `8885` | Port for iframe embedding (walk sheets, cut exports). | --- ## Excalidraw (Whiteboard) | Variable | Default | Description | |----------|---------|-------------| | `EXCALIDRAW_PORT` | `8090` | Excalidraw web UI port. | | `EXCALIDRAW_URL` | `http://excalidraw-changemaker:80` | Internal container URL. | | `EXCALIDRAW_EMBED_PORT` | `8886` | Port for iframe embedding. | | `EXCALIDRAW_WS_URL` | `wss://draw.cmlite.org` | WebSocket URL for real-time collaboration. | --- ## Vaultwarden (Password Manager) :material-tune-variant: Self-hosted Bitwarden-compatible password manager. Optional service. | Variable | Default | Description | |----------|---------|-------------| | `VAULTWARDEN_PORT` | `8445` | Vaultwarden web UI port. | | `VAULTWARDEN_URL` | `http://vaultwarden-changemaker:80` | Internal container URL. | | `VAULTWARDEN_EMBED_PORT` | `8890` | Port for iframe embedding in admin. | | `VAULTWARDEN_ADMIN_TOKEN` | *(empty)* | 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. | | `ROCKETCHAT_ADMIN_USER` | `rcadmin` | Rocket.Chat admin username. | | `ROCKETCHAT_ADMIN_PASSWORD` | — | :material-alert-circle:{ .text-red } Rocket.Chat admin password. | | `ROCKETCHAT_URL` | `http://rocketchat-changemaker:3000` | Internal container URL. | | `ROCKETCHAT_EMBED_PORT` | `8891` | Port for iframe embedding in admin. | --- ## Gancio (Event Management) :material-flask: Self-hosted event management platform. Uses the shared PostgreSQL database (auto-created by `init-gancio-db.sh`). | Variable | Default | Description | |----------|---------|-------------| | `GANCIO_PORT` | `8092` | Gancio web UI port. | | `GANCIO_URL` | `http://gancio-changemaker:13120` | Internal container URL. | | `GANCIO_EMBED_PORT` | `8892` | Port for iframe embedding in admin. | | `GANCIO_BASE_URL` | `https://events.cmlite.org` | Public-facing URL for Gancio. Used in event links. | | `GANCIO_ADMIN_USER` | `admin` | Gancio admin username for shift-to-event sync (OAuth login). | | `GANCIO_ADMIN_PASSWORD` | — | :material-alert-circle:{ .text-red } Gancio admin password. | | `GANCIO_SYNC_ENABLED` | `false` | :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. --- ## SMS Campaigns (Termux Android Bridge) :material-flask: 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. --- ## MailHog (Development Email) | Variable | Default | Description | |----------|---------|-------------| | `MAILHOG_SMTP_PORT` | `1025` | SMTP port for capturing emails. | | `MAILHOG_WEB_PORT` | `8025` | Web UI to view captured emails. | --- ## NAR (National Address Register) :material-tune-variant: 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. | Download NAR data from [Statistics Canada](https://www150.statcan.gc.ca/n1/pub/46-26-0002/462600022022001-eng.htm). --- ## Geocoding :material-tune-variant: 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](https://www.mapbox.com/pricing). | | `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](https://github.com/fosrl/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](../deployment/index.md) for the full guide. --- ## Monitoring :material-flask: These services are behind the `monitoring` Docker Compose profile. Start them with: ```bash 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. | | `BUNKER_OPS_REMOTE_WRITE_URL` | *(empty)* | VictoriaMetrics `remote_write` endpoint (e.g., `https://ops.example.com/api/v1/write`). | --- ## Generating Secrets Use these commands to generate all required secrets at once: ```bash # JWT secrets (two separate values) echo "JWT_ACCESS_SECRET=$(openssl rand -hex 32)" echo "JWT_REFRESH_SECRET=$(openssl rand -hex 32)" # Encryption key (must differ from JWT secrets) echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" # Database and Redis passwords echo "V2_POSTGRES_PASSWORD=$(openssl rand -hex 24)" echo "REDIS_PASSWORD=$(openssl rand -hex 24)" # Listmonk echo "LISTMONK_DB_PASSWORD=$(openssl rand -hex 24)" echo "LISTMONK_WEB_ADMIN_PASSWORD=$(openssl rand -hex 16)" LISTMONK_TOKEN=$(openssl rand -hex 16) echo "LISTMONK_API_TOKEN=$LISTMONK_TOKEN" echo "LISTMONK_ADMIN_PASSWORD=$LISTMONK_TOKEN" # Supporting services echo "GITEA_DB_PASSWD=$(openssl rand -hex 24)" echo "GITEA_DB_ROOT_PASSWORD=$(openssl rand -hex 24)" echo "N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)" echo "N8N_USER_PASSWORD=$(openssl rand -hex 16)" echo "NC_ADMIN_PASSWORD=$(openssl rand -hex 16)" echo "INITIAL_ADMIN_PASSWORD=$(openssl rand -base64 18)" # Vaultwarden echo "VAULTWARDEN_ADMIN_TOKEN=$(openssl rand -hex 32)" # Rocket.Chat echo "ROCKETCHAT_ADMIN_PASSWORD=$(openssl rand -hex 16)" # Gancio echo "GANCIO_ADMIN_PASSWORD=$(openssl rand -hex 16)" # Jitsi Meet echo "JITSI_APP_SECRET=$(openssl rand -hex 32)" echo "JITSI_JICOFO_AUTH_PASSWORD=$(openssl rand -hex 16)" echo "JITSI_JVB_AUTH_PASSWORD=$(openssl rand -hex 16)" ``` !!! 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=... 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=... 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 ```