############################################################################### # Changemaker Lite v2 — Docker Compose ############################################################################### x-logging: &default-logging driver: "json-file" options: max-size: "10m" max-file: "3" services: # ========================================================================= # V2 CORE SERVICES # ========================================================================= # Unified Express.js API api: image: ${GITEA_REGISTRY:-gitea.bnkops.com/admin}/changemaker-api:${IMAGE_TAG:-local} build: context: ./api target: ${BUILD_TARGET:-development} container_name: changemaker-v2-api restart: unless-stopped ports: - "127.0.0.1:${API_PORT:-4000}:4000" - "127.0.0.1:${LISTMONK_PROXY_PORT:-9002}:9002" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:4000/api/health"] interval: 15s timeout: 5s retries: 3 start_period: 120s environment: - NODE_ENV=${NODE_ENV:-development} - PORT=4000 - LOG_DIR=/app/logs - DATABASE_URL=postgresql://${V2_POSTGRES_USER:-changemaker}:${V2_POSTGRES_PASSWORD:?V2_POSTGRES_PASSWORD must be set in .env}@changemaker-v2-postgres:5432/${V2_POSTGRES_DB:-changemaker_v2} - REDIS_URL=redis://:${REDIS_PASSWORD}@redis-changemaker:6379 - JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET} - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET} - JWT_INVITE_SECRET=${JWT_INVITE_SECRET} # Updated 2026-04-12 (P2-2, P2-3): removed `:-` fallback (empty default) # now that these are required, and shortened refresh expiry default 7d→24h. - GITEA_SSO_SECRET=${GITEA_SSO_SECRET} - SERVICE_PASSWORD_SALT=${SERVICE_PASSWORD_SALT} - JWT_ACCESS_EXPIRY=${JWT_ACCESS_EXPIRY:-15m} - JWT_REFRESH_EXPIRY=${JWT_REFRESH_EXPIRY:-24h} - ENCRYPTION_KEY=${ENCRYPTION_KEY} - INITIAL_ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL:-admin@cmlite.org} - INITIAL_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD:?INITIAL_ADMIN_PASSWORD must be set in .env} - SMTP_HOST=${SMTP_HOST:-mailhog-changemaker} - SMTP_PORT=${SMTP_PORT:-1025} - SMTP_USER=${SMTP_USER:-} - SMTP_PASS=${SMTP_PASS:-} - SMTP_FROM=${SMTP_FROM:-noreply@cmlite.org} - EMAIL_TEST_MODE=${EMAIL_TEST_MODE:-true} - LISTMONK_URL=http://listmonk-app:9000 - LISTMONK_ADMIN_USER=${LISTMONK_ADMIN_USER:-admin} - LISTMONK_ADMIN_PASSWORD=${LISTMONK_ADMIN_PASSWORD:-} - LISTMONK_SYNC_ENABLED=${LISTMONK_SYNC_ENABLED:-false} - LISTMONK_WEBHOOK_SECRET=${LISTMONK_WEBHOOK_SECRET:-} - LISTMONK_PROXY_PORT=${LISTMONK_PROXY_PORT:-9002} - N8N_WEBHOOK_URLS=${N8N_WEBHOOK_URLS:-} - REPRESENT_API_URL=${REPRESENT_API_URL:-https://represent.opennorth.ca} - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000,http://localhost} - ADMIN_URL=${ADMIN_URL:-http://localhost:3000} - API_URL=${API_URL:-http://localhost:4000} - DOMAIN=${DOMAIN:-cmlite.org} - NAR_DATA_DIR=/data - PANGOLIN_API_URL=${PANGOLIN_API_URL:-} - PANGOLIN_API_KEY=${PANGOLIN_API_KEY:-} - PANGOLIN_ORG_ID=${PANGOLIN_ORG_ID:-} - PANGOLIN_SITE_ID=${PANGOLIN_SITE_ID:-} - PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT:-} - PANGOLIN_NEWT_ID=${PANGOLIN_NEWT_ID:-} - PANGOLIN_NEWT_SECRET=${PANGOLIN_NEWT_SECRET:-} # NODE_TLS_REJECT_UNAUTHORIZED removed — never disable TLS validation globally - EXCALIDRAW_URL=${EXCALIDRAW_URL:-http://excalidraw-changemaker:80} - EXCALIDRAW_PORT=${EXCALIDRAW_PORT:-8090} - EXCALIDRAW_EMBED_PORT=${EXCALIDRAW_EMBED_PORT:-8886} - HOMEPAGE_URL=${HOMEPAGE_URL:-http://homepage-changemaker:3000} - HOMEPAGE_EMBED_PORT=${HOMEPAGE_EMBED_PORT:-8887} - VAULTWARDEN_URL=${VAULTWARDEN_URL:-http://vaultwarden-changemaker:80} - VAULTWARDEN_EMBED_PORT=${VAULTWARDEN_EMBED_PORT:-8890} - ROCKETCHAT_URL=${ROCKETCHAT_URL:-http://rocketchat-changemaker:3000} - ROCKETCHAT_ADMIN_USER=${ROCKETCHAT_ADMIN_USER:-rcadmin} - ROCKETCHAT_ADMIN_PASSWORD=${ROCKETCHAT_ADMIN_PASSWORD:?ROCKETCHAT_ADMIN_PASSWORD must be set in .env} - ROCKETCHAT_EMBED_PORT=${ROCKETCHAT_EMBED_PORT:-8891} - ENABLE_CHAT=${ENABLE_CHAT:-false} - GANCIO_URL=${GANCIO_URL:-http://gancio-changemaker:13120} - GANCIO_EMBED_PORT=${GANCIO_EMBED_PORT:-8892} - GANCIO_ADMIN_USER=${GANCIO_ADMIN_USER:-admin} - GANCIO_ADMIN_PASSWORD=${GANCIO_ADMIN_PASSWORD:-} - GANCIO_SYNC_ENABLED=${GANCIO_SYNC_ENABLED:-false} # Jitsi Meet (video conferencing) - ENABLE_MEET=${ENABLE_MEET:-false} - JITSI_APP_ID=${JITSI_APP_ID:-changemaker} - JITSI_APP_SECRET=${JITSI_APP_SECRET:-} - JITSI_URL=${JITSI_URL:-http://jitsi-web-changemaker:80} - JITSI_EMBED_PORT=${JITSI_EMBED_PORT:-8893} # Embed ports for iframe embedding without DNS/subdomain (configurable for multi-instance) - NOCODB_EMBED_PORT=${NOCODB_EMBED_PORT:-8881} - N8N_EMBED_PORT=${N8N_EMBED_PORT:-8882} - GITEA_EMBED_PORT=${GITEA_EMBED_PORT:-8883} - MAILHOG_EMBED_PORT=${MAILHOG_EMBED_PORT:-8884} - GRAFANA_EMBED_PORT=${GRAFANA_EMBED_PORT:-8894} - ALERTMANAGER_EMBED_PORT=${ALERTMANAGER_EMBED_PORT:-8895} # SMS Campaigns (Termux Android Bridge) - ENABLE_SMS=${ENABLE_SMS:-false} # Social, People, Analytics (initial defaults; DB authoritative once admin saves) - ENABLE_SOCIAL=${ENABLE_SOCIAL:-false} - ENABLE_PEOPLE=${ENABLE_PEOPLE:-false} - ENABLE_ANALYTICS=${ENABLE_ANALYTICS:-false} # CCP Agent (remote management) - ENABLE_CCP_AGENT=${ENABLE_CCP_AGENT:-false} - CCP_URL=${CCP_URL:-} - CCP_AGENT_URL=${CCP_AGENT_URL:-} - COMPOSE_PROFILES=${COMPOSE_PROFILES:-} - TERMUX_API_URL=${TERMUX_API_URL:-http://10.0.0.193:5001} - TERMUX_API_KEY=${TERMUX_API_KEY:-} - SMS_DELAY_BETWEEN_MS=${SMS_DELAY_BETWEEN_MS:-3000} - SMS_MAX_RETRIES=${SMS_MAX_RETRIES:-3} - SMS_RESPONSE_SYNC_INTERVAL_MS=${SMS_RESPONSE_SYNC_INTERVAL_MS:-30000} - SMS_DEVICE_MONITOR_INTERVAL_MS=${SMS_DEVICE_MONITOR_INTERVAL_MS:-30000} # Docker container management (socket proxy, network discovery, service names) - DOCKER_PROXY_URL=${DOCKER_PROXY_URL:-http://docker-socket-proxy:2375} - DOCKER_NETWORK_NAME=${DOCKER_NETWORK_NAME:-changemaker-lite} - NEWT_CONTAINER_NAME=${NEWT_CONTAINER_NAME:-newt-changemaker} - NEWT_COMPOSE_SERVICE=${NEWT_COMPOSE_SERVICE:-newt} # Container Registry - GITEA_REGISTRY=${GITEA_REGISTRY:-gitea.bnkops.com/admin} - GITEA_REGISTRY_USER=${GITEA_REGISTRY_USER:-} - GITEA_REGISTRY_PASS=${GITEA_REGISTRY_PASS:-} # Gitea (docs comments, version history, auto-setup) - GITEA_URL=${GITEA_URL:-http://gitea-changemaker:3000} - GITEA_API_TOKEN=${GITEA_API_TOKEN:-} - GITEA_ADMIN_PASSWORD=${GITEA_ADMIN_PASSWORD:-${INITIAL_ADMIN_PASSWORD}} - GITEA_DOCS_REPO=${GITEA_DOCS_REPO:-admin/changemaker.lite} - GITEA_DOCS_PREFIX=${GITEA_DOCS_PREFIX:-mkdocs/docs} - GITEA_DOCS_BRANCH=${GITEA_DOCS_BRANCH:-v2} # GeoIP (MaxMind GeoLite2) - MAXMIND_ACCOUNT_ID=${MAXMIND_ACCOUNT_ID:-} - MAXMIND_LICENSE_KEY=${MAXMIND_LICENSE_KEY:-} volumes: - ./api:/app - /app/node_modules - ./assets/uploads:/app/uploads - ./mkdocs:/mkdocs:rw - ./data:/data:ro - ./data/geoip:/data/geoip:rw - ./data/upgrade:/app/upgrade:rw - ./configs:/app/configs:ro - ./logs/api:/app/logs - ./.env:/.env:rw # Pangolin setup auto-writes credentials deploy: resources: limits: cpus: '2' memory: 1G reservations: cpus: '0.25' memory: 256M depends_on: v2-postgres: condition: service_healthy redis: condition: service_healthy logging: *default-logging networks: - changemaker-lite # Fastify Media API (Microservice for Media Management) media-api: image: ${GITEA_REGISTRY:-gitea.bnkops.com/admin}/changemaker-media-api:${IMAGE_TAG:-local} build: context: ./api dockerfile: Dockerfile.media target: ${BUILD_TARGET:-development} container_name: changemaker-media-api restart: unless-stopped ports: - "127.0.0.1:${MEDIA_API_PORT:-4100}:4100" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:4100/health"] interval: 15s timeout: 5s retries: 3 start_period: 30s environment: - NODE_ENV=${NODE_ENV:-development} - MEDIA_API_PORT=${MEDIA_API_PORT:-4100} - DATABASE_URL=postgresql://${V2_POSTGRES_USER:-changemaker}:${V2_POSTGRES_PASSWORD:?V2_POSTGRES_PASSWORD must be set in .env}@changemaker-v2-postgres:5432/${V2_POSTGRES_DB:-changemaker_v2} - REDIS_URL=redis://:${REDIS_PASSWORD}@redis-changemaker:6379 - JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET} - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET} - JWT_INVITE_SECRET=${JWT_INVITE_SECRET} # Added 2026-04-12 (P2-2): media-api shares the same env schema as api; # both require these secrets to boot. - GITEA_SSO_SECRET=${GITEA_SSO_SECRET} - SERVICE_PASSWORD_SALT=${SERVICE_PASSWORD_SALT} - JWT_ACCESS_EXPIRY=${JWT_ACCESS_EXPIRY:-15m} - JWT_REFRESH_EXPIRY=${JWT_REFRESH_EXPIRY:-24h} - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000,http://localhost:3100} - ENCRYPTION_KEY=${ENCRYPTION_KEY} - ENABLE_MEDIA_FEATURES=${ENABLE_MEDIA_FEATURES:-true} - ENABLE_HLS_TRANSCODE=${ENABLE_HLS_TRANSCODE:-false} - MEDIA_ROOT=/media/local - MEDIA_UPLOADS=/media/uploads - MAX_UPLOAD_SIZE_GB=${MAX_UPLOAD_SIZE_GB:-10} - INITIAL_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD} volumes: - ./api:/app - /app/node_modules - ${MEDIA_ROOT:-./media}:/media:ro - ${MEDIA_ROOT:-./media}/local/inbox:/media/local/inbox:rw - ${MEDIA_ROOT:-./media}/local/thumbnails:/media/local/thumbnails:rw - ${MEDIA_ROOT:-./media}/local/photos:/media/local/photos:rw - ${MEDIA_ROOT:-./media}/local/documents:/media/local/documents:rw - ${MEDIA_ROOT:-./media}/local/hls:/media/local/hls:rw - ${MEDIA_ROOT:-./media}/public:/media/public:rw deploy: resources: # Bumped from 2/1G to 4/2G to give FFmpeg HLS transcoding (in-process # via hls-transcode-queue worker, concurrency 1) headroom without # starving the API thread. Revisit if upload spikes saturate cores. limits: cpus: '4' memory: 2G reservations: cpus: '0.5' memory: 512M depends_on: v2-postgres: condition: service_healthy redis: condition: service_healthy logging: *default-logging networks: - changemaker-lite # React Admin GUI (Vite dev server) admin: image: ${GITEA_REGISTRY:-gitea.bnkops.com/admin}/changemaker-admin:${IMAGE_TAG:-local} build: context: ./admin target: ${BUILD_TARGET:-development} container_name: changemaker-v2-admin restart: unless-stopped ports: - "127.0.0.1:${ADMIN_PORT:-3000}:3000" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/"] interval: 30s timeout: 5s retries: 3 start_period: 60s environment: - DOMAIN=${DOMAIN:-cmlite.org} - NODE_ENV=${NODE_ENV:-development} - VITE_API_URL=http://changemaker-v2-api:4000 - VITE_MEDIA_API_URL=${VITE_MEDIA_API_URL:-http://changemaker-media-api:4100} - VITE_MKDOCS_URL=http://mkdocs-changemaker:8000 - VITE_DOMAIN=${DOMAIN:-cmlite.org} - VITE_MKDOCS_SITE_PORT=${MKDOCS_SITE_SERVER_PORT:-4004} volumes: - ./admin:/app - /app/node_modules depends_on: api: condition: service_healthy logging: *default-logging networks: - changemaker-lite # PostgreSQL 16 (v2 database) v2-postgres: image: postgres:16-alpine container_name: changemaker-v2-postgres restart: unless-stopped ports: - "127.0.0.1:${V2_POSTGRES_PORT:-5433}:5432" environment: POSTGRES_USER: ${V2_POSTGRES_USER:-changemaker} POSTGRES_PASSWORD: ${V2_POSTGRES_PASSWORD:?V2_POSTGRES_PASSWORD must be set in .env} POSTGRES_DB: ${V2_POSTGRES_DB:-changemaker_v2} volumes: - v2-postgres-data:/var/lib/postgresql/data - ./api/prisma/init-nocodb-db.sh:/docker-entrypoint-initdb.d/init-nocodb-db.sh:ro - ./api/prisma/init-gancio-db.sh:/docker-entrypoint-initdb.d/init-gancio-db.sh:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${V2_POSTGRES_USER:-changemaker}"] interval: 10s timeout: 5s retries: 5 logging: *default-logging networks: - changemaker-lite # Nginx reverse proxy nginx: image: ${GITEA_REGISTRY:-gitea.bnkops.com/admin}/changemaker-nginx:${IMAGE_TAG:-local} build: context: ./nginx container_name: changemaker-v2-nginx restart: unless-stopped ports: - "${NGINX_HTTP_PORT:-80}:80" - "${NGINX_HTTPS_PORT:-443}:443" # Embed proxy ports — configurable via .env to avoid conflicts in multi-instance deployments - "127.0.0.1:${NOCODB_EMBED_PORT:-8881}:${NOCODB_EMBED_PORT:-8881}" - "127.0.0.1:${N8N_EMBED_PORT:-8882}:${N8N_EMBED_PORT:-8882}" - "127.0.0.1:${GITEA_EMBED_PORT:-8883}:${GITEA_EMBED_PORT:-8883}" - "127.0.0.1:${MAILHOG_EMBED_PORT:-8884}:${MAILHOG_EMBED_PORT:-8884}" - "127.0.0.1:${MINI_QR_EMBED_PORT:-8885}:${MINI_QR_EMBED_PORT:-8885}" - "127.0.0.1:${EXCALIDRAW_EMBED_PORT:-8886}:${EXCALIDRAW_EMBED_PORT:-8886}" - "127.0.0.1:${HOMEPAGE_EMBED_PORT:-8887}:${HOMEPAGE_EMBED_PORT:-8887}" - "127.0.0.1:${VAULTWARDEN_EMBED_PORT:-8890}:${VAULTWARDEN_EMBED_PORT:-8890}" - "127.0.0.1:${ROCKETCHAT_EMBED_PORT:-8891}:${ROCKETCHAT_EMBED_PORT:-8891}" - "127.0.0.1:${GANCIO_EMBED_PORT:-8892}:${GANCIO_EMBED_PORT:-8892}" - "127.0.0.1:${JITSI_EMBED_PORT:-8893}:${JITSI_EMBED_PORT:-8893}" - "127.0.0.1:${GRAFANA_EMBED_PORT:-8894}:${GRAFANA_EMBED_PORT:-8894}" - "127.0.0.1:${ALERTMANAGER_EMBED_PORT:-8895}:${ALERTMANAGER_EMBED_PORT:-8895}" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:80/"] interval: 30s timeout: 5s retries: 3 environment: - DOMAIN=${DOMAIN:-cmlite.org} - PANGOLIN_SITE_ID=${PANGOLIN_SITE_ID:-} # Embed proxy ports (passed to envsubst for nginx template processing) - NOCODB_EMBED_PORT=${NOCODB_EMBED_PORT:-8881} - N8N_EMBED_PORT=${N8N_EMBED_PORT:-8882} - GITEA_EMBED_PORT=${GITEA_EMBED_PORT:-8883} - MAILHOG_EMBED_PORT=${MAILHOG_EMBED_PORT:-8884} - MINI_QR_EMBED_PORT=${MINI_QR_EMBED_PORT:-8885} - EXCALIDRAW_EMBED_PORT=${EXCALIDRAW_EMBED_PORT:-8886} - HOMEPAGE_EMBED_PORT=${HOMEPAGE_EMBED_PORT:-8887} - VAULTWARDEN_EMBED_PORT=${VAULTWARDEN_EMBED_PORT:-8890} - ROCKETCHAT_EMBED_PORT=${ROCKETCHAT_EMBED_PORT:-8891} - GANCIO_EMBED_PORT=${GANCIO_EMBED_PORT:-8892} - JITSI_EMBED_PORT=${JITSI_EMBED_PORT:-8893} - GRAFANA_EMBED_PORT=${GRAFANA_EMBED_PORT:-8894} - ALERTMANAGER_EMBED_PORT=${ALERTMANAGER_EMBED_PORT:-8895} volumes: # Note: conf.d is NOT mounted (configs are generated at startup from templates) - ./public-web:/usr/share/nginx/public-web:ro - ./configs/pangolin:/etc/pangolin:ro depends_on: api: condition: service_healthy admin: condition: service_healthy logging: *default-logging networks: - changemaker-lite # NocoDB v2 — pointed at v2 PostgreSQL as read-only data browser nocodb-v2: image: nocodb/nocodb:0.301.3 container_name: changemaker-v2-nocodb restart: unless-stopped ports: - "127.0.0.1:${NOCODB_V2_PORT:-8091}:8080" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/api/v1/health"] interval: 30s timeout: 5s retries: 3 start_period: 30s environment: NC_DB: "pg://changemaker-v2-postgres:5432?u=${V2_POSTGRES_USER:-changemaker}&p=${V2_POSTGRES_PASSWORD:?V2_POSTGRES_PASSWORD must be set in .env}&d=nocodb_meta" NC_ADMIN_EMAIL: ${NC_ADMIN_EMAIL:-admin@cmlite.org} NC_ADMIN_PASSWORD: ${NC_ADMIN_PASSWORD:?NC_ADMIN_PASSWORD must be set in .env} NC_PUBLIC_URL: ${NC_PUBLIC_URL:-http://localhost:8091} volumes: - nocodb-v2-data:/usr/app/data depends_on: v2-postgres: condition: service_healthy logging: *default-logging networks: - changemaker-lite # NocoDB Init — auto-registers changemaker_v2 as a browsable data source nocodb-init: image: alpine:3 container_name: nocodb-init depends_on: nocodb-v2: condition: service_healthy v2-postgres: condition: service_healthy restart: "no" environment: NOCODB_URL: http://changemaker-v2-nocodb:8080 NC_ADMIN_EMAIL: ${NC_ADMIN_EMAIL:-admin@cmlite.org} NC_ADMIN_PASSWORD: ${NC_ADMIN_PASSWORD:?NC_ADMIN_PASSWORD must be set in .env} DB_HOST: changemaker-v2-postgres DB_PORT: "5432" DB_USER: ${V2_POSTGRES_USER:-changemaker} DB_PASSWORD: ${V2_POSTGRES_PASSWORD:?V2_POSTGRES_PASSWORD must be set in .env} DB_NAME: ${V2_POSTGRES_DB:-changemaker_v2} volumes: - ./scripts/nocodb-init.sh:/init.sh:ro entrypoint: ["/bin/sh", "/init.sh"] logging: *default-logging networks: - changemaker-lite # ========================================================================= # SHARED INFRASTRUCTURE (kept from v1) # ========================================================================= # Shared Redis — sessions, BullMQ queues, cache redis: image: redis:7-alpine container_name: redis-changemaker command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy noeviction --requirepass "${REDIS_PASSWORD}" ports: - "127.0.0.1:6379:6379" volumes: - redis-data:/data restart: always healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: cpus: '1' memory: 512M reservations: cpus: '0.25' memory: 256M networks: - changemaker-lite logging: driver: "json-file" options: max-size: "5m" max-file: "2" # Listmonk — Email marketing (kept as Docker image, controlled via REST API) listmonk-app: image: listmonk/listmonk:v6.0.0 container_name: listmonk-app restart: unless-stopped ports: - "127.0.0.1:${LISTMONK_PORT:-9001}:9000" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:9000/"] interval: 30s timeout: 5s retries: 3 start_period: 30s depends_on: listmonk-db: condition: service_healthy command: [sh, -c, "./listmonk --install --idempotent --yes --config '' && ./listmonk --upgrade --yes --config '' && ./listmonk --config ''"] environment: LISTMONK_app__address: 0.0.0.0:9000 LISTMONK_db__user: ${LISTMONK_DB_USER:-listmonk} LISTMONK_db__password: ${LISTMONK_DB_PASSWORD:?LISTMONK_DB_PASSWORD must be set in .env} LISTMONK_db__database: ${LISTMONK_DB_NAME:-listmonk} LISTMONK_db__host: listmonk-db LISTMONK_db__port: 5432 LISTMONK_db__ssl_mode: disable TZ: Etc/UTC LISTMONK_ADMIN_USER: ${LISTMONK_WEB_ADMIN_USER:-admin} LISTMONK_ADMIN_PASSWORD: ${LISTMONK_WEB_ADMIN_PASSWORD:-} volumes: - ./assets/uploads:/listmonk/uploads:rw logging: *default-logging networks: - changemaker-lite listmonk-db: image: postgres:17-alpine container_name: listmonk-db restart: unless-stopped ports: - "127.0.0.1:${LISTMONK_DB_PORT:-5434}:5432" environment: POSTGRES_USER: ${LISTMONK_DB_USER:-listmonk} POSTGRES_PASSWORD: ${LISTMONK_DB_PASSWORD:?LISTMONK_DB_PASSWORD must be set in .env} POSTGRES_DB: ${LISTMONK_DB_NAME:-listmonk} healthcheck: test: ["CMD-SHELL", "pg_isready -U ${LISTMONK_DB_USER:-listmonk}"] interval: 10s timeout: 5s retries: 6 volumes: - listmonk-data:/var/lib/postgresql/data logging: *default-logging networks: - changemaker-lite # One-shot: creates the Listmonk API user for V2 integration after tables exist. # Safe to re-run (upserts). Exits 0 on success. Set LISTMONK_API_TOKEN in .env. listmonk-init: image: postgres:17-alpine container_name: listmonk-init depends_on: listmonk-app: condition: service_healthy restart: "no" environment: PGPASSWORD: ${LISTMONK_DB_PASSWORD:?LISTMONK_DB_PASSWORD must be set in .env} LISTMONK_API_USER: ${LISTMONK_API_USER:-v2-api} LISTMONK_API_TOKEN: ${LISTMONK_API_TOKEN:-} LISTMONK_SMTP_HOST: ${LISTMONK_SMTP_HOST:-mailhog-changemaker} LISTMONK_SMTP_PORT: ${LISTMONK_SMTP_PORT:-1025} LISTMONK_SMTP_USER: ${LISTMONK_SMTP_USER:-} LISTMONK_SMTP_PASSWORD: ${LISTMONK_SMTP_PASSWORD:-} LISTMONK_SMTP_TLS_TYPE: ${LISTMONK_SMTP_TLS_TYPE:-none} LISTMONK_SMTP_FROM: ${LISTMONK_SMTP_FROM:-Changemaker Lite } entrypoint: ["/bin/sh", "-c"] command: - | echo "[listmonk-init] Waiting for Listmonk tables..." for i in $$(seq 1 30); do if psql -h listmonk-db -U ${LISTMONK_DB_USER:-listmonk} -d ${LISTMONK_DB_NAME:-listmonk} -c "SELECT 1 FROM users LIMIT 1" >/dev/null 2>&1; then break fi sleep 2 done if [ -n "$$LISTMONK_API_TOKEN" ]; then echo "[listmonk-init] Upserting API user '$$LISTMONK_API_USER'..." psql -h listmonk-db -U ${LISTMONK_DB_USER:-listmonk} -d ${LISTMONK_DB_NAME:-listmonk} -q </dev/null 2>&1; then break fi sleep 2 done if [ -z "$$VAULTWARDEN_ADMIN_TOKEN" ]; then echo "[vaultwarden-init] VAULTWARDEN_ADMIN_TOKEN not set, skipping invite" exit 0 fi echo "[vaultwarden-init] Authenticating with admin panel..." SESSION_COOKIE=$(mktemp) HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ -c "$$SESSION_COOKIE" \ -X POST "$$VAULTWARDEN_URL/admin" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=$$VAULTWARDEN_ADMIN_TOKEN") if [ "$$HTTP_CODE" != "200" ] && [ "$$HTTP_CODE" != "302" ]; then echo "[vaultwarden-init] Admin auth failed (HTTP $$HTTP_CODE)" rm -f "$$SESSION_COOKIE" exit 1 fi echo "[vaultwarden-init] Authenticated" echo "[vaultwarden-init] Inviting $$INVITE_EMAIL..." INVITE_CODE=$(curl -s -w "\n%{http_code}" \ -b "$$SESSION_COOKIE" \ -X POST "$$VAULTWARDEN_URL/admin/invite" \ -H "Content-Type: application/json" \ -d "{\"email\":\"$$INVITE_EMAIL\"}") INVITE_HTTP=$(echo "$$INVITE_CODE" | tail -1) INVITE_BODY=$(echo "$$INVITE_CODE" | head -n -1) if [ "$$INVITE_HTTP" = "200" ] || [ "$$INVITE_HTTP" = "422" ]; then echo "[vaultwarden-init] Invite sent (or user already exists)" else echo "[vaultwarden-init] Invite failed (HTTP $$INVITE_HTTP): $$INVITE_BODY" fi rm -f "$$SESSION_COOKIE" echo "[vaultwarden-init] Done" logging: *default-logging networks: - changemaker-lite # Rocket.Chat — Team coordination chat rocketchat: image: rocketchat/rocket.chat:7.9.7 container_name: rocketchat-changemaker restart: unless-stopped depends_on: mongodb-rocketchat: condition: service_healthy nats-rocketchat: condition: service_started environment: - ROOT_URL=http://chat.${DOMAIN:-cmlite.org} - MONGO_URL=mongodb://${MONGO_ROOT_USER:-rocketchat}:${MONGO_ROOT_PASSWORD}@mongodb-rocketchat:27017/rocketchat?replicaSet=rs0&authSource=admin - MONGO_OPLOG_URL=mongodb://${MONGO_ROOT_USER:-rocketchat}:${MONGO_ROOT_PASSWORD}@mongodb-rocketchat:27017/local?replicaSet=rs0&authSource=admin - TRANSPORTER=monolith+nats://nats-rocketchat:4222 - PORT=3000 - ADMIN_USERNAME=${ROCKETCHAT_ADMIN_USER:-rcadmin} - ADMIN_NAME=Admin - ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL:-admin@cmlite.org} - ADMIN_PASS=${ROCKETCHAT_ADMIN_PASSWORD:?ROCKETCHAT_ADMIN_PASSWORD must be set in .env} - CREATE_TOKENS_FOR_USERS=true - OVERWRITE_SETTING_Iframe_Integration_send_enable=true - OVERWRITE_SETTING_Iframe_Integration_receive_enable=true - OVERWRITE_SETTING_Iframe_Integration_receive_origin=http://app.${DOMAIN:-cmlite.org},https://app.${DOMAIN:-cmlite.org},http://localhost:${ADMIN_PORT:-3000} # Jitsi integration (JWT-authenticated video calls) - OVERWRITE_SETTING_Jitsi_Enabled=${ENABLE_MEET:-false} - OVERWRITE_SETTING_Jitsi_Domain=meet.${DOMAIN:-cmlite.org} - OVERWRITE_SETTING_Jitsi_URL_Room_Prefix=RocketChat - OVERWRITE_SETTING_Jitsi_Enable_Channels=true - OVERWRITE_SETTING_Jitsi_Open_New_Window=false - OVERWRITE_SETTING_Jitsi_Enabled_TokenAuth=true - OVERWRITE_SETTING_Jitsi_Application_ID=${JITSI_APP_ID:-changemaker} - OVERWRITE_SETTING_Jitsi_Application_Secret=${JITSI_APP_SECRET:-} # Conference Call provider (marketplace app-based system in RC 7.x) - OVERWRITE_SETTING_VideoConf_Default_Provider=jitsi volumes: - rocketchat-uploads:/app/uploads logging: *default-logging networks: - changemaker-lite healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/info"] interval: 30s timeout: 10s retries: 10 start_period: 90s # NATS (required by Rocket.Chat 7.x+ for microservice messaging) nats-rocketchat: image: nats:2.11-alpine container_name: nats-rocketchat restart: unless-stopped command: --http_port 8222 logging: *default-logging networks: - changemaker-lite # MongoDB (required by Rocket.Chat — replica set for oplog tailing) mongodb-rocketchat: image: mongo:6.0 container_name: mongodb-rocketchat restart: unless-stopped # Generate keyfile then delegate to Docker's standard entrypoint (creates INITDB user) entrypoint: ["/bin/bash", "-c", "if [ ! -f /data/replica.key ]; then openssl rand -base64 756 > /data/replica.key; fi && chmod 400 /data/replica.key && chown 999:999 /data/replica.key && exec docker-entrypoint.sh mongod --replSet rs0 --bind_ip_all --keyFile /data/replica.key"] environment: MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER:-rocketchat} MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD:?MONGO_ROOT_PASSWORD must be set in .env} volumes: - mongodb-rocketchat-data:/data/db logging: *default-logging networks: - changemaker-lite healthcheck: test: ["CMD", "mongosh", "-u", "${MONGO_ROOT_USER:-rocketchat}", "-p", "${MONGO_ROOT_PASSWORD}", "--authenticationDatabase", "admin", "--quiet", "--eval", "try { rs.status().ok } catch(e) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb-rocketchat:27017'}]}).ok }"] interval: 10s timeout: 10s retries: 10 start_period: 30s # Gancio — Event management platform (uses shared PostgreSQL) gancio: image: cisti/gancio:1.28.2 container_name: gancio-changemaker restart: unless-stopped depends_on: v2-postgres: condition: service_healthy ports: - "127.0.0.1:${GANCIO_PORT:-8092}:13120" healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:13120/', r => process.exit(r.statusCode < 400 ? 0 : 1)).on('error', () => process.exit(1))"] interval: 30s timeout: 10s retries: 5 start_period: 60s environment: - GANCIO_DATA=/home/node/data - NODE_ENV=production - GANCIO_DB_DIALECT=postgres - GANCIO_DB_HOST=changemaker-v2-postgres - GANCIO_DB_PORT=5432 - GANCIO_DB_DATABASE=gancio - GANCIO_DB_USERNAME=${V2_POSTGRES_USER:-changemaker} - GANCIO_DB_PASSWORD=${V2_POSTGRES_PASSWORD:?V2_POSTGRES_PASSWORD must be set in .env} - server__baseurl=${GANCIO_BASE_URL:-https://events.cmlite.org} volumes: - gancio-data:/home/node/data logging: *default-logging networks: - changemaker-lite # Gancio Init — Creates admin user + seeds default theme settings after Gancio creates its tables # Runs once after Gancio is healthy, then exits. Idempotent (ON CONFLICT DO NOTHING). gancio-init: image: postgres:16-alpine container_name: gancio-init depends_on: gancio: condition: service_healthy environment: - PGHOST=changemaker-v2-postgres - PGUSER=${V2_POSTGRES_USER:-changemaker} - PGPASSWORD=${V2_POSTGRES_PASSWORD:?V2_POSTGRES_PASSWORD must be set in .env} - PGDATABASE=gancio - GANCIO_ADMIN_USER=${GANCIO_ADMIN_USER:-admin} - GANCIO_ADMIN_PASSWORD=${GANCIO_ADMIN_PASSWORD} entrypoint: ["sh", "-c"] command: - | echo "Ensuring pgcrypto extension exists..." psql -c "CREATE EXTENSION IF NOT EXISTS pgcrypto;" echo "Creating Gancio admin user (if not exists)..." if [ -n "$$GANCIO_ADMIN_PASSWORD" ]; then psql -c "INSERT INTO users (email, password, display_name, role, is_admin, is_active, \"createdAt\", \"updatedAt\") VALUES ('$$GANCIO_ADMIN_USER', crypt('$$GANCIO_ADMIN_PASSWORD', gen_salt('bf', 10)), 'Admin', 'admin', true, true, NOW(), NOW()) ON CONFLICT (email) DO NOTHING;" echo "Gancio admin user ensured." else echo "WARNING: GANCIO_ADMIN_PASSWORD not set, skipping admin user creation." fi echo "Seeding Gancio default theme settings..." psql -c "INSERT INTO settings (key, value, is_secret, \"createdAt\", \"updatedAt\") VALUES ('dark_colors', '{\"primary\": \"#FF6E40\", \"error\": \"#FF5252\", \"info\": \"#2196F3\", \"success\": \"#4CAF50\", \"warning\": \"#FB8C00\"}', false, NOW(), NOW()), ('light_colors', '{\"primary\": \"#FF4500\", \"error\": \"#FF5252\", \"info\": \"#2196F3\", \"success\": \"#4CAF50\", \"warning\": \"#FB8C00\"}', false, NOW(), NOW()) ON CONFLICT (key) DO NOTHING;" echo "Gancio theme settings seeded." restart: "no" logging: *default-logging networks: - changemaker-lite # ========================================================================= # JITSI MEET (Self-hosted video conferencing with JWT auth) # ========================================================================= # Jitsi Web — Frontend UI (served via nginx at meet.${DOMAIN}) jitsi-web: image: jitsi/web:stable-9823 container_name: jitsi-web-changemaker restart: unless-stopped depends_on: jitsi-prosody: condition: service_healthy environment: - ENABLE_AUTH=1 - ENABLE_GUESTS=1 - AUTH_TYPE=jwt - JWT_APP_ID=${JITSI_APP_ID:-changemaker} - JWT_APP_SECRET=${JITSI_APP_SECRET:-} - XMPP_DOMAIN=meet.jitsi - XMPP_AUTH_DOMAIN=auth.meet.jitsi - XMPP_MUC_DOMAIN=muc.meet.jitsi - XMPP_INTERNAL_MUC_DOMAIN=internal-muc.meet.jitsi - XMPP_BOSH_URL_BASE=http://jitsi-prosody-changemaker:5280 - XMPP_SERVER=jitsi-prosody-changemaker - PUBLIC_URL=https://meet.${DOMAIN:-cmlite.org} - TOKEN_AUTH_URL=https://app.${DOMAIN:-cmlite.org}/jitsi-auth/{room} - TZ=UTC volumes: - jitsi-web-config:/config healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:80/"] interval: 30s timeout: 5s retries: 5 start_period: 30s logging: *default-logging networks: - changemaker-lite # Jitsi Prosody — XMPP server + JWT validation jitsi-prosody: image: jitsi/prosody:stable-9823 container_name: jitsi-prosody-changemaker restart: unless-stopped environment: - ENABLE_AUTH=1 - ENABLE_GUESTS=1 - AUTH_TYPE=jwt - JWT_APP_ID=${JITSI_APP_ID:-changemaker} - JWT_APP_SECRET=${JITSI_APP_SECRET:-} - JWT_ACCEPTED_ISSUERS=${JITSI_APP_ID:-changemaker} - JWT_ACCEPTED_AUDIENCES=${JITSI_APP_ID:-changemaker} - XMPP_DOMAIN=meet.jitsi - XMPP_AUTH_DOMAIN=auth.meet.jitsi - XMPP_MUC_DOMAIN=muc.meet.jitsi - XMPP_INTERNAL_MUC_DOMAIN=internal-muc.meet.jitsi - XMPP_MUC_MODULES=token_verification,token_affiliation - JICOFO_AUTH_PASSWORD=${JITSI_JICOFO_AUTH_PASSWORD:-} - JVB_AUTH_PASSWORD=${JITSI_JVB_AUTH_PASSWORD:-} - TZ=UTC volumes: - jitsi-prosody-config:/config - jitsi-prosody-plugins:/prosody-plugins-custom healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:5280/health"] interval: 30s timeout: 5s retries: 5 start_period: 30s logging: *default-logging networks: - changemaker-lite # Jitsi Jicofo — Conference focus / room management jitsi-jicofo: image: jitsi/jicofo:stable-9823 container_name: jitsi-jicofo-changemaker restart: unless-stopped depends_on: jitsi-prosody: condition: service_healthy environment: - ENABLE_AUTH=1 - AUTH_TYPE=jwt - XMPP_DOMAIN=meet.jitsi - XMPP_AUTH_DOMAIN=auth.meet.jitsi - XMPP_MUC_DOMAIN=muc.meet.jitsi - XMPP_INTERNAL_MUC_DOMAIN=internal-muc.meet.jitsi - XMPP_SERVER=jitsi-prosody-changemaker - JICOFO_AUTH_PASSWORD=${JITSI_JICOFO_AUTH_PASSWORD:-} - TZ=UTC volumes: - jitsi-jicofo-config:/config logging: *default-logging networks: - changemaker-lite # Jitsi JVB — Video bridge (media relay for audio/video) jitsi-jvb: image: jitsi/jvb:stable-9823 container_name: jitsi-jvb-changemaker restart: unless-stopped depends_on: jitsi-prosody: condition: service_healthy ports: - "127.0.0.1:${JVB_PORT:-10000}:10000/udp" environment: - XMPP_DOMAIN=meet.jitsi - XMPP_AUTH_DOMAIN=auth.meet.jitsi - XMPP_MUC_DOMAIN=muc.meet.jitsi - XMPP_INTERNAL_MUC_DOMAIN=internal-muc.meet.jitsi - XMPP_SERVER=jitsi-prosody-changemaker - JVB_AUTH_PASSWORD=${JITSI_JVB_AUTH_PASSWORD:-} - JVB_ADVERTISE_IPS=${JVB_ADVERTISE_IP:-} - JVB_PORT=${JVB_PORT:-10000} - TZ=UTC volumes: - jitsi-jvb-config:/config logging: *default-logging networks: - changemaker-lite # MailHog — Email testing (dev) mailhog: image: mailhog/mailhog:v1.0.1 container_name: mailhog-changemaker ports: - "127.0.0.1:${MAILHOG_WEB_PORT:-8025}:8025" # SMTP port 1025 is only exposed on the Docker network (containers connect via mailhog-changemaker:1025) restart: unless-stopped networks: - changemaker-lite logging: driver: "json-file" options: max-size: "5m" max-file: "2" # ========================================================================= # TUNNEL (Pangolin Newt connector) # ========================================================================= # Newt — Pangolin tunnel connector (connects to Pangolin server) newt: image: fosrl/newt container_name: newt-changemaker restart: unless-stopped environment: - PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT} - NEWT_ID=${PANGOLIN_NEWT_ID} - NEWT_SECRET=${PANGOLIN_NEWT_SECRET} depends_on: nginx: condition: service_healthy logging: *default-logging networks: - changemaker-lite # Docker socket proxy — read-only access for container status monitoring docker-socket-proxy: image: tecnativa/docker-socket-proxy:v0.4.2 container_name: docker-socket-proxy restart: unless-stopped environment: - CONTAINERS=1 # Allow container inspection - POST=0 # Block all write operations - IMAGES=0 - NETWORKS=0 - VOLUMES=0 - EXEC=0 - SWARM=0 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro logging: *default-logging networks: - changemaker-lite # ========================================================================= # MONITORING (behind profile flag) # ========================================================================= prometheus: image: prom/prometheus:v3.10.0 container_name: prometheus-changemaker command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--storage.tsdb.retention.time=30d' ports: - "127.0.0.1:${PROMETHEUS_PORT:-9090}:9090" volumes: - ./configs/prometheus:/etc/prometheus - prometheus-data:/prometheus restart: always logging: *default-logging networks: - changemaker-lite profiles: - monitoring grafana: image: grafana/grafana:12.3.0 container_name: grafana-changemaker ports: - "127.0.0.1:${GRAFANA_PORT:-3001}:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD must be set in .env} - GF_USERS_ALLOW_SIGN_UP=false - GF_SERVER_ROOT_URL=${GRAFANA_ROOT_URL:-http://localhost:3001} - GF_SECURITY_ALLOW_EMBEDDING=true - GF_AUTH_ANONYMOUS_ENABLED=false volumes: - grafana-data:/var/lib/grafana - ./configs/grafana:/etc/grafana/provisioning restart: always depends_on: - prometheus logging: *default-logging networks: - changemaker-lite profiles: - monitoring cadvisor: image: gcr.io/cadvisor/cadvisor:v0.55.1 container_name: cadvisor-changemaker ports: - "127.0.0.1:${CADVISOR_PORT:-8080}:8080" volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro - /dev/disk/:/dev/disk:ro cap_drop: - ALL cap_add: - SYS_PTRACE - DAC_READ_SEARCH security_opt: - no-new-privileges:true read_only: true devices: - /dev/kmsg restart: always logging: *default-logging networks: - changemaker-lite profiles: - monitoring node-exporter: image: prom/node-exporter:v1.10.2 container_name: node-exporter-changemaker ports: - "127.0.0.1:${NODE_EXPORTER_PORT:-9100}:9100" command: - '--path.rootfs=/host' - '--path.procfs=/host/proc' - '--path.sysfs=/host/sys' - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro restart: always logging: *default-logging networks: - changemaker-lite profiles: - monitoring redis-exporter: image: oliver006/redis_exporter:v1.81.0 container_name: redis-exporter-changemaker ports: - "127.0.0.1:${REDIS_EXPORTER_PORT:-9121}:9121" environment: - REDIS_ADDR=redis://redis-changemaker:6379 - REDIS_PASSWORD=${REDIS_PASSWORD} restart: always depends_on: - redis logging: *default-logging networks: - changemaker-lite profiles: - monitoring alertmanager: image: prom/alertmanager:v0.31.1 container_name: alertmanager-changemaker ports: - "127.0.0.1:${ALERTMANAGER_PORT:-9093}:9093" volumes: - ./configs/alertmanager:/etc/alertmanager - alertmanager-data:/alertmanager command: - '--config.file=/etc/alertmanager/alertmanager.yml' - '--storage.path=/alertmanager' restart: always logging: *default-logging networks: - changemaker-lite profiles: - monitoring gotify: image: gotify/server:v2.9.0 container_name: gotify-changemaker ports: - "127.0.0.1:${GOTIFY_PORT:-8889}:80" environment: - GOTIFY_DEFAULTUSER_NAME=${GOTIFY_ADMIN_USER:-admin} - GOTIFY_DEFAULTUSER_PASS=${GOTIFY_ADMIN_PASSWORD:?GOTIFY_ADMIN_PASSWORD must be set in .env} - TZ=Etc/UTC volumes: - gotify-data:/app/data restart: always logging: *default-logging networks: - changemaker-lite profiles: - monitoring # ========================================================================= # CCP REMOTE AGENT (optional — enabled via COMPOSE_PROFILES=ccp-agent) # ========================================================================= ccp-agent: build: context: ./changemaker-control-panel/agent dockerfile: Dockerfile container_name: ${COMPOSE_PROJECT_NAME:-changemaker-lite}-ccp-agent restart: unless-stopped profiles: ["ccp-agent"] ports: - "${CCP_AGENT_PORT:-7443}:7443" volumes: - /var/run/docker.sock:/var/run/docker.sock - ccp-agent-data:/var/lib/ccp-agent - ccp-agent-certs:/etc/ccp-agent - .:/app/instance:ro environment: - AGENT_PORT=7443 - AGENT_DATA_DIR=/var/lib/ccp-agent - CCP_URL=${CCP_URL:-} - CCP_INVITE_CODE=${CCP_INVITE_CODE:-} - CCP_AGENT_URL=${CCP_AGENT_URL:-} - INSTANCE_SLUG=${COMPOSE_PROJECT_NAME:-changemaker-lite} - INSTANCE_DOMAIN=${DOMAIN:-localhost} - INSTANCE_BASE_PATH=/app/instance # Pass the host's compose project name so the agent runs `docker compose -p ` # against the right project (not basename of INSTANCE_BASE_PATH, which is "instance"). - COMPOSE_PROJECT=${COMPOSE_PROJECT_NAME:-changemaker-lite} logging: *default-logging networks: - changemaker-lite # ============================================================================= # NETWORKS & VOLUMES # ============================================================================= networks: changemaker-lite: driver: bridge volumes: # v2 v2-postgres-data: nocodb-v2-data: # Shared redis-data: listmonk-data: # Platform n8n-data: gitea-data: mysql-data: vaultwarden-data: # Rocket.Chat rocketchat-uploads: mongodb-rocketchat-data: # Gancio gancio-data: # Jitsi Meet jitsi-web-config: jitsi-prosody-config: jitsi-prosody-plugins: jitsi-jicofo-config: jitsi-jvb-config: # Monitoring prometheus-data: grafana-data: alertmanager-data: gotify-data: # CCP Agent ccp-agent-data: ccp-agent-certs: