NocoDB v2 stores auth tokens in-memory (Pinia store), not in cookies
accessible to external pages. The auth bridge approach can't inject
tokens into NocoDB's SPA state. Reverted to the original banner
approach ("sign in to NocoDB in a new tab").
Kept: CSP fix (frame-ancestors http://localhost:* instead of just
localhost, which only matched port 80).
Bunker Admin
1451 lines
53 KiB
YAML
1451 lines
53 KiB
YAML
###############################################################################
|
|
# 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: 30s
|
|
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}
|
|
- 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:-7d}
|
|
- 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:-}
|
|
- 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}
|
|
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000,http://localhost:3100}
|
|
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
|
- ENABLE_MEDIA_FEATURES=${ENABLE_MEDIA_FEATURES:-true}
|
|
- 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}/public:/media/public:rw
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 1G
|
|
reservations:
|
|
cpus: '0.25'
|
|
memory: 256M
|
|
depends_on:
|
|
v2-postgres:
|
|
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: 20s
|
|
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
|
|
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_started
|
|
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 <noreply@cmlite.org>}
|
|
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 <<SQL
|
|
INSERT INTO users (username, password, password_login, email, name, type, user_role_id, status)
|
|
VALUES ('$$LISTMONK_API_USER', '$$LISTMONK_API_TOKEN', true, '$$LISTMONK_API_USER@api.internal', '$$LISTMONK_API_USER', 'api', 1, 'enabled')
|
|
ON CONFLICT (username) DO UPDATE SET password = EXCLUDED.password, status = 'enabled', user_role_id = 1;
|
|
SQL
|
|
echo "[listmonk-init] API user configured"
|
|
else
|
|
echo "[listmonk-init] LISTMONK_API_TOKEN not set, skipping API user"
|
|
fi
|
|
|
|
if [ -n "$$LISTMONK_SMTP_HOST" ]; then
|
|
echo "[listmonk-init] Configuring SMTP..."
|
|
# Always include MailHog as first provider
|
|
MAILHOG_ENTRY='{"host":"mailhog-changemaker","port":1025,"username":"","password":"","tls_type":"none","auth_protocol":"none","enabled":true,"max_conns":5,"idle_timeout":"15s","wait_timeout":"5s","max_msg_retries":2,"tls_skip_verify":false,"email_headers":[],"hello_hostname":""}'
|
|
if [ -n "$$LISTMONK_SMTP_USER" ]; then
|
|
# Production SMTP as second provider
|
|
PROD_ENTRY='{"host":"'"$$LISTMONK_SMTP_HOST"'","port":'"$$LISTMONK_SMTP_PORT"',"username":"'"$$LISTMONK_SMTP_USER"'","password":"'"$$LISTMONK_SMTP_PASSWORD"'","tls_type":"'"$$LISTMONK_SMTP_TLS_TYPE"'","auth_protocol":"plain","enabled":true,"max_conns":5,"idle_timeout":"15s","wait_timeout":"5s","max_msg_retries":2,"tls_skip_verify":false,"email_headers":[],"hello_hostname":""}'
|
|
SMTP_VALUE="[$$MAILHOG_ENTRY,$$PROD_ENTRY]"
|
|
echo "[listmonk-init] Dual SMTP: MailHog + $$LISTMONK_SMTP_HOST:$$LISTMONK_SMTP_PORT"
|
|
else
|
|
SMTP_VALUE="[$$MAILHOG_ENTRY]"
|
|
echo "[listmonk-init] Single SMTP: MailHog (no production credentials)"
|
|
fi
|
|
psql -h listmonk-db -U ${LISTMONK_DB_USER:-listmonk} -d ${LISTMONK_DB_NAME:-listmonk} -q <<SQL
|
|
UPDATE settings SET value = '$$SMTP_VALUE' WHERE key = 'smtp';
|
|
UPDATE settings SET value = '"$$LISTMONK_SMTP_FROM"' WHERE key = 'app.from_email';
|
|
SQL
|
|
echo "[listmonk-init] SMTP configured"
|
|
else
|
|
echo "[listmonk-init] LISTMONK_SMTP_HOST not set, skipping SMTP config"
|
|
fi
|
|
|
|
echo "[listmonk-init] Done"
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# =========================================================================
|
|
# PLATFORM SERVICES (kept from v1)
|
|
# =========================================================================
|
|
|
|
# Code Server — Browser IDE (LinuxServer upstream, no custom build)
|
|
code-server:
|
|
image: ${GITEA_REGISTRY:-gitea.bnkops.com/admin}/code-server:latest
|
|
container_name: code-server-changemaker
|
|
environment:
|
|
- PUID=${USER_ID:-1000}
|
|
- PGID=${GROUP_ID:-1000}
|
|
- TZ=${TZ:-UTC}
|
|
- DEFAULT_WORKSPACE=/config/workspace
|
|
volumes:
|
|
- ./configs/code-server:/config
|
|
- ./api:/config/workspace/api
|
|
- ./admin:/config/workspace/admin
|
|
- ./nginx:/config/workspace/nginx
|
|
- ./configs:/config/workspace/configs
|
|
- ./scripts:/config/workspace/scripts
|
|
- ./mkdocs:/config/workspace/mkdocs
|
|
- ./docker-compose.yml:/config/workspace/docker-compose.yml
|
|
# NOTE: .env intentionally excluded — secrets must not be accessible via Code Server
|
|
ports:
|
|
- "127.0.0.1:${CODE_SERVER_PORT:-8888}:8443"
|
|
restart: unless-stopped
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# MkDocs — Live documentation preview
|
|
mkdocs:
|
|
image: squidfunk/mkdocs-material
|
|
container_name: mkdocs-changemaker
|
|
volumes:
|
|
- ./mkdocs:/docs:rw
|
|
- ./assets/images:/docs/assets/images:rw
|
|
- ./scripts/mkdocs-build-trigger.py:/scripts/mkdocs-build-trigger.py:ro
|
|
- ./scripts/mkdocs-entrypoint.sh:/scripts/mkdocs-entrypoint.sh:ro
|
|
user: "${USER_ID:-1000}:${GROUP_ID:-1000}"
|
|
ports:
|
|
- "127.0.0.1:${MKDOCS_PORT:-4003}:8000"
|
|
environment:
|
|
- SITE_URL=${BASE_DOMAIN:-https://cmlite.org}
|
|
- ADMIN_PORT=${ADMIN_PORT:-3000}
|
|
- ADMIN_URL=${ADMIN_URL:-}
|
|
- MEDIA_API_PUBLIC_URL=${MEDIA_API_PUBLIC_URL:-http://localhost:4100}
|
|
- MEDIA_API_PORT=${MEDIA_API_PORT:-4100}
|
|
- BASE_DOMAIN=${BASE_DOMAIN:-}
|
|
- API_URL=${API_URL:-}
|
|
- API_PORT=${API_PORT:-4000}
|
|
- GANCIO_URL=${GANCIO_URL:-http://gancio-changemaker:13120}
|
|
- GANCIO_PORT=${GANCIO_PORT:-8092}
|
|
entrypoint: ["/bin/sh", "/scripts/mkdocs-entrypoint.sh"]
|
|
restart: unless-stopped
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# MkDocs built site — Nginx static server
|
|
mkdocs-site-server:
|
|
image: lscr.io/linuxserver/nginx:1.28.2
|
|
container_name: mkdocs-site-server-changemaker
|
|
environment:
|
|
- PUID=${USER_ID:-1000}
|
|
- PGID=${GROUP_ID:-1000}
|
|
- TZ=Etc/UTC
|
|
volumes:
|
|
- ./mkdocs/site:/config/www
|
|
- ./configs/mkdocs-site/default.conf:/config/nginx/site-confs/default.conf
|
|
ports:
|
|
- "127.0.0.1:${MKDOCS_SITE_SERVER_PORT:-4004}:80"
|
|
restart: unless-stopped
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# n8n — Workflow automation
|
|
n8n:
|
|
image: docker.n8n.io/n8nio/n8n
|
|
container_name: n8n-changemaker
|
|
restart: unless-stopped
|
|
ports:
|
|
- "127.0.0.1:${N8N_PORT:-5678}:5678"
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5678/healthz"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
environment:
|
|
- N8N_HOST=${N8N_HOST:-n8n.cmlite.org}
|
|
- N8N_PORT=5678
|
|
- N8N_PROTOCOL=https
|
|
- NODE_ENV=production
|
|
- WEBHOOK_URL=https://${N8N_HOST:-n8n.cmlite.org}/
|
|
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE:-UTC}
|
|
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY:?N8N_ENCRYPTION_KEY must be set in .env}
|
|
- N8N_USER_MANAGEMENT_DISABLED=false
|
|
- N8N_DEFAULT_USER_EMAIL=${N8N_USER_EMAIL:-admin@example.com}
|
|
- N8N_DEFAULT_USER_PASSWORD=${N8N_USER_PASSWORD:?N8N_USER_PASSWORD must be set in .env}
|
|
volumes:
|
|
- n8n-data:/home/node/.n8n
|
|
- ./local-files:/files
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# Homepage dashboard
|
|
homepage:
|
|
image: ghcr.io/gethomepage/homepage:v0.7.2
|
|
container_name: homepage-changemaker
|
|
ports:
|
|
- "127.0.0.1:${HOMEPAGE_PORT:-3010}:3000"
|
|
volumes:
|
|
- ./configs/homepage:/app/config
|
|
- ./assets/icons:/app/public/icons
|
|
- ./assets/images:/app/public/images
|
|
# Docker socket access removed for security — configure homepage widgets via config files instead
|
|
environment:
|
|
- PUID=${USER_ID:-1000}
|
|
- PGID=${DOCKER_GROUP_ID:-984}
|
|
- TZ=Etc/UTC
|
|
- HOMEPAGE_ALLOWED_HOSTS=*
|
|
- HOMEPAGE_VAR_BASE_URL=${HOMEPAGE_VAR_BASE_URL:-http://localhost}
|
|
restart: unless-stopped
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# Gitea — Git hosting
|
|
gitea-app:
|
|
image: gitea/gitea:1.23.7
|
|
container_name: gitea-changemaker
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 30s
|
|
environment:
|
|
- USER_UID=${USER_ID:-1000}
|
|
- USER_GID=${GROUP_ID:-1000}
|
|
- GITEA__database__DB_TYPE=${GITEA_DB_TYPE:-mysql}
|
|
- GITEA__database__HOST=${GITEA_DB_HOST:-gitea-db:3306}
|
|
- GITEA__database__NAME=${GITEA_DB_NAME:-gitea}
|
|
- GITEA__database__USER=${GITEA_DB_USER:-gitea}
|
|
- GITEA__database__PASSWD=${GITEA_DB_PASSWD}
|
|
- GITEA__server__ROOT_URL=${GITEA_ROOT_URL}
|
|
- GITEA__server__HTTP_PORT=3000
|
|
- GITEA__server__PROTOCOL=http
|
|
- GITEA__server__DOMAIN=${GITEA_DOMAIN}
|
|
- GITEA__server__ENABLE_GZIP=true
|
|
- GITEA__server__X_FRAME_OPTIONS=
|
|
# Increase upload limits for large pushes
|
|
- GITEA__server__LFS_MAX_FILE_SIZE=1024
|
|
- GITEA__repository__upload__FILE_MAX_SIZE=1024
|
|
- GITEA__repository__upload__MAX_FILES=1000
|
|
# Reverse proxy auth — nginx injects X-WEBAUTH-USER for SSO
|
|
- GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION=true
|
|
- GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=false
|
|
- GITEA__service__ENABLE_REVERSE_PROXY_EMAIL=false
|
|
- GITEA__service__REVERSE_PROXY_AUTHENTICATION_HEADER=X-WEBAUTH-USER
|
|
- GITEA__service__REQUIRE_SIGNIN_VIEW=true
|
|
# Skip installation wizard — admin user created by gitea-init container
|
|
- GITEA__security__INSTALL_LOCK=true
|
|
restart: unless-stopped
|
|
volumes:
|
|
- gitea-data:/data
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
ports:
|
|
- "127.0.0.1:${GITEA_WEB_PORT:-3030}:3000"
|
|
- "127.0.0.1:${GITEA_SSH_PORT:-2222}:22"
|
|
depends_on:
|
|
- gitea-db
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
gitea-db:
|
|
image: mysql:8
|
|
container_name: gitea-mysql
|
|
restart: unless-stopped
|
|
environment:
|
|
- MYSQL_ROOT_PASSWORD=${GITEA_DB_ROOT_PASSWORD}
|
|
- MYSQL_USER=${GITEA_DB_USER:-gitea}
|
|
- MYSQL_PASSWORD=${GITEA_DB_PASSWD}
|
|
- MYSQL_DATABASE=${GITEA_DB_NAME:-gitea}
|
|
volumes:
|
|
- mysql-data:/var/lib/mysql
|
|
healthcheck:
|
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "${GITEA_DB_USER:-gitea}", "-p${GITEA_DB_PASSWD}"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# Gitea Init — creates admin user on first boot (after gitea-app is healthy)
|
|
gitea-init:
|
|
image: gitea/gitea:1.23.7
|
|
container_name: gitea-init
|
|
depends_on:
|
|
gitea-app:
|
|
condition: service_healthy
|
|
restart: "no"
|
|
environment:
|
|
- GITEA_ADMIN_USER=${GITEA_ADMIN_USER:-admin}
|
|
- GITEA_ADMIN_PASSWORD=${GITEA_ADMIN_PASSWORD:-}
|
|
- GITEA_ADMIN_EMAIL=${INITIAL_ADMIN_EMAIL:-admin@cmlite.org}
|
|
volumes:
|
|
- gitea-data:/data
|
|
- ./scripts/gitea-init.sh:/init.sh:ro
|
|
entrypoint: ["/bin/sh", "/init.sh"]
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# Mini QR — QR code generator
|
|
mini-qr:
|
|
image: ghcr.io/lyqht/mini-qr:v0.26.0
|
|
container_name: mini-qr
|
|
ports:
|
|
- "127.0.0.1:${MINI_QR_PORT:-8089}:8080"
|
|
restart: unless-stopped
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# Excalidraw — Collaborative whiteboard
|
|
excalidraw:
|
|
image: excalidraw/excalidraw:latest
|
|
container_name: excalidraw-changemaker
|
|
restart: unless-stopped
|
|
ports:
|
|
- "127.0.0.1:${EXCALIDRAW_PORT:-8090}:80"
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:80/"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 20s
|
|
environment:
|
|
- VITE_APP_COLLAB_SERVER_URL=${EXCALIDRAW_WS_URL:-wss://draw.cmlite.org}
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# Vaultwarden — Password manager (Bitwarden-compatible)
|
|
vaultwarden:
|
|
image: vaultwarden/server:1.35.4
|
|
container_name: vaultwarden-changemaker
|
|
restart: unless-stopped
|
|
ports:
|
|
- "127.0.0.1:${VAULTWARDEN_PORT:-8445}:80"
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-sf", "http://localhost:80/alive"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
environment:
|
|
- ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN:-}
|
|
- DOMAIN=${VAULTWARDEN_DOMAIN:-https://vault.cmlite.org}
|
|
- SIGNUPS_ALLOWED=${VAULTWARDEN_SIGNUPS_ALLOWED:-false}
|
|
- WEBSOCKET_ENABLED=${VAULTWARDEN_WEBSOCKET_ENABLED:-true}
|
|
- ROCKET_PORT=80
|
|
- LOG_LEVEL=info
|
|
- SMTP_HOST=${SMTP_HOST:-mailhog-changemaker}
|
|
- SMTP_PORT=${SMTP_PORT:-1025}
|
|
- SMTP_FROM=${SMTP_USER:-noreply@cmlite.org}
|
|
- SMTP_FROM_NAME=${SMTP_FROM_NAME:-Vaultwarden}
|
|
- SMTP_SECURITY=${VAULTWARDEN_SMTP_SECURITY:-off}
|
|
- SMTP_USERNAME=${SMTP_USER:-}
|
|
- SMTP_PASSWORD=${SMTP_PASS:-}
|
|
volumes:
|
|
- vaultwarden-data:/data
|
|
logging: *default-logging
|
|
networks:
|
|
- changemaker-lite
|
|
|
|
# One-shot: invites the initial admin user into Vaultwarden after it starts.
|
|
# Uses the admin panel API to send an invitation email (lands in MailHog or real SMTP).
|
|
# Safe to re-run (Vaultwarden ignores duplicate invites for existing users). Exits 0 on success.
|
|
vaultwarden-init:
|
|
image: alpine/curl:8.11.1
|
|
container_name: vaultwarden-init
|
|
depends_on:
|
|
vaultwarden:
|
|
condition: service_healthy
|
|
restart: "no"
|
|
environment:
|
|
VAULTWARDEN_URL: http://vaultwarden-changemaker:80
|
|
VAULTWARDEN_ADMIN_TOKEN: ${VAULTWARDEN_ADMIN_TOKEN:-}
|
|
INVITE_EMAIL: ${INITIAL_ADMIN_EMAIL:-admin@cmlite.org}
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
echo "[vaultwarden-init] Waiting for Vaultwarden..."
|
|
for i in $(seq 1 20); do
|
|
if curl -sf http://vaultwarden-changemaker:80/alive >/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
|
|
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
|
|
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:
|