Skip to content

# Environment Variables

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

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
--- | ---
| Must be set before first run
| Has a working default; change for production
| 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)

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` | — | Must change. Database password.
`V2_POSTGRES_DB` | `changemaker_v2` | Database name.
`V2_POSTGRES_PORT` | `5433` | Host port mapping. The container listens on `5432` internally.

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

Variable | Default | Description
--- | --- | ---
`JWT_ACCESS_SECRET` | — | Secret for signing access tokens. Generate with `openssl rand -hex 32`.
`JWT_REFRESH_SECRET` | — | Secret for signing refresh tokens. Must differ from the access secret.
`JWT_INVITE_SECRET` | — | 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

Variable | Default | Description
--- | --- | ---
`ENCRYPTION_KEY` | — | 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

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.

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

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` | — | 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.

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

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

Shared by rate limiting, BullMQ job queues, geocoding cache, and session data.

Variable | Default | Description
--- | --- | ---
`REDIS_PASSWORD` | — | Must change. Redis requires authentication.
`REDIS_URL` | `redis://:${REDIS_PASSWORD}@redis-changemaker:6379` | Full connection URL. Uses the password variable automatically.

## Payments (Stripe)

Variable | Default | Description
--- | --- | ---
`ENABLE_PAYMENTS` | `false` | 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

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.

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)

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` | — | 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` | — | 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` | 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.

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 <noreply@cmlite.org>` | 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)

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` | — | NocoDB admin password.

## Media Manager

Video library with upload, analytics, scheduling, and a public gallery.

Variable | Default | Description
--- | --- | ---
`ENABLE_MEDIA_FEATURES` | `false` | 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.

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)

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` | — | Gitea database password.
`GITEA_DB_ROOT_PASSWORD` | — | 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.

Gitea Docs Comments

Enable comments on MkDocs documentation pages, backed by Gitea Issues.

Variable | Default | Description
--- | --- | ---
`GITEA_COMMENTS_ENABLED` | `false` | 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)

Variable | Default | Description
--- | --- | ---
`N8N_PORT` | `5678` | n8n web UI port.
`N8N_HOST` | `n8n.cmlite.org` | Public hostname for n8n.
`N8N_ENCRYPTION_KEY` | — | Encryption key for n8n credentials storage.
`N8N_USER_EMAIL` | `admin@example.com` | Initial n8n admin email.
`N8N_USER_PASSWORD` | — | 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-changemaker:8443` | Internal container URL.

## 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)

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.

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)

Self-hosted team chat for volunteer coordination. Requires MongoDB (auto-configured).

Variable | Default | Description
--- | --- | ---
`ENABLE_CHAT` | `false` | 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` | — | Rocket.Chat admin password.
`ROCKETCHAT_URL` | `http://rocketchat-changemaker:3000` | Internal container URL.
`ROCKETCHAT_EMBED_PORT` | `8891` | Port for iframe embedding in admin.
`MONGO_ROOT_USER` | `rocketchat` | MongoDB admin username.
`MONGO_ROOT_PASSWORD` | — | MongoDB admin password. MongoDB runs with `--auth` enabled.

## Gancio (Event Management)

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` | — | Gancio admin password.
`GANCIO_SYNC_ENABLED` | `false` | Set to `true` to enable automatic shift → Gancio event synchronization.

## Jitsi Meet (Video Conferencing)

Self-hosted video conferencing with JWT authentication. Integrates with Rocket.Chat for in-channel video calls.

Variable | Default | Description
--- | --- | ---
`ENABLE_MEET` | `false` | 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` | — | 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.

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)

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` | 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).

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)

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.

## Geocoding

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

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

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

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

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` | 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` | 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)

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` | 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`).

## Social, People & Analytics

Feature flags for the social graph, CRM people module, and analytics dashboard.

Variable | Default | Description
--- | --- | ---
`ENABLE_SOCIAL` | `false` | Enable the social module (friendships, challenges, spotlights, referrals). The initial default; once saved in admin Settings, the DB value is authoritative.
`ENABLE_PEOPLE` | `false` | Enable the CRM people module. The initial default; once saved in admin Settings, the DB value is authoritative.
`ENABLE_ANALYTICS` | `false` | 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)

Geographic IP lookup for analytics visitor location tracking. Requires a free MaxMind account.

Variable | Default | Description
--- | --- | ---
`MAXMIND_ACCOUNT_ID` | (empty) | MaxMind account ID. Sign up free.
`MAXMIND_LICENSE_KEY` | (empty) | 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)

Remote management agent for the Changemaker Control Panel — enables centralized multi-instance management.

Variable | Default | Description
--- | --- | ---
`ENABLE_CCP_AGENT` | `false` | 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

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

## Docker / Container Management

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

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

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.

## Prisma CLI (Host-Side)

Variable | Default | Description
--- | --- | ---
`DATABASE_URL` | `postgresql://changemaker:YOUR_POSTGRES_PASSWORD@localhost:5433/changemaker_v2` | 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:

    # JWT secrets (three separate values)
    echo "JWT_ACCESS_SECRET=$(openssl rand -hex 32)"
    echo "JWT_REFRESH_SECRET=$(openssl rand -hex 32)"
    echo "JWT_INVITE_SECRET=$(openssl rand -hex 32)"

    # Encryption key (must differ from JWT secrets)
    echo "ENCRYPTION_KEY=$(openssl rand -hex 32)"

    # Security extras (key separation)
    echo "GITEA_SSO_SECRET=$(openssl rand -hex 32)"
    echo "SERVICE_PASSWORD_SALT=$(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 + MongoDB
    echo "ROCKETCHAT_ADMIN_PASSWORD=$(openssl rand -hex 16)"
    echo "MONGO_ROOT_PASSWORD=$(openssl rand -hex 24)"

    # 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)Full Stack

For a basic deployment with campaigns, map, and admin:

Required variables

    V2_POSTGRES_PASSWORD=...
    REDIS_PASSWORD=...
    JWT_ACCESS_SECRET=...
    JWT_REFRESH_SECRET=...
    JWT_INVITE_SECRET=...
    ENCRYPTION_KEY=...
    INITIAL_ADMIN_PASSWORD=...

Start services

    docker compose up -d v2-postgres redis api admin

For the complete platform including media, newsletters, monitoring, and all services:

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

Start services

    docker compose up -d
    docker compose --profile monitoring up -d