changemaker.lite/docker-compose.yml
admin e7890b0be1 Add admin user creation to gancio-init container
The gancio-init container only seeded default color palettes but never
created an admin user, causing the settings sync to silently fail on
every API startup. Now creates an admin user via pgcrypto bcrypt hashing
using GANCIO_ADMIN_USER/GANCIO_ADMIN_PASSWORD env vars, with
ON CONFLICT DO NOTHING for idempotency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:20:19 -07:00

1240 lines
43 KiB
YAML

###############################################################################
# Changemaker Lite v2 — Docker Compose
###############################################################################
services:
# =========================================================================
# V2 CORE SERVICES
# =========================================================================
# Unified Express.js API
api:
build:
context: ./api
target: ${BUILD_TARGET:-development}
container_name: changemaker-v2-api
restart: unless-stopped
ports:
- "${API_PORT:-4000}:4000"
- "${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
- DATABASE_URL=postgresql://${V2_POSTGRES_USER:-changemaker}:${V2_POSTGRES_PASSWORD:-changemaker}@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_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:-REQUIRED_STRONG_PASSWORD_CHANGE_THIS}
- 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_PROXY_PORT=${LISTMONK_PROXY_PORT:-9002}
- 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:-changeme}
- 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}
# Monitoring embed ports (for iframe embedding without DNS/subdomain)
- GRAFANA_EMBED_PORT=${GRAFANA_EMBED_PORT:-8894}
- ALERTMANAGER_EMBED_PORT=${ALERTMANAGER_EMBED_PORT:-8895}
# SMS Campaigns (Termux Android Bridge)
- ENABLE_SMS=${ENABLE_SMS:-false}
- 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 status via socket proxy (read-only, containers endpoint only)
- DOCKER_PROXY_URL=http://docker-socket-proxy:2375
volumes:
- ./api:/app
- /app/node_modules
- ./assets/uploads:/app/uploads
- ./mkdocs:/mkdocs:rw
- ./data:/data:ro
- ./data/upgrade:/app/upgrade:rw
- ./configs:/app/configs:ro
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
depends_on:
v2-postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- changemaker-lite
# Fastify Media API (Microservice for Media Management)
media-api:
build:
context: ./api
dockerfile: Dockerfile.media
target: ${BUILD_TARGET:-development}
container_name: changemaker-media-api
restart: unless-stopped
ports:
- "${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:-changemaker}@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}
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000,http://localhost:3100}
- ENABLE_MEDIA_FEATURES=${ENABLE_MEDIA_FEATURES:-true}
- MEDIA_ROOT=/media/local
- MEDIA_UPLOADS=/media/uploads
- MAX_UPLOAD_SIZE_GB=${MAX_UPLOAD_SIZE_GB:-10}
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
networks:
- changemaker-lite
# React Admin GUI (Vite dev server)
admin:
build:
context: ./admin
target: ${BUILD_TARGET:-development}
container_name: changemaker-v2-admin
restart: unless-stopped
ports:
- "${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
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:-changemaker}
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
networks:
- changemaker-lite
# Nginx reverse proxy
nginx:
build:
context: ./nginx
container_name: changemaker-v2-nginx
restart: unless-stopped
ports:
- "${NGINX_HTTP_PORT:-80}:80"
- "${NGINX_HTTPS_PORT:-443}:443"
- "8881:8881" # NocoDB embed proxy (strips X-Frame-Options)
- "8882:8882" # n8n embed proxy
- "8883:8883" # Gitea embed proxy
- "8884:8884" # MailHog embed proxy
- "8885:8885" # Mini QR embed proxy
- "8886:8886" # Excalidraw embed proxy
- "8887:8887" # Homepage embed proxy
- "8890:8890" # Vaultwarden embed proxy
- "8891:8891" # Rocket.Chat embed proxy
- "8892:8892" # Gancio embed proxy
- "8893:8893" # Jitsi Meet embed proxy
- "8894:8894" # Grafana embed proxy
- "8895:8895" # Alertmanager embed proxy
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:-}
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
- admin
networks:
- changemaker-lite
# NocoDB v2 — pointed at v2 PostgreSQL as read-only data browser
nocodb-v2:
image: nocodb/nocodb:latest
container_name: changemaker-v2-nocodb
restart: unless-stopped
ports:
- "${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:-changemaker}&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
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 allkeys-lru --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:latest
container_name: listmonk-app
restart: unless-stopped
ports:
- "${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
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}
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
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}
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
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}
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"
networks:
- changemaker-lite
# =========================================================================
# PLATFORM SERVICES (kept from v1)
# =========================================================================
# Code Server — Browser IDE
code-server:
build:
context: .
dockerfile: Dockerfile.code-server
container_name: code-server-changemaker
command: /home/coder/project
environment:
- DOCKER_USER=${USER_NAME:-coder}
user: "${USER_ID:-1000}:${GROUP_ID:-1000}"
volumes:
- ./configs/code-server/.config:/home/coder/.config
- ./configs/code-server/.local:/home/coder/.local
- .:/home/coder/project
ports:
- "${CODE_SERVER_PORT:-8888}:8080"
restart: unless-stopped
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:
- "${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
networks:
- changemaker-lite
# MkDocs built site — Nginx static server
mkdocs-site-server:
image: lscr.io/linuxserver/nginx:latest
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:
- "${MKDOCS_SITE_SERVER_PORT:-4004}:80"
restart: unless-stopped
networks:
- changemaker-lite
# n8n — Workflow automation
n8n:
image: docker.n8n.io/n8nio/n8n
container_name: n8n-changemaker
restart: unless-stopped
ports:
- "${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
networks:
- changemaker-lite
# Homepage dashboard
homepage:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage-changemaker
ports:
- "${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
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
restart: unless-stopped
volumes:
- gitea-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "${GITEA_WEB_PORT:-3030}:3000"
- "${GITEA_SSH_PORT:-2222}:22"
depends_on:
- gitea-db
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
networks:
- changemaker-lite
# Mini QR — QR code generator
mini-qr:
image: ghcr.io/lyqht/mini-qr:latest
container_name: mini-qr
ports:
- "${MINI_QR_PORT:-8089}:8080"
restart: unless-stopped
networks:
- changemaker-lite
# Excalidraw — Collaborative whiteboard
excalidraw:
image: kiliandeca/excalidraw:latest
container_name: excalidraw-changemaker
restart: unless-stopped
ports:
- "${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}
networks:
- changemaker-lite
# Vaultwarden — Password manager (Bitwarden-compatible)
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden-changemaker
restart: unless-stopped
ports:
- "${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
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:latest
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"
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://mongodb-rocketchat:27017/rocketchat?replicaSet=rs0
- MONGO_OPLOG_URL=mongodb://mongodb-rocketchat:27017/local?replicaSet=rs0
- 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
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
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
command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
volumes:
- mongodb-rocketchat-data:/data/db
networks:
- changemaker-lite
healthcheck:
test: ["CMD", "mongosh", "--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:latest
container_name: gancio-changemaker
restart: unless-stopped
depends_on:
v2-postgres:
condition: service_healthy
ports:
- "${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:-changemaker}
- server__baseurl=${GANCIO_BASE_URL:-https://events.cmlite.org}
volumes:
- gancio-data:/home/node/data
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:-changemaker}
- 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"
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
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
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
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:
- "${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
networks:
- changemaker-lite
# MailHog — Email testing (dev)
mailhog:
image: mailhog/mailhog:latest
container_name: mailhog-changemaker
ports:
- "${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
networks:
- changemaker-lite
# Docker socket proxy — read-only access for container status monitoring
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:latest
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
networks:
- changemaker-lite
# =========================================================================
# MONITORING (behind profile flag)
# =========================================================================
prometheus:
image: prom/prometheus:latest
container_name: prometheus-changemaker
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
ports:
- "${PROMETHEUS_PORT:-9090}:9090"
volumes:
- ./configs/prometheus:/etc/prometheus
- prometheus-data:/prometheus
restart: always
networks:
- changemaker-lite
profiles:
- monitoring
grafana:
image: grafana/grafana:latest
container_name: grafana-changemaker
ports:
- "${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=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
volumes:
- grafana-data:/var/lib/grafana
- ./configs/grafana:/etc/grafana/provisioning
restart: always
depends_on:
- prometheus
networks:
- changemaker-lite
profiles:
- monitoring
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor-changemaker
ports:
- "${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
privileged: true
devices:
- /dev/kmsg
restart: always
networks:
- changemaker-lite
profiles:
- monitoring
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter-changemaker
ports:
- "${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
networks:
- changemaker-lite
profiles:
- monitoring
redis-exporter:
image: oliver006/redis_exporter:latest
container_name: redis-exporter-changemaker
ports:
- "${REDIS_EXPORTER_PORT:-9121}:9121"
environment:
- REDIS_ADDR=redis://redis-changemaker:6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
restart: always
depends_on:
- redis-changemaker
networks:
- changemaker-lite
profiles:
- monitoring
alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager-changemaker
ports:
- "${ALERTMANAGER_PORT:-9093}:9093"
volumes:
- ./configs/alertmanager:/etc/alertmanager
- alertmanager-data:/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
restart: always
networks:
- changemaker-lite
profiles:
- monitoring
gotify:
image: gotify/server:latest
container_name: gotify-changemaker
ports:
- "${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
networks:
- changemaker-lite
profiles:
- monitoring
# =============================================================================
# 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: