24 KiB
Environment Variables Reference
Overview
Changemaker Lite V2 uses over 100 environment variables to configure services, credentials, and feature flags. This document provides a complete reference organized by functional area.
Configuration File: .env (never committed to Git)
Template: .env.example (committed, safe to share)
Validation: api/src/config/env.ts (Zod schema validates all variables on startup)
Quick Start
Initial Setup
# Copy template
cp .env.example .env
# Generate secrets
openssl rand -hex 32 # For JWT_ACCESS_SECRET
openssl rand -hex 32 # For JWT_REFRESH_SECRET
openssl rand -hex 32 # For ENCRYPTION_KEY (must differ from JWT secrets!)
openssl rand -hex 16 # For LISTMONK_API_TOKEN
# Edit .env
nano .env
Minimal Required Variables
Must set before first start:
V2_POSTGRES_PASSWORD=<strong-password>
REDIS_PASSWORD=<strong-password>
JWT_ACCESS_SECRET=<openssl-rand-hex-32>
JWT_REFRESH_SECRET=<openssl-rand-hex-32>
ENCRYPTION_KEY=<openssl-rand-hex-32> # Production only
All other variables have safe defaults for development.
General Configuration
| Variable | Default | Required | Description |
|---|---|---|---|
NODE_ENV |
development |
No | Environment mode (development | production) |
DOMAIN |
cmlite.org |
No | Base domain for subdomain routing |
USER_ID |
1000 |
No | Host user ID for volume permissions |
GROUP_ID |
1000 |
No | Host group ID for volume permissions |
DOCKER_GROUP_ID |
984 |
No | Docker group ID (for homepage container) |
Usage:
NODE_ENV=production docker compose up -d
V2 PostgreSQL
| Variable | Default | Required | Description |
|---|---|---|---|
V2_POSTGRES_USER |
changemaker |
No | PostgreSQL username |
V2_POSTGRES_PASSWORD |
CHANGE_ME_STRONG_PASSWORD |
Yes | PostgreSQL password |
V2_POSTGRES_DB |
changemaker_v2 |
No | Database name |
V2_POSTGRES_PORT |
5433 |
No | Host port (container always 5432) |
Connection String (auto-generated in docker-compose.yml):
postgresql://changemaker:PASSWORD@changemaker-v2-postgres:5432/changemaker_v2
Port Binding: 127.0.0.1:5433:5432 (localhost only for security)
Important: Change V2_POSTGRES_PASSWORD before production deployment.
JWT Authentication
| Variable | Default | Required | Description |
|---|---|---|---|
JWT_ACCESS_SECRET |
GENERATE_WITH_openssl_rand_hex_32 |
Yes | Access token secret (15min lifespan) |
JWT_REFRESH_SECRET |
GENERATE_WITH_openssl_rand_hex_32 |
Yes | Refresh token secret (7 day lifespan) |
JWT_ACCESS_EXPIRY |
15m |
No | Access token expiration (15m, 1h, etc.) |
JWT_REFRESH_EXPIRY |
7d |
No | Refresh token expiration (7d, 30d, etc.) |
ENCRYPTION_KEY |
GENERATE_WITH_openssl_rand_hex_32 |
Yes (prod) | DB encryption key for SMTP passwords, etc. |
Security Requirements (enforced by Zod schema):
JWT_ACCESS_SECRETmust be 32+ charactersJWT_REFRESH_SECRETmust be 32+ charactersENCRYPTION_KEYmust be 32+ characters and differ from JWT secrets
Generation:
export JWT_ACCESS_SECRET=$(openssl rand -hex 32)
export JWT_REFRESH_SECRET=$(openssl rand -hex 32)
export ENCRYPTION_KEY=$(openssl rand -hex 32)
echo "JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET}" >> .env
echo "JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}" >> .env
echo "ENCRYPTION_KEY=${ENCRYPTION_KEY}" >> .env
Production Note: ENCRYPTION_KEY required in production (dev mode allows empty for testing).
Redis
| Variable | Default | Required | Description |
|---|---|---|---|
REDIS_PASSWORD |
CHANGE_ME_REDIS_PASSWORD |
Yes | Redis authentication password |
REDIS_URL |
redis://:PASSWORD@redis-changemaker:6379 |
No | Full connection URL (auto-generated) |
Format: redis://[:<password>@]<host>:<port>[/<db>]
Example:
REDIS_PASSWORD=mySecurePassword123
REDIS_URL=redis://:mySecurePassword123@redis-changemaker:6379
Security Note: As of Security Audit 2025-02-11, Redis requires authentication in production.
Docker Command (in docker-compose.yml):
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru --requirepass "${REDIS_PASSWORD}"
API Configuration
| Variable | Default | Required | Description |
|---|---|---|---|
API_PORT |
4000 |
No | Express API port (host) |
API_URL |
http://localhost:4000 |
No | Public API URL (for emails, OAuth redirects) |
CORS_ORIGINS |
http://localhost:3000,http://localhost |
No | Allowed CORS origins (comma-separated) |
Production Example:
API_PORT=4000
API_URL=https://api.cmlite.org
CORS_ORIGINS=https://app.cmlite.org,https://cmlite.org
CORS Note: List all frontend origins (admin, public site, media gallery).
Admin GUI
| Variable | Default | Required | Description |
|---|---|---|---|
ADMIN_PORT |
3000 |
No | Admin GUI port (host) |
ADMIN_URL |
http://localhost:3000 |
No | Public admin URL |
VITE_API_URL |
http://changemaker-v2-api:4000 |
No | API URL for Vite proxy (Docker internal) |
VITE_MEDIA_API_URL |
http://changemaker-media-api:4100 |
No | Media API URL for Vite proxy |
VITE_MKDOCS_URL |
http://mkdocs-changemaker:8000 |
No | MkDocs URL for iframe embed |
Development vs Production:
Development (Docker):
VITE_API_URL=http://changemaker-v2-api:4000 # Container name
VITE_MEDIA_API_URL=http://changemaker-media-api:4100
Development (local):
VITE_API_URL=http://localhost:4000 # Localhost
VITE_MEDIA_API_URL=http://localhost:4100
Production: Vite build embeds these URLs at build time.
Nginx
| Variable | Default | Required | Description |
|---|---|---|---|
NGINX_HTTP_PORT |
80 |
No | HTTP port |
NGINX_HTTPS_PORT |
443 |
No | HTTPS port |
Port Mapping (docker-compose.yml):
nginx:
ports:
- "80:80"
- "443:443"
- "8881:8881" # NocoDB embed proxy
- "8882:8882" # n8n embed proxy
- "8883:8883" # Gitea embed proxy
- "8884:8884" # MailHog embed proxy
- "8885:8885" # Mini QR embed proxy
Custom Ports (if 80/443 occupied):
NGINX_HTTP_PORT=8080
NGINX_HTTPS_PORT=8443
SMTP / Email
| Variable | Default | Required | Description |
|---|---|---|---|
SMTP_HOST |
mailhog-changemaker |
No | SMTP server hostname |
SMTP_PORT |
1025 |
No | SMTP server port |
SMTP_USER |
`` | No | SMTP username (empty for MailHog) |
SMTP_PASS |
`` | No | SMTP password |
SMTP_FROM |
noreply@cmlite.org |
No | Default sender email |
SMTP_FROM_NAME |
Changemaker Lite |
No | Default sender name |
EMAIL_TEST_MODE |
true |
No | Route all emails to MailHog (dev mode) |
TEST_EMAIL_RECIPIENT |
admin@cmlite.org |
No | Override recipient in test mode |
Development (MailHog):
SMTP_HOST=mailhog-changemaker
SMTP_PORT=1025
SMTP_USER=
SMTP_PASS=
EMAIL_TEST_MODE=true
Production (e.g., ProtonMail):
SMTP_HOST=smtp.protonmail.ch
SMTP_PORT=587
SMTP_USER=your@email.com
SMTP_PASS=your-app-password
EMAIL_TEST_MODE=false
Test Mode Behavior:
true: All emails sent to MailHog (visible at http://localhost:8025)false: Emails sent to real recipients via SMTP
SiteSettings Override: Admins can override SMTP config via /app/settings (stored encrypted in DB).
Listmonk
Database
| Variable | Default | Required | Description |
|---|---|---|---|
LISTMONK_DB_PORT |
5432 |
No | Listmonk PostgreSQL port |
LISTMONK_DB_USER |
listmonk |
No | Database username |
LISTMONK_DB_PASSWORD |
CHANGE_ME_LISTMONK_PASSWORD |
Yes | Database password |
LISTMONK_DB_NAME |
listmonk |
No | Database name |
Web Admin
| Variable | Default | Required | Description |
|---|---|---|---|
LISTMONK_PORT |
9001 |
No | Listmonk web UI port |
LISTMONK_WEB_ADMIN_USER |
admin |
No | Web UI username |
LISTMONK_WEB_ADMIN_PASSWORD |
CHANGE_ME_LISTMONK_ADMIN |
Yes | Web UI password |
API Integration
| Variable | Default | Required | Description |
|---|---|---|---|
LISTMONK_API_USER |
v2-api |
No | API user (auto-created by listmonk-init) |
LISTMONK_API_TOKEN |
GENERATE_WITH_openssl_rand_hex_16 |
Yes | API token (plaintext, not bcrypt) |
LISTMONK_ADMIN_USER |
v2-api |
No | Alias for API user (V2 uses this) |
LISTMONK_ADMIN_PASSWORD |
SAME_AS_LISTMONK_API_TOKEN |
Yes | Alias for API token |
LISTMONK_SYNC_ENABLED |
false |
No | Enable participant/location sync |
LISTMONK_PROXY_PORT |
9002 |
No | OAuth proxy port (for future integrations) |
API User Setup: The listmonk-init container auto-creates the API user by directly inserting into PostgreSQL.
Token Generation:
export LISTMONK_API_TOKEN=$(openssl rand -hex 16)
echo "LISTMONK_API_TOKEN=${LISTMONK_API_TOKEN}" >> .env
echo "LISTMONK_ADMIN_PASSWORD=${LISTMONK_API_TOKEN}" >> .env
Sync Behavior:
false: Manual sync only (default)true: Auto-sync participants/locations to Listmonk lists on signup/create
SMTP Configuration
| Variable | Default | Required | Description |
|---|---|---|---|
LISTMONK_SMTP_HOST |
mailhog-changemaker |
No | SMTP server for newsletters |
LISTMONK_SMTP_PORT |
1025 |
No | SMTP port |
LISTMONK_SMTP_USER |
`` | No | SMTP username |
LISTMONK_SMTP_PASSWORD |
`` | No | SMTP password |
LISTMONK_SMTP_TLS_TYPE |
none |
No | TLS mode (none | STARTTLS | TLS) |
LISTMONK_SMTP_FROM |
Changemaker Lite <noreply@cmlite.org> |
No | Newsletter sender |
listmonk-init Behavior: Configures dual SMTP providers (MailHog + production if credentials set).
Represent API
| Variable | Default | Required | Description |
|---|---|---|---|
REPRESENT_API_URL |
https://represent.opennorth.ca |
No | Represent API endpoint (Canadian electoral data) |
Free Public API: No authentication required.
Usage: Postal code → representative lookup for Influence campaigns.
NocoDB
| Variable | Default | Required | Description |
|---|---|---|---|
NOCODB_V2_PORT |
8091 |
No | NocoDB web UI port |
NOCODB_URL |
http://changemaker-v2-nocodb:8080 |
No | Internal NocoDB URL |
NC_ADMIN_EMAIL |
admin@cmlite.org |
No | Admin email |
NC_ADMIN_PASSWORD |
CHANGE_ME_NOCODB_PASSWORD |
Yes | Admin password |
NC_PUBLIC_URL |
http://localhost:8091 |
No | Public NocoDB URL |
Database Connection: Uses separate nocodb_meta database (auto-created by init-nocodb-db.sh).
Connection String:
pg://changemaker-v2-postgres:5432?u=changemaker&p=PASSWORD&d=nocodb_meta
Media Management
| Variable | Default | Required | Description |
|---|---|---|---|
ENABLE_MEDIA_FEATURES |
false |
No | Enable media manager features |
MEDIA_API_PORT |
4100 |
No | Fastify media API port |
MEDIA_API_PUBLIC_URL |
http://media-api:4100 |
No | Public media API URL |
MEDIA_ROOT |
/media/library |
No | Media library root path |
MEDIA_UPLOADS |
/media/uploads |
No | Upload staging directory |
MAX_UPLOAD_SIZE_GB |
10 |
No | Max video upload size (GB) |
PUBLIC_MEDIA_PORT |
3100 |
No | Public media gallery port |
VIDEO_PLAYER_DEBUG |
false |
No | Enable video.js debug logging |
Feature Flag: Set ENABLE_MEDIA_FEATURES=true to activate media routes.
Volume Mounts (in docker-compose.yml):
volumes:
- ${MEDIA_ROOT:-./media}:/media:ro # Library (read-only)
- ${MEDIA_ROOT:-./media}/local/inbox:/media/local/inbox:rw # Inbox (writable)
Supported Formats: MP4, MOV, AVI, MKV, WebM, M4V, FLV
Gitea
| Variable | Default | Required | Description |
|---|---|---|---|
GITEA_URL |
http://gitea-changemaker:3000 |
No | Internal Gitea URL |
GITEA_WEB_PORT |
3030 |
No | Gitea web UI port |
GITEA_SSH_PORT |
2222 |
No | Gitea SSH port (for git push/pull) |
GITEA_DB_TYPE |
mysql |
No | Database type |
GITEA_DB_HOST |
gitea-db:3306 |
No | MySQL hostname |
GITEA_DB_NAME |
gitea |
No | Database name |
GITEA_DB_USER |
gitea |
No | Database username |
GITEA_DB_PASSWD |
CHANGE_ME_GITEA_DB |
Yes | Database password |
GITEA_DB_ROOT_PASSWORD |
CHANGE_ME_GITEA_ROOT |
Yes | MySQL root password |
GITEA_ROOT_URL |
https://git.cmlite.org |
No | Public Gitea URL |
GITEA_DOMAIN |
git.cmlite.org |
No | Gitea domain |
First-Time Setup: Visit http://localhost:3030 to create admin account.
Git Commands:
# Clone via HTTP
git clone http://localhost:3030/user/repo.git
# Clone via SSH
git clone ssh://git@localhost:2222/user/repo.git
n8n
| Variable | Default | Required | Description |
|---|---|---|---|
N8N_URL |
http://n8n-changemaker:5678 |
No | Internal n8n URL |
N8N_PORT |
5678 |
No | n8n port |
N8N_HOST |
n8n.cmlite.org |
No | Public n8n hostname |
N8N_ENCRYPTION_KEY |
CHANGE_ME_N8N_KEY |
Yes | Workflow encryption key |
N8N_USER_EMAIL |
admin@example.com |
No | Default admin email |
N8N_USER_PASSWORD |
CHANGE_ME_N8N_PASSWORD |
Yes | Default admin password |
GENERIC_TIMEZONE |
UTC |
No | Workflow timezone |
First Start: n8n creates admin user with N8N_USER_EMAIL/N8N_USER_PASSWORD automatically.
Encryption Key: Used to encrypt credentials in workflows.
MkDocs
| Variable | Default | Required | Description |
|---|---|---|---|
MKDOCS_PORT |
4003 |
No | MkDocs live preview port |
MKDOCS_SITE_SERVER_PORT |
4001 |
No | MkDocs static site port |
BASE_DOMAIN |
https://cmlite.org |
No | Site URL for sitemap/canonical |
MKDOCS_PREVIEW_URL |
http://mkdocs:8000 |
No | Internal preview URL |
MKDOCS_DOCS_PATH |
/mkdocs/docs |
No | Documentation source path |
Port Change: Was 4000 in V1, changed to 4003 to avoid conflict with API.
Live Reload: http://localhost:4003 (updates on file save)
Static Build: http://localhost:4001 (Nginx-served production build)
Code Server
| Variable | Default | Required | Description |
|---|---|---|---|
CODE_SERVER_PORT |
8888 |
No | Code Server port |
CODE_SERVER_URL |
http://code-server:8080 |
No | Internal Code Server URL |
USER_NAME |
coder |
No | Code Server username |
Access: http://localhost:8888
Password: Set in configs/code-server/.config/code-server/config.yaml
Homepage
| Variable | Default | Required | Description |
|---|---|---|---|
HOMEPAGE_PORT |
3010 |
No | Homepage dashboard port |
HOMEPAGE_VAR_BASE_URL |
http://localhost |
No | Base URL for service links |
Configuration: Edit configs/homepage/services.yaml to customize dashboard.
Mini QR
| Variable | Default | Required | Description |
|---|---|---|---|
MINI_QR_PORT |
8089 |
No | Mini QR service port |
MINI_QR_URL |
http://mini-qr:8080 |
No | Internal Mini QR URL |
MINI_QR_EMBED_PORT |
8885 |
No | Nginx embed proxy port |
Usage: Walk sheets + cut exports embed QR codes via API or iframe.
MailHog
| Variable | Default | Required | Description |
|---|---|---|---|
MAILHOG_SMTP_PORT |
1025 |
No | SMTP port (internal only) |
MAILHOG_WEB_PORT |
8025 |
No | Web UI port |
Web UI: http://localhost:8025
SMTP: Only accessible from Docker network (not exposed to host).
NAR Import
| Variable | Default | Required | Description |
|---|---|---|---|
NAR_DATA_DIR |
/data |
No | Path to NAR data directory (in container) |
Host Mount (in docker-compose.yml):
volumes:
- ./data:/data:ro # Read-only NAR data
Data Structure:
./data/
└─ 202501/ (YYYYMM)
├─ Addresses/
│ ├─ Address_10.txt (PEI)
│ ├─ Address_24_part_1.txt (Quebec part 1)
│ └─ ...
└─ Locations/
├─ Location_10.txt
└─ ...
Download: https://www150.statcan.gc.ca/n1/pub/46-26-0002/462600022022001-eng.htm
Geocoding
| Variable | Default | Required | Description |
|---|---|---|---|
MAPBOX_API_KEY |
`` | No | Mapbox API key (optional, 100k free/month) |
GEOCODING_RATE_LIMIT_MS |
1100 |
No | Delay between provider requests (ms) |
GEOCODING_CACHE_ENABLED |
true |
No | Enable Redis caching |
GEOCODING_CACHE_TTL_HOURS |
24 |
No | Cache TTL in hours |
GOOGLE_MAPS_API_KEY |
`` | No | Google Maps API key (optional, paid) |
GOOGLE_MAPS_ENABLED |
false |
No | Enable Google geocoding provider |
GEOCODING_PARALLEL_ENABLED |
true |
No | Parallel geocoding for bulk imports |
GEOCODING_BATCH_SIZE |
10 |
No | Batch size for parallel geocoding |
BULK_GEOCODE_ENABLED |
true |
No | Enable bulk re-geocode feature |
BULK_GEOCODE_MAX_BATCH |
5000 |
No | Max locations per bulk geocode batch |
Providers (in fallback order):
- Nominatim (OpenStreetMap, free)
- ArcGIS (free tier)
- Photon (free)
- Mapbox (100k free/month, requires API key)
- LocationIQ (free tier)
- Google (paid, most accurate)
Recommendation: Add MAPBOX_API_KEY for better accuracy without cost.
Pangolin Tunnel
| Variable | Default | Required | Description |
|---|---|---|---|
PANGOLIN_API_URL |
https://api.bnkserve.org/v1 |
No | Pangolin API endpoint |
PANGOLIN_API_KEY |
`` | No | Pangolin API key |
PANGOLIN_ORG_ID |
`` | No | Organization ID (from setup wizard) |
PANGOLIN_SITE_ID |
`` | No | Site ID (from setup wizard) |
PANGOLIN_ENDPOINT |
https://pangolin.bnkserve.org |
No | Tunnel endpoint URL |
PANGOLIN_NEWT_ID |
`` | No | Newt connector ID |
PANGOLIN_NEWT_SECRET |
`` | No | Newt connector secret |
Setup Workflow:
- Visit
/app/pangolinin admin GUI - Enter
PANGOLIN_API_KEY - Create org → site → endpoint → resource
- Copy
NEWT_ID/NEWT_SECRETto.env - Restart Newt container
Manual Setup:
# Set API key
export PANGOLIN_API_KEY=your-api-key
# Create org (returns ORG_ID)
curl -H "Authorization: Bearer $PANGOLIN_API_KEY" \
https://api.bnkserve.org/v1/orgs \
-d '{"name":"My Organization"}'
# Create site (returns SITE_ID)
curl -H "Authorization: Bearer $PANGOLIN_API_KEY" \
https://api.bnkserve.org/v1/sites \
-d '{"org_id":"ORG_ID","name":"Production Site"}'
# Continue setup...
See Tunneling for complete guide.
Monitoring
Prometheus
| Variable | Default | Required | Description |
|---|---|---|---|
PROMETHEUS_PORT |
9090 |
No | Prometheus port |
Scrape Targets (configured in configs/prometheus/prometheus.yml):
changemaker-v2-api:4000/api/metrics(10s interval)redis-exporter:9121(15s interval)cadvisor:8080(15s interval)node-exporter:9100(15s interval)
Retention: 30 days (configured in docker-compose.yml command).
Grafana
| Variable | Default | Required | Description |
|---|---|---|---|
GRAFANA_PORT |
3001 |
No | Grafana port |
GRAFANA_ADMIN_PASSWORD |
admin |
No | Admin password |
GRAFANA_ROOT_URL |
http://localhost:3001 |
No | Public Grafana URL |
Default Login: admin / admin (change on first login)
Dashboards: 3 pre-configured dashboards auto-provisioned from configs/grafana/
Exporters
| Variable | Default | Required | Description |
|---|---|---|---|
CADVISOR_PORT |
8080 |
No | cAdvisor container metrics port |
NODE_EXPORTER_PORT |
9100 |
No | Node exporter system metrics port |
REDIS_EXPORTER_PORT |
9121 |
No | Redis exporter port |
Alertmanager
| Variable | Default | Required | Description |
|---|---|---|---|
ALERTMANAGER_PORT |
9093 |
No | Alertmanager port |
Configuration: Edit configs/alertmanager/alertmanager.yml for notification receivers.
Gotify
| Variable | Default | Required | Description |
|---|---|---|---|
GOTIFY_PORT |
8889 |
No | Gotify push notification server port |
GOTIFY_ADMIN_USER |
admin |
No | Gotify admin username |
GOTIFY_ADMIN_PASSWORD |
admin |
No | Gotify admin password |
Usage: Create apps in Gotify UI, add webhook URL to Alertmanager.
Security Checklist
Before production deployment:
- Change all
CHANGE_ME_*passwords - Generate strong
JWT_ACCESS_SECRET(32+ chars) - Generate strong
JWT_REFRESH_SECRET(32+ chars) - Generate strong
ENCRYPTION_KEY(32+ chars, different from JWT secrets) - Set strong
REDIS_PASSWORD - Set strong
V2_POSTGRES_PASSWORD - Set strong
LISTMONK_DB_PASSWORD - Set strong
LISTMONK_API_TOKEN - Set strong
GITEA_DB_PASSWD+GITEA_DB_ROOT_PASSWORD - Set strong
N8N_ENCRYPTION_KEY+N8N_USER_PASSWORD - Set strong
NC_ADMIN_PASSWORD(NocoDB) - Set strong
GRAFANA_ADMIN_PASSWORD - Disable
EMAIL_TEST_MODE(set tofalse) - Configure real SMTP credentials
- Set
NODE_ENV=production - Review
CORS_ORIGINS(whitelist only trusted domains)
Validation:
# Check for remaining placeholders
grep -r "CHANGE_ME" .env
# Verify secrets are different
echo "JWT_ACCESS_SECRET: $(grep JWT_ACCESS_SECRET .env)"
echo "JWT_REFRESH_SECRET: $(grep JWT_REFRESH_SECRET .env)"
echo "ENCRYPTION_KEY: $(grep ENCRYPTION_KEY .env)"
Troubleshooting
Missing .env File
Symptoms: Containers fail to start with "missing environment variable" errors
Solution:
# Create from template
cp .env.example .env
# Verify file exists
ls -la .env
Invalid Environment Variables
Symptoms: API fails to start with Zod validation errors
Diagnosis:
# View API startup logs
docker compose logs api | grep -A10 "Environment validation"
Common errors:
JWT_ACCESS_SECRETtoo short (must be 32+ chars)ENCRYPTION_KEYsame asJWT_ACCESS_SECRET(must differ)- Invalid URL format (
API_URLmust start with http:// or https://)
Solution:
# Regenerate secrets
export JWT_ACCESS_SECRET=$(openssl rand -hex 32)
export ENCRYPTION_KEY=$(openssl rand -hex 32)
# Update .env
sed -i "s/^JWT_ACCESS_SECRET=.*/JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET}/" .env
sed -i "s/^ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${ENCRYPTION_KEY}/" .env
# Restart API
docker compose restart api
PostgreSQL Connection Failures
Symptoms: API logs show ECONNREFUSED or authentication failed
Diagnosis:
# Check PostgreSQL is running
docker compose ps v2-postgres
# Test connection
docker compose exec api npx prisma db pull
# Verify DATABASE_URL
docker compose exec api printenv | grep DATABASE_URL
Solution:
# Verify password matches in .env
grep V2_POSTGRES_PASSWORD .env
# Restart PostgreSQL
docker compose restart v2-postgres
# Wait for healthcheck
docker compose ps v2-postgres # Should show (healthy)
Redis Connection Failures
Symptoms: API logs show ECONNREFUSED or WRONGPASS invalid password
Diagnosis:
# Check Redis is running
docker compose ps redis
# Test connection
docker compose exec redis redis-cli -a "${REDIS_PASSWORD}" ping
Solution:
# Verify password in .env
grep REDIS_PASSWORD .env
# Ensure REDIS_URL includes password
grep REDIS_URL .env # Should be redis://:PASSWORD@redis-changemaker:6379
# Restart Redis
docker compose restart redis
Environment Variables Not Updating
Symptoms: Changed .env but service still uses old value
Cause: Docker Compose reads .env at startup, not runtime
Solution:
# Recreate container (picks up new env vars)
docker compose up -d --force-recreate api
# Or: stop and start
docker compose down
docker compose up -d
Related Documentation
- Docker Compose — Service orchestration
- SSL/TLS — Certificate management
- Tunneling — Pangolin setup
- Backup & Restore — Data protection
- Security Audit — Security requirements