managarten/docker-compose.macmini.yml
Till JS 4a48182677 feat(monitoring): integrate Promtail for centralized log collection via Loki
Loki was already running but had no log shipper. Adds Promtail to collect
Docker logs from all 66 containers with automatic tier labeling (infra,
auth, core, app, matrix, games) and a Grafana Logs Explorer dashboard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:22:44 +02:00

1943 lines
63 KiB
YAML

# Mana Mac Mini Configuration
# Domain: mana.how (via Cloudflare Tunnel)
#
# Port Schema:
# 3000-3099: Core Services & Backends
# 4000-4099: Matrix Stack
# 5000-5099: Web Frontends
# 5100-5199: Games
# 8000-8099: Monitoring Dashboards
# 9000-9199: Infrastructure & Exporters
#
# Naming Convention: mana-{category}-{service}
# Categories: infra, core, app, matrix, mon, auto
#
# Memory Limits:
# All containers have explicit mem_limit to prevent unbounded growth.
# Total budget: ~9.8 GiB (fits in 12 GiB Colima VM with ~2 GiB for builds)
# Run ./scripts/mac-mini/memory-baseline.sh to verify actual usage.
# Limits are ceilings — actual usage is typically 50-70% of limits.
services:
# ============================================
# Tier 0: Infrastructure Services
# ============================================
postgres:
image: postgres:16-alpine
container_name: mana-infra-postgres
restart: always
mem_limit: 1024m
environment:
POSTGRES_DB: mana
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mana123}
volumes:
- /Volumes/ManaData/postgres:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 5s
retries: 5
start_period: 10s
command: >
postgres
-c shared_buffers=512MB
-c effective_cache_size=2GB
-c work_mem=16MB
-c maintenance_work_mem=128MB
-c max_wal_size=1GB
-c max_wal_senders=3
-c log_min_duration_statement=1000
-c log_checkpoints=on
# PostgreSQL Backup — hourly pg_dumpall + daily pg_basebackup
# Retention: 48 hourly dumps + 30 daily base backups
# Restore: see docs/POSTGRES_BACKUP.md
postgres-backup:
image: postgres:16-alpine
container_name: mana-infra-postgres-backup
restart: unless-stopped
mem_limit: 128m
depends_on:
postgres:
condition: service_healthy
environment:
PGHOST: postgres
PGUSER: postgres
PGPASSWORD: ${POSTGRES_PASSWORD:-mana123}
BACKUP_DIR: /backups
RETENTION_HOURLY: 48
RETENTION_DAILY: 30
volumes:
- /Volumes/ManaData/backups/postgres:/backups
entrypoint: >
/bin/sh -c "
echo 'PostgreSQL Backup Service started';
echo 'Hourly: pg_dumpall (retention: 48)';
echo 'Daily 03:00: pg_basebackup (retention: 30)';
while true; do
HOUR=$$(date +%H);
TIMESTAMP=$$(date +%Y%m%d_%H%M%S);
echo \"[$$TIMESTAMP] Running hourly backup...\";
pg_dumpall -h postgres -U postgres | gzip > /backups/hourly_$$TIMESTAMP.sql.gz;
if [ \"$$HOUR\" = '03' ]; then
echo \"[$$TIMESTAMP] Running daily base backup...\";
mkdir -p /backups/base_$$TIMESTAMP;
pg_basebackup -h postgres -U postgres -D /backups/base_$$TIMESTAMP -Ft -z -P;
fi;
find /backups -name 'hourly_*.sql.gz' -mmin +$$((48*60)) -delete 2>/dev/null;
find /backups -name 'base_*' -maxdepth 1 -type d -mtime +30 -exec rm -rf {} + 2>/dev/null;
echo \"[$$TIMESTAMP] Backup complete. Sleeping 1h...\";
sleep 3600;
done
"
# Self-hosted Landing Pages (replaces Cloudflare Pages)
# Serves all Astro landing page dist/ folders via Nginx
# Build with: ./scripts/mac-mini/build-landings.sh
landings:
image: nginx:alpine
container_name: mana-infra-landings
restart: always
mem_limit: 48m
volumes:
- ./docker/nginx:/etc/nginx/host-config:ro
- /Volumes/ManaData/landings:/srv/landings:ro
command: >
sh -c "cp /etc/nginx/host-config/landings.conf /etc/nginx/conf.d/default.conf &&
cp -r /etc/nginx/host-config/snippets/* /etc/nginx/snippets/ 2>/dev/null;
nginx -g 'daemon off;'"
ports:
- "4400:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/health"]
interval: 30s
timeout: 5s
retries: 3
redis:
image: redis:7-alpine
container_name: mana-infra-redis
restart: always
mem_limit: 192m
command: redis-server --requirepass ${REDIS_PASSWORD:-redis123}
volumes:
- redis_data:/data
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 30s
timeout: 5s
retries: 5
start_period: 10s
minio:
image: minio/minio:latest
container_name: mana-infra-minio
restart: always
mem_limit: 256m
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
MINIO_API_CORS_ALLOW_ORIGIN: https://mukke.mana.how,https://mana.how,https://picture.mana.how,https://storage.mana.how,https://planta.mana.how,https://contacts.mana.how,https://chat.mana.how,https://nutriphi.mana.how,https://photos.mana.how
volumes:
- /Volumes/ManaData/minio:/data
ports:
- "9000:9000"
- "9001:9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 30s
timeout: 20s
retries: 3
start_period: 15s
# MinIO bucket initialization and lifecycle rules (runs once)
minio-init:
image: minio/mc:latest
container_name: mana-infra-minio-init
mem_limit: 64m
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set myminio http://minio:9000 $${MINIO_ACCESS_KEY:-minioadmin} $${MINIO_SECRET_KEY:-minioadmin};
mc mb --ignore-existing myminio/manacore-storage;
mc mb --ignore-existing myminio/picture-storage;
mc mb --ignore-existing myminio/chat-storage;
mc mb --ignore-existing myminio/manadeck-storage;
mc mb --ignore-existing myminio/presi-storage;
mc mb --ignore-existing myminio/calendar-storage;
mc mb --ignore-existing myminio/contacts-storage;
mc mb --ignore-existing myminio/storage-storage;
mc mb --ignore-existing myminio/inventory-storage;
mc mb --ignore-existing myminio/mukke-storage;
mc mb --ignore-existing myminio/planta-storage;
mc mb --ignore-existing myminio/projectdoc-storage;
mc mb --ignore-existing myminio/mail-storage;
mc anonymous set download myminio/manacore-storage;
mc anonymous set download myminio/picture-storage;
mc anonymous set download myminio/planta-storage;
mc anonymous set download myminio/inventory-storage;
mc ilm rule add --expire-days 90 myminio/chat-storage --prefix 'tmp/' 2>/dev/null || true;
mc ilm rule add --expire-days 30 myminio/calendar-storage --prefix 'tmp/' 2>/dev/null || true;
mc ilm rule add --expire-days 7 myminio/picture-storage --prefix 'tmp/' 2>/dev/null || true;
echo 'Buckets and lifecycle rules created successfully';
exit 0;
"
# ============================================
# Tier 0b: Forgejo (Git + CI/CD + Registry)
# ============================================
forgejo:
image: codeberg.org/forgejo/forgejo:11
container_name: mana-core-forgejo
restart: always
mem_limit: 512m
depends_on:
postgres:
condition: service_healthy
environment:
USER_UID: 1000
USER_GID: 1000
FORGEJO__database__DB_TYPE: postgres
FORGEJO__database__HOST: postgres:5432
FORGEJO__database__NAME: forgejo
FORGEJO__database__USER: postgres
FORGEJO__database__PASSWD: ${POSTGRES_PASSWORD:-mana123}
FORGEJO__server__DOMAIN: git.mana.how
FORGEJO__server__SSH_DOMAIN: git.mana.how
FORGEJO__server__ROOT_URL: https://git.mana.how/
FORGEJO__server__HTTP_PORT: 3000
FORGEJO__server__SSH_PORT: 2222
FORGEJO__server__LFS_START_SERVER: "true"
FORGEJO__service__DISABLE_REGISTRATION: "true"
FORGEJO__service__REQUIRE_SIGNIN_VIEW: "false"
FORGEJO__actions__ENABLED: "true"
FORGEJO__actions__DEFAULT_ACTIONS_URL: https://code.forgejo.org
FORGEJO__packages__ENABLED: "true"
FORGEJO__ui__DEFAULT_THEME: forgejo-dark
FORGEJO__ui__SHOW_USER_EMAIL: "false"
FORGEJO__mailer__ENABLED: "false"
volumes:
- /Volumes/ManaData/forgejo:/data
ports:
- "3041:3000"
- "2222:2222"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/v1/version"]
interval: 120s
timeout: 10s
retries: 3
start_period: 30s
forgejo-runner:
image: code.forgejo.org/forgejo/runner:6.3.1
container_name: mana-core-forgejo-runner
command: forgejo-runner daemon
restart: always
mem_limit: 256m
user: "0:0"
depends_on:
forgejo:
condition: service_healthy
environment:
DOCKER_HOST: unix:///var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /Volumes/ManaData/forgejo-runner:/data
# ============================================
# Tier 1: Core Auth Service (Port 3001)
# ============================================
mana-auth:
build:
context: services/mana-auth
dockerfile: Dockerfile
image: mana-auth:local
container_name: mana-auth
restart: always
mem_limit: 192m
depends_on:
postgres:
condition: service_healthy
environment:
TZ: Europe/Berlin
NODE_ENV: production
PORT: 3001
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_auth
BASE_URL: https://auth.mana.how
COOKIE_DOMAIN: .mana.how
MANA_CORE_SERVICE_KEY: ${MANA_CORE_SERVICE_KEY}
MANA_CREDITS_URL: http://mana-credits:3061
MANA_SUBSCRIPTIONS_URL: http://mana-subscriptions:3063
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-${JWT_SECRET:-your-jwt-secret-change-me}}
SMTP_HOST: smtp-relay.brevo.com
SMTP_PORT: 587
SMTP_USER: ${SMTP_USER:-94cde5002@smtp-brevo.com}
SMTP_PASS: ${SMTP_PASSWORD}
SYNAPSE_OIDC_CLIENT_SECRET: ${SYNAPSE_OIDC_CLIENT_SECRET:-}
MAX_DAILY_SIGNUPS: ${MAX_DAILY_SIGNUPS:-0}
CORS_ORIGINS: https://mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://docs.mana.how,https://element.mana.how,https://inventar.mana.how,https://link.mana.how,https://manadeck.mana.how,https://matrix.mana.how,https://mukke.mana.how,https://nutriphi.mana.how,https://photos.mana.how,https://picture.mana.how,https://planta.mana.how,https://playground.mana.how,https://presi.mana.how,https://questions.mana.how,https://skilltree.mana.how,https://storage.mana.how,https://taktik.mana.how,https://todo.mana.how,https://traces.mana.how,https://zitare.mana.how
ports:
- "3001:3001"
healthcheck:
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3001/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 120s
timeout: 10s
retries: 3
start_period: 40s
# ============================================
# Tier 1b: Credits Service (Hono + Bun)
# ============================================
mana-credits:
build:
context: services/mana-credits
dockerfile: Dockerfile
image: mana-credits:local
container_name: mana-credits
restart: always
mem_limit: 128m
depends_on:
postgres:
condition: service_healthy
environment:
TZ: Europe/Berlin
PORT: 3002
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_credits
MANA_CORE_AUTH_URL: http://mana-auth:3001
MANA_CORE_SERVICE_KEY: ${MANA_CORE_SERVICE_KEY}
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
STRIPE_WEBHOOK_SECRET: ${STRIPE_CREDITS_WEBHOOK_SECRET:-}
BASE_URL: https://credits.mana.how
CORS_ORIGINS: https://mana.how,https://chat.mana.how,https://picture.mana.how,https://todo.mana.how,https://zitare.mana.how,https://calendar.mana.how,https://clock.mana.how,https://contacts.mana.how,https://manadeck.mana.how,https://presi.mana.how,https://storage.mana.how,https://nutriphi.mana.how,https://planta.mana.how,https://mukke.mana.how,https://context.mana.how,https://photos.mana.how,https://questions.mana.how,https://calc.mana.how
ports:
- "3002:3002"
healthcheck:
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3002/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 120s
timeout: 10s
retries: 3
start_period: 15s
labels:
- "traefik.enable=true"
- "traefik.http.routers.mana-credits.rule=Host(`credits.mana.how`)"
- "traefik.http.routers.mana-credits.tls=true"
- "traefik.http.services.mana-credits.loadbalancer.server.port=3002"
mana-user:
build:
context: services/mana-user
dockerfile: Dockerfile
image: mana-user:local
container_name: mana-user
restart: always
mem_limit: 128m
depends_on:
postgres: { condition: service_healthy }
environment:
TZ: Europe/Berlin
PORT: 3062
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_user
MANA_CORE_AUTH_URL: http://mana-auth:3001
MANA_CORE_SERVICE_KEY: ${MANA_CORE_SERVICE_KEY}
CORS_ORIGINS: https://mana.how,https://calc.mana.how,https://calendar.mana.how,https://chat.mana.how,https://clock.mana.how,https://contacts.mana.how,https://context.mana.how,https://manadeck.mana.how,https://mukke.mana.how,https://nutriphi.mana.how,https://photos.mana.how,https://picture.mana.how,https://planta.mana.how,https://presi.mana.how,https://questions.mana.how,https://storage.mana.how,https://todo.mana.how,https://zitare.mana.how
ports:
- "3062:3062"
healthcheck:
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3062/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 120s
timeout: 10s
retries: 3
start_period: 15s
mana-subscriptions:
build:
context: services/mana-subscriptions
dockerfile: Dockerfile
image: mana-subscriptions:local
container_name: mana-subscriptions
restart: always
mem_limit: 128m
depends_on:
postgres: { condition: service_healthy }
environment:
TZ: Europe/Berlin
PORT: 3063
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_subscriptions
MANA_CORE_AUTH_URL: http://mana-auth:3001
MANA_CORE_SERVICE_KEY: ${MANA_CORE_SERVICE_KEY}
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
STRIPE_WEBHOOK_SECRET: ${STRIPE_SUBSCRIPTIONS_WEBHOOK_SECRET:-}
BASE_URL: https://subscriptions.mana.how
CORS_ORIGINS: https://mana.how
ports:
- "3063:3063"
healthcheck:
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3063/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 120s
timeout: 10s
retries: 3
start_period: 15s
mana-analytics:
build:
context: services/mana-analytics
dockerfile: Dockerfile
image: mana-analytics:local
container_name: mana-analytics
restart: always
mem_limit: 128m
depends_on:
postgres: { condition: service_healthy }
environment:
TZ: Europe/Berlin
PORT: 3064
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_analytics
MANA_CORE_AUTH_URL: http://mana-auth:3001
MANA_LLM_URL: http://mana-llm:3025
CORS_ORIGINS: https://mana.how
ports:
- "3064:3064"
healthcheck:
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3064/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 120s
timeout: 10s
retries: 3
start_period: 15s
# ============================================
# Tier 2: Gateway & Search Services (Ports 3010-3029)
# ============================================
api-gateway:
build:
context: .
dockerfile: services/mana-api-gateway/Dockerfile
image: mana-api-gateway:local
container_name: mana-api-gateway
restart: always
mem_limit: 64m
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
TZ: Europe/Berlin
PORT: 3016
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana?sslmode=disable
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}
MANA_CORE_AUTH_URL: http://mana-auth:3001
SEARCH_SERVICE_URL: http://mana-search:3012
STT_SERVICE_URL: ${STT_SERVICE_URL:-http://192.168.178.11:3020}
TTS_SERVICE_URL: ${TTS_SERVICE_URL:-http://192.168.178.11:3022}
CORS_ORIGINS: https://api.mana.how,https://mana.how
ADMIN_USER_IDS: ${ADMIN_USER_IDS:-}
ports:
- "3016:3016"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3016/health"]
interval: 60s
timeout: 5s
retries: 3
start_period: 5s
searxng:
image: searxng/searxng:latest
container_name: mana-core-searxng
restart: always
mem_limit: 256m
volumes:
- ./services/mana-search/searxng:/mnt/searxng-config:ro
entrypoint: >
sh -c "cp /mnt/searxng-config/settings.yml /etc/searxng/settings.yml &&
cp /mnt/searxng-config/limiter.toml /etc/searxng/limiter.toml 2>/dev/null;
exec /usr/local/searxng/dockerfiles/docker-entrypoint.sh"
environment:
SEARXNG_BASE_URL: http://searxng:8080
SEARXNG_SECRET: ${SEARXNG_SECRET:-change-me-searxng-secret}
# Internal only - no external port mapping
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/healthz"]
interval: 120s
timeout: 10s
retries: 3
start_period: 15s
mana-search:
build:
context: .
dockerfile: services/mana-search/Dockerfile
image: mana-search:local
container_name: mana-core-search
restart: always
mem_limit: 64m
depends_on:
searxng:
condition: service_healthy
environment:
PORT: 3012
SEARXNG_URL: http://searxng:8080
SEARXNG_TIMEOUT: 15000
SEARXNG_DEFAULT_LANGUAGE: de-DE
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}
CACHE_SEARCH_TTL: 3600
CACHE_EXTRACT_TTL: 86400
EXTRACT_TIMEOUT: 10000
EXTRACT_MAX_LENGTH: 50000
ports:
- "3012:3012"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3012/health"]
interval: 120s
timeout: 10s
retries: 3
start_period: 5s
mana-sync:
build:
context: services/mana-sync
dockerfile: Dockerfile
image: mana-sync:local
container_name: mana-core-sync
restart: always
mem_limit: 64m
depends_on:
postgres:
condition: service_healthy
environment:
PORT: 3010
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana?sslmode=disable
JWKS_URL: http://mana-auth:3001/api/v1/auth/jwks
CORS_ORIGINS: "https://mana.how,https://*.mana.how"
ports:
- "3010:3010"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3010/health"]
interval: 120s
timeout: 5s
retries: 3
start_period: 5s
mana-notify:
build:
context: .
dockerfile: services/mana-notify/Dockerfile
image: mana-notify:local
container_name: mana-core-notify
restart: always
mem_limit: 64m
depends_on:
postgres:
condition: service_healthy
environment:
PORT: 3013
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana?sslmode=disable
SERVICE_KEY: ${NOTIFY_SERVICE_KEY:-dev-service-key}
MANA_CORE_AUTH_URL: http://mana-auth:3001
SMTP_HOST: ${SMTP_HOST:-smtp-relay.brevo.com}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: "ManaCore <noreply@mana.how>"
EXPO_ACCESS_TOKEN: ${EXPO_ACCESS_TOKEN:-}
MATRIX_HOMESERVER_URL: http://mana-matrix-synapse:8008
MATRIX_ACCESS_TOKEN: ${MATRIX_NOTIFY_BOT_TOKEN:-}
ports:
- "3013:3013"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3013/health"]
interval: 120s
timeout: 5s
retries: 3
start_period: 5s
mana-crawler:
build:
context: .
dockerfile: services/mana-crawler/Dockerfile
image: mana-crawler:local
container_name: mana-crawler
restart: always
mem_limit: 128m
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
TZ: Europe/Berlin
PORT: 3014
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana?sslmode=disable
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}
CRAWLER_USER_AGENT: "ManaCoreCrawler/1.0 (+https://mana.how/bot)"
QUEUE_CONCURRENCY: 5
ports:
- "3014:3014"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3014/health"]
interval: 60s
timeout: 5s
retries: 3
start_period: 5s
mana-media:
build:
context: services/mana-media/apps/api
image: mana-media:local
container_name: mana-core-media
restart: always
mem_limit: 128m
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
minio:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 3011
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/mana_media
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}
S3_ENDPOINT: minio
S3_PORT: 9000
S3_USE_SSL: "false"
S3_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
S3_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
S3_BUCKET: mana-media
S3_PUBLIC_URL: https://media.mana.how
MATRIX_HOMESERVER_URL: https://matrix.mana.how
PUBLIC_URL: https://media.mana.how/api/v1
CORS_ORIGINS: https://mana.how,https://nutriphi.mana.how,https://contacts.mana.how,https://chat.mana.how,https://storage.mana.how,https://photos.mana.how
ports:
- "3011:3011"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3011/health"]
interval: 120s
timeout: 10s
retries: 3
start_period: 10s
mana-landing-builder:
build:
context: .
dockerfile: services/mana-landing-builder/Dockerfile
image: mana-landing-builder:local
container_name: mana-core-landing-builder
restart: always
mem_limit: 192m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 3015
MANA_CORE_AUTH_URL: http://mana-auth:3001
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN:-}
CLOUDFLARE_ACCOUNT_ID: ${CLOUDFLARE_ACCOUNT_ID:-}
ORG_LANDING_DOMAIN: mana.how
ports:
- "3015:3015"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3015/api/v1/health"]
interval: 120s
timeout: 10s
retries: 3
start_period: 40s
# ============================================
# Tier 3: App Compute Servers (Hono + Bun)
# CRUD is handled by mana-sync. These only handle
# server-side compute (AI, file upload, external APIs).
# ============================================
# --- App Compute Servers (Hono + Bun, ~120 LOC each) ---
# CRUD handled by mana-sync. These handle AI, uploads, external APIs.
# All use FROM oven/bun:1, ~160MB images, ~30MB RAM, <50ms cold start.
# Ports match the old NestJS backends for backward compatibility.
# NOTE: These apps need Hono server Dockerfiles to be added.
# For now they share the same pattern:
# build: { context: apps/{app}/apps/server, dockerfile: ../../Dockerfile.bun }
# Requires: Dockerfile.bun in apps/ root (FROM oven/bun:1, COPY, CMD bun run src/index.ts)
# ============================================
# Tier 4: Matrix Stack (Ports 4000-4099)
# ============================================
synapse:
image: matrixdotorg/synapse:latest
container_name: mana-matrix-synapse
restart: always
mem_limit: 512m
depends_on:
postgres:
condition: service_healthy
entrypoint: >
sh -c "mkdir -p /config &&
cp /mnt/synapse-config/*.yaml /config/ 2>/dev/null;
cp -r /mnt/synapse-config/appservices /config/ 2>/dev/null;
exec python -m synapse.app.homeserver -c /config/homeserver.yaml"
environment:
TZ: Europe/Berlin
SYNAPSE_DB_PASSWORD: ${SYNAPSE_DB_PASSWORD:-synapse-secure-password}
SYNAPSE_PASSWORD_PEPPER: ${SYNAPSE_PASSWORD_PEPPER:-change-me-pepper}
SYNAPSE_FORM_SECRET: ${SYNAPSE_FORM_SECRET:-change-me-form-secret}
SYNAPSE_MACAROON_SECRET: ${SYNAPSE_MACAROON_SECRET:-change-me-macaroon-secret}
SYNAPSE_REGISTRATION_SECRET: ${SYNAPSE_REGISTRATION_SECRET:-change-me-registration-secret}
SYNAPSE_OIDC_CLIENT_SECRET: ${SYNAPSE_OIDC_CLIENT_SECRET}
volumes:
- ./docker/matrix/config:/mnt/synapse-config:ro
- ./docker/matrix/data:/data
ports:
- "4000:8008"
- "9002:9002" # Metrics
healthcheck:
test: ["CMD", "curl", "-fSs", "http://localhost:8008/health"]
interval: 120s
timeout: 10s
retries: 3
start_period: 60s
element-web:
image: vectorim/element-web:latest
container_name: mana-matrix-element
restart: always
mem_limit: 48m
depends_on:
synapse:
condition: service_healthy
volumes:
- ./docker/matrix/element-config.json:/app/config.json:ro
ports:
- "4010:80"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
matrix-web:
build:
context: .
dockerfile: apps/matrix/apps/web/Dockerfile
image: matrix-web:latest
container_name: mana-matrix-web
restart: always
mem_limit: 96m
depends_on:
synapse:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 4011
PUBLIC_MANA_CORE_AUTH_URL: https://auth.mana.how
ports:
- "4011:4011"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4011/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
# ============================================
# Matrix Bots — Consolidated Go Service
# Replaces 21 separate NestJS bot containers
# ============================================
mana-matrix-bot:
build:
context: .
dockerfile: services/mana-matrix-bot/Dockerfile
image: mana-matrix-bot:local
container_name: mana-matrix-bot
restart: always
mem_limit: 128m
depends_on:
synapse:
condition: service_healthy
redis:
condition: service_healthy
environment:
TZ: Europe/Berlin
PORT: 4001
# Matrix
MATRIX_HOMESERVER_URL: http://synapse:8008
MATRIX_STORAGE_PATH: /app/data
# Auth & Redis
MANA_CORE_AUTH_URL: http://mana-auth:3001
MANA_CORE_SERVICE_KEY: ${MANA_CORE_SERVICE_KEY}
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}
# Voice services (GPU server via LAN)
STT_URL: ${STT_SERVICE_URL:-http://192.168.178.11:3020}
TTS_URL: ${TTS_SERVICE_URL:-http://192.168.178.11:3022}
# AI (GPU server via LAN)
OLLAMA_URL: ${OLLAMA_URL:-http://192.168.178.11:11434}
OLLAMA_MODEL: ${OLLAMA_MODEL:-gemma3:12b}
# Plugin tokens (all 21 bot identities)
MATRIX_MANA_BOT_TOKEN: ${MATRIX_MANA_BOT_TOKEN}
MATRIX_MANA_BOT_ROOMS: ${MATRIX_MANA_BOT_ROOMS:-}
MATRIX_TODO_BOT_TOKEN: ${MATRIX_TODO_BOT_TOKEN}
MATRIX_TODO_BOT_ROOMS: ${MATRIX_TODO_BOT_ROOMS:-}
MATRIX_CALENDAR_BOT_TOKEN: ${MATRIX_CALENDAR_BOT_TOKEN}
MATRIX_CALENDAR_BOT_ROOMS: ${MATRIX_CALENDAR_BOT_ROOMS:-}
MATRIX_CLOCK_BOT_TOKEN: ${MATRIX_CLOCK_BOT_TOKEN}
MATRIX_CLOCK_BOT_ROOMS: ${MATRIX_CLOCK_BOT_ROOMS:-}
MATRIX_OLLAMA_BOT_TOKEN: ${MATRIX_OLLAMA_BOT_TOKEN}
MATRIX_OLLAMA_BOT_ROOMS: ${MATRIX_OLLAMA_BOT_ROOMS:-}
MATRIX_STATS_BOT_TOKEN: ${MATRIX_STATS_BOT_TOKEN}
MATRIX_STATS_BOT_ROOMS: ${MATRIX_STATS_BOT_ROOMS:-}
MATRIX_CONTACTS_BOT_TOKEN: ${MATRIX_CONTACTS_BOT_TOKEN:-}
MATRIX_CONTACTS_BOT_ROOMS: ${MATRIX_CONTACTS_BOT_ROOMS:-}
MATRIX_CHAT_BOT_TOKEN: ${MATRIX_CHAT_BOT_TOKEN:-}
MATRIX_MANADECK_BOT_TOKEN: ${MATRIX_MANADECK_BOT_TOKEN:-}
MATRIX_NUTRIPHI_BOT_TOKEN: ${MATRIX_NUTRIPHI_BOT_TOKEN}
MATRIX_NUTRIPHI_BOT_ROOMS: ${MATRIX_NUTRIPHI_BOT_ROOMS:-}
MATRIX_PICTURE_BOT_TOKEN: ${MATRIX_PICTURE_BOT_TOKEN:-}
MATRIX_PLANTA_BOT_TOKEN: ${MATRIX_PLANTA_BOT_TOKEN}
MATRIX_PLANTA_BOT_ROOMS: ${MATRIX_PLANTA_BOT_ROOMS:-}
MATRIX_PRESI_BOT_TOKEN: ${MATRIX_PRESI_BOT_TOKEN:-}
MATRIX_QUESTIONS_BOT_TOKEN: ${MATRIX_QUESTIONS_BOT_TOKEN:-}
MATRIX_SKILLTREE_BOT_TOKEN: ${MATRIX_SKILLTREE_BOT_TOKEN:-}
MATRIX_STORAGE_BOT_TOKEN: ${MATRIX_STORAGE_BOT_TOKEN:-}
MATRIX_PROJECT_DOC_BOT_TOKEN: ${MATRIX_PROJECT_DOC_BOT_TOKEN}
MATRIX_STT_BOT_TOKEN: ${MATRIX_STT_BOT_TOKEN}
MATRIX_STT_BOT_ROOMS: ${MATRIX_STT_BOT_ROOMS:-}
MATRIX_TTS_BOT_TOKEN: ${MATRIX_TTS_BOT_TOKEN}
MATRIX_TTS_BOT_ROOMS: ${MATRIX_TTS_BOT_ROOMS:-}
MATRIX_ZITARE_BOT_TOKEN: ${MATRIX_ZITARE_BOT_TOKEN}
MATRIX_ZITARE_BOT_ROOMS: ${MATRIX_ZITARE_BOT_ROOMS:-}
MATRIX_ONBOARDING_BOT_TOKEN: ${MATRIX_ONBOARDING_BOT_TOKEN}
MATRIX_ONBOARDING_BOT_ROOMS: ${MATRIX_ONBOARDING_BOT_ROOMS:-}
# Backend URLs
TODO_BACKEND_URL: http://todo-backend:3031
CALENDAR_BACKEND_URL: http://calendar-backend:3032
# CLOCK_BACKEND_URL: removed — migrated to local-first
CONTACTS_BACKEND_URL: http://contacts-backend:3033
# ZITARE_BACKEND_URL: removed — migrated to local-first
PLANTA_BACKEND_URL: http://planta-backend:3039
NUTRIPHI_BACKEND_URL: http://nutriphi-backend:3038
STORAGE_BACKEND_URL: http://storage-backend:3034
volumes:
- matrix_bots_data:/app/data
# No host port mapping needed — only communicates with synapse internally
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4001/health"]
interval: 60s
timeout: 5s
retries: 3
start_period: 10s
# ============================================
# Tier 5: Web Frontends (Ports 5000-5099)
# ============================================
mana-web:
build:
context: .
dockerfile: apps/manacore/apps/web/Dockerfile
image: manacore-web:local
container_name: mana-app-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5000
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_TODO_API_URL: http://todo-backend:3031
PUBLIC_TODO_API_URL_CLIENT: https://todo-api.mana.how
PUBLIC_CALENDAR_API_URL: http://calendar-backend:3032
PUBLIC_CALENDAR_API_URL_CLIENT: https://calendar-api.mana.how
# PUBLIC_CLOCK_API_URL: removed — migrated to local-first
PUBLIC_CONTACTS_API_URL: http://contacts-backend:3033
PUBLIC_CONTACTS_API_URL_CLIENT: https://contacts-api.mana.how
ports:
- "5000:5000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5000/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 20s
chat-web:
image: ghcr.io/memo-2023/chat-web:latest
container_name: mana-app-chat-web
restart: always
mem_limit: 128m
environment:
NODE_ENV: production
PORT: 5010
PUBLIC_BACKEND_URL: http://chat-backend:3030
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_BACKEND_URL_CLIENT: https://chat-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
ports:
- "5010:5010"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5010/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 20s
todo-web:
build:
context: .
dockerfile: apps/todo/apps/web/Dockerfile
image: todo-web:local
container_name: mana-app-todo-web
restart: always
mem_limit: 128m
environment:
NODE_ENV: production
PORT: 5011
PUBLIC_BACKEND_URL: http://todo-backend:3031
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_BACKEND_URL_CLIENT: https://todo-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
ports:
- "5011:5011"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5011/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 20s
zitare-web:
build:
context: .
dockerfile: apps/zitare/apps/web/Dockerfile
image: zitare-web:local
container_name: mana-app-zitare-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5026
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5026:5026"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5026/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 35s
calendar-web:
build:
context: .
dockerfile: apps/calendar/apps/web/Dockerfile
image: calendar-web:local
container_name: mana-app-calendar-web
restart: always
mem_limit: 128m
environment:
NODE_ENV: production
PORT: 5012
PUBLIC_BACKEND_URL: http://calendar-backend:3032
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_BACKEND_URL_CLIENT: https://calendar-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_TODO_BACKEND_URL: https://todo-api.mana.how
PUBLIC_CONTACTS_API_URL: https://contacts-api.mana.how
ports:
- "5012:5012"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5012/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 30s
clock-web:
image: ghcr.io/memo-2023/clock-web:latest
container_name: mana-app-clock-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5013
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5013:5013"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5013/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 30s
contacts-web:
build:
context: .
dockerfile: apps/contacts/apps/web/Dockerfile
args:
PUBLIC_BACKEND_URL: http://contacts-backend:3033
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
image: contacts-web:local
container_name: mana-app-contacts-web
restart: always
mem_limit: 128m
environment:
NODE_ENV: production
PORT: 5014
PUBLIC_BACKEND_URL: http://contacts-backend:3033
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_BACKEND_URL_CLIENT: https://contacts-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_TODO_BACKEND_URL: https://todo-api.mana.how
ports:
- "5014:5014"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5014/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 30s
storage-web:
build:
context: .
dockerfile: apps/storage/apps/web/Dockerfile
args:
PUBLIC_BACKEND_URL: http://storage-backend:3034
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
image: storage-web:local
container_name: mana-app-storage-web
restart: always
mem_limit: 128m
environment:
NODE_ENV: production
PORT: 5015
PUBLIC_BACKEND_URL: http://storage-backend:3034
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_BACKEND_URL_CLIENT: https://storage-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
ports:
- "5015:5015"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5015/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 35s
presi-web:
image: ghcr.io/memo-2023/presi-web:latest
container_name: mana-app-presi-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5016
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5016:5016"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5016/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
manadeck-web:
build:
context: .
dockerfile: apps/manadeck/apps/web/Dockerfile
image: manadeck-web:local
container_name: mana-app-manadeck-web
restart: always
mem_limit: 128m
environment:
NODE_ENV: production
PORT: 5023
PUBLIC_BACKEND_URL: http://manadeck-backend:3036
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_BACKEND_URL_CLIENT: https://manadeck-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
ports:
- "5023:5023"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5023/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
nutriphi-web:
image: ghcr.io/memo-2023/nutriphi-web:latest
container_name: mana-app-nutriphi-web
restart: always
mem_limit: 128m
environment:
NODE_ENV: production
PORT: 5017
PUBLIC_BACKEND_URL: http://nutriphi-backend:3038
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_BACKEND_URL_CLIENT: https://nutriphi-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
ports:
- "5017:5017"
healthcheck:
test: ["CMD", "node", "-e", "const http = require('http'); http.get('http://127.0.0.1:5017/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
skilltree-web:
build:
context: .
dockerfile: apps/skilltree/apps/web/Dockerfile
args:
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
image: skilltree-web:local
container_name: mana-app-skilltree-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5020
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-core-sync:3010
ports:
- "5020:5020"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5020/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
photos-web:
build:
context: .
dockerfile: apps/photos/apps/web/Dockerfile
args:
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_MEDIA_URL: http://mana-media:3011
image: photos-web:local
container_name: mana-app-photos-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5019
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_MEDIA_URL: http://mana-media:3011
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_MANA_MEDIA_URL_CLIENT: https://media.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5019:5019"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5019/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
mukke-web:
build:
context: .
dockerfile: apps/mukke/apps/web/Dockerfile
args:
PUBLIC_BACKEND_URL: http://mukke-backend:3037
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
image: mukke-web:local
container_name: mana-app-mukke-web
restart: always
mem_limit: 128m
environment:
NODE_ENV: production
PORT: 5024
PUBLIC_BACKEND_URL: http://mukke-backend:3037
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_BACKEND_URL_CLIENT: https://mukke-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
ports:
- "5024:5024"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5024/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
citycorners-web:
build:
context: .
dockerfile: apps/citycorners/apps/web/Dockerfile
args:
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
image: citycorners-web:local
container_name: mana-app-citycorners-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5022
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-core-sync:3010
ports:
- "5022:5022"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5022/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
# picture-backend: REMOVED — replaced by Hono server (apps/picture/apps/server)
picture-web:
build:
context: .
dockerfile: apps/picture/apps/web/Dockerfile
args:
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
image: picture-web:local
container_name: mana-app-picture-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5021
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-core-sync:3010
ports:
- "5021:5021"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5021/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 35s
inventar-web:
build:
context: .
dockerfile: apps/inventar/apps/web/Dockerfile
image: inventar-web:local
container_name: mana-app-inventar-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5025
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
ports:
- "5025:5025"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5025/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 20s
calc-web:
build:
context: .
dockerfile: apps/calc/apps/web/Dockerfile
image: calc-web:local
container_name: mana-app-calc-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5026
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5026:5026"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5026/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 20s
arcade-web:
build:
context: .
dockerfile: games/arcade/apps/web/Dockerfile
image: arcade-web:local
container_name: mana-app-arcade-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5210
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5210:5210"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5210/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 20s
taktik-web:
build:
context: .
dockerfile: apps/taktik/apps/web/Dockerfile
image: taktik-web:local
container_name: mana-app-taktik-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5027
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5027:5027"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5027/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 20s
manavoxel-web:
build:
context: .
dockerfile: apps/manavoxel/apps/web/Dockerfile
image: manavoxel-web:local
container_name: mana-app-manavoxel-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5028
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5028:5028"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5028/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
uload-server:
build:
context: apps/uload/apps/server
dockerfile: Dockerfile
image: uload-server:local
container_name: mana-app-uload-server
restart: always
mem_limit: 256m
depends_on:
postgres:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 3041
DATABASE_URL: postgresql://manacore:${POSTGRES_PASSWORD:-devpassword}@postgres:5432/mana_sync
MANA_CORE_AUTH_URL: http://mana-auth:3001
CORS_ORIGINS: http://uload-web:5029,https://uload.mana.how,https://ulo.ad
ports:
- "3041:3041"
healthcheck:
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3041/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
uload-web:
build:
context: .
dockerfile: apps/uload/apps/web/Dockerfile
image: uload-web:local
container_name: mana-app-uload-web
restart: always
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5029
PUBLIC_ULOAD_SERVER_URL: http://uload-server:3041
PUBLIC_ULOAD_SERVER_URL_CLIENT: https://uload-api.mana.how
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_SYNC_SERVER_URL: ws://mana-sync:3010
ports:
- "5029:5029"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5029/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
mana-llm:
build:
context: ./services/mana-llm
dockerfile: Dockerfile
container_name: mana-service-llm
restart: unless-stopped
mem_limit: 256m
depends_on:
redis:
condition: service_healthy
environment:
PORT: 3025
LOG_LEVEL: info
OLLAMA_URL: ${OLLAMA_URL:-http://192.168.178.11:11434}
OLLAMA_DEFAULT_MODEL: ${OLLAMA_MODEL:-gemma3:12b}
OLLAMA_TIMEOUT: 120
REDIS_URL: redis://redis:6379
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
GROQ_API_KEY: ${GROQ_API_KEY:-}
TOGETHER_API_KEY: ${TOGETHER_API_KEY:-}
GOOGLE_API_KEY: ${GOOGLE_API_KEY:-}
GOOGLE_DEFAULT_MODEL: gemini-2.0-flash
AUTO_FALLBACK_ENABLED: "true"
OLLAMA_MAX_CONCURRENT: 5
CORS_ORIGINS: https://playground.mana.how,https://mana.how,https://chat.mana.how
ports:
- "3025:3025"
healthcheck:
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:3025/health').raise_for_status()"]
interval: 120s
timeout: 10s
retries: 3
start_period: 30s
llm-playground:
build:
context: .
dockerfile: apps/playground/apps/web/Dockerfile
container_name: mana-app-llm-playground
restart: unless-stopped
mem_limit: 128m
depends_on:
mana-auth:
condition: service_healthy
mana-llm:
condition: service_healthy
environment:
NODE_ENV: production
PORT: 5050
PUBLIC_MANA_CORE_AUTH_URL: http://mana-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.mana.how
PUBLIC_MANA_LLM_URL: http://mana-llm:3025
PUBLIC_MANA_LLM_URL_CLIENT: https://llm.mana.how
ports:
- "5050:5050"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5050/health"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
labels:
- "com.centurylinklabs.watchtower.enable=true"
# ============================================
# Tier 7: Monitoring Dashboards (Ports 8000-8099)
# ============================================
grafana:
image: grafana/grafana:10.4.1
container_name: mana-mon-grafana
restart: always
mem_limit: 192m
depends_on:
victoriametrics:
condition: service_healthy
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
GF_USERS_ALLOW_SIGN_UP: false
GF_AUTH_ANONYMOUS_ENABLED: true
GF_AUTH_ANONYMOUS_ORG_ROLE: Viewer
GF_SERVER_ROOT_URL: https://grafana.mana.how
GF_SERVER_HTTP_PORT: 8000
GF_INSTALL_PLUGINS: yesoreyeram-infinity-datasource
GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH: /var/lib/grafana/dashboards/master-overview.json
volumes:
- ./docker/grafana/provisioning:/etc/grafana/provisioning:ro
- ./docker/grafana/dashboards:/var/lib/grafana/dashboards:ro
- grafana_data:/var/lib/grafana
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8000/api/health"]
interval: 300s
timeout: 10s
retries: 3
start_period: 10s
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: mana-mon-umami
restart: always
mem_limit: 256m
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/umami
DATABASE_TYPE: postgresql
APP_SECRET: ${UMAMI_APP_SECRET:-change-me-umami-secret}
DISABLE_TELEMETRY: 1
ports:
- "8010:3000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/heartbeat"]
interval: 300s
timeout: 10s
retries: 3
start_period: 30s
# ============================================
# Tier 8: Metrics & Exporters (Ports 9000-9199)
# ============================================
victoriametrics:
image: victoriametrics/victoria-metrics:v1.99.0
container_name: mana-mon-victoria
restart: always
mem_limit: 256m
entrypoint: >
sh -c "mkdir -p /etc/prometheus &&
cp /mnt/prometheus-config/*.yml /etc/prometheus/ 2>/dev/null;
exec /victoria-metrics-prod
-storageDataPath=/storage
-retentionPeriod=2y
-httpListenAddr=:9090
-promscrape.config=/etc/prometheus/prometheus.yml
-promscrape.config.strictParse=false
-selfScrapeInterval=15s
-search.latencyOffset=0s"
volumes:
- ./docker/prometheus:/mnt/prometheus-config:ro
- victoriametrics_data:/storage
ports:
- "9090:9090"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9090/health"]
interval: 300s
timeout: 10s
retries: 3
start_period: 10s
loki:
image: grafana/loki:3.0.0
container_name: mana-mon-loki
restart: always
mem_limit: 192m
entrypoint: >
sh -c "mkdir -p /etc/loki &&
cp /mnt/loki-config/*.yaml /etc/loki/ 2>/dev/null;
exec /usr/bin/loki -config.file=/etc/loki/local-config.yaml"
volumes:
- ./docker/loki:/mnt/loki-config:ro
- loki_data:/loki
ports:
- "3100:3100"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3100/ready"]
interval: 300s
timeout: 10s
retries: 3
start_period: 15s
promtail:
image: grafana/promtail:3.0.0
container_name: mana-mon-promtail
restart: always
mem_limit: 96m
command: -config.file=/etc/promtail/config.yaml -config.expand-env=true
volumes:
- ./docker/promtail:/etc/promtail:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
loki:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9080/ready"]
interval: 300s
timeout: 10s
retries: 3
start_period: 10s
pushgateway:
image: prom/pushgateway:v1.7.0
container_name: mana-mon-pushgateway
restart: always
mem_limit: 48m
ports:
- "9091:9091"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9091/-/healthy"]
interval: 300s
timeout: 10s
retries: 3
start_period: 20s
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.49.1
container_name: mana-mon-cadvisor
restart: always
mem_limit: 128m
privileged: true
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
ports:
- "9110:8080"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/healthz"]
interval: 300s
timeout: 10s
retries: 3
start_period: 20s
postgres-exporter:
image: prometheuscommunity/postgres-exporter:v0.15.0
container_name: mana-mon-postgres-exporter
restart: always
mem_limit: 48m
depends_on:
postgres:
condition: service_healthy
environment:
DATA_SOURCE_NAME: postgresql://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/postgres?sslmode=disable
ports:
- "9187:9187"
redis-exporter:
image: oliver006/redis_exporter:v1.58.0
container_name: mana-mon-redis-exporter
restart: always
mem_limit: 32m
depends_on:
redis:
condition: service_healthy
environment:
REDIS_ADDR: redis://redis:6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis123}
ports:
- "9121:9121"
node-exporter:
image: prom/node-exporter:v1.7.0
container_name: mana-mon-node-exporter
restart: always
mem_limit: 32m
# macOS Docker runs in a Linux VM, so we can only monitor the VM's metrics
# For full host metrics on macOS, install node_exporter natively
command:
- '--collector.disable-defaults'
- '--collector.cpu'
- '--collector.meminfo'
- '--collector.loadavg'
- '--collector.filesystem'
- '--collector.netdev'
- '--collector.time'
- '--collector.uname'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
ports:
- "9100:9100"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9100/metrics"]
interval: 300s
timeout: 10s
retries: 3
start_period: 20s
# ============================================
# Alerting Stack (Ports 9093-9095)
# ============================================
vmalert:
image: victoriametrics/vmalert:v1.99.0
container_name: mana-mon-vmalert
restart: always
mem_limit: 64m
depends_on:
victoriametrics:
condition: service_healthy
alertmanager:
condition: service_healthy
entrypoint: >
sh -c "mkdir -p /etc/alerts &&
cp /mnt/alerts-config/*.yml /etc/alerts/ 2>/dev/null;
exec /vmalert-prod
-datasource.url=http://victoriametrics:9090
-notifier.url=http://alertmanager:9093
-remoteWrite.url=http://victoriametrics:9090
-remoteRead.url=http://victoriametrics:9090
-rule='/etc/alerts/*.yml'
-evaluationInterval=30s
-httpListenAddr=:8880"
volumes:
- ./docker/prometheus:/mnt/alerts-config:ro
ports:
- "8880:8880"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8880/health"]
interval: 300s
timeout: 10s
retries: 3
start_period: 25s
alertmanager:
image: prom/alertmanager:v0.27.0
container_name: mana-mon-alertmanager
restart: always
mem_limit: 64m
depends_on:
alert-notifier:
condition: service_healthy
entrypoint: >
sh -c "mkdir -p /tmp/am-config &&
cp /mnt/alertmanager-config/*.yml /tmp/am-config/ 2>/dev/null;
exec /bin/alertmanager
--config.file=/tmp/am-config/alertmanager.yml
--storage.path=/alertmanager
--web.listen-address=:9093"
volumes:
- ./docker/alertmanager:/mnt/alertmanager-config:ro
- alertmanager_data:/alertmanager
ports:
- "9093:9093"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9093/-/healthy"]
interval: 300s
timeout: 10s
retries: 3
start_period: 25s
alert-notifier:
build:
context: ./docker/alert-notifier
dockerfile: Dockerfile
image: alert-notifier:local
container_name: mana-mon-alert-notifier
restart: always
mem_limit: 32m
environment:
PORT: 8080
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}
NTFY_TOPIC: ${NTFY_TOPIC:-}
ports:
- "9095:8080"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/health"]
interval: 300s
timeout: 5s
retries: 3
start_period: 25s
# ============================================
# Auto-Update (Watchtower)
# ============================================
watchtower:
image: nickfedor/watchtower:latest
container_name: mana-auto-watchtower
restart: always
mem_limit: 64m
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
TZ: Europe/Berlin
DOCKER_API_VERSION: 1.45
WATCHTOWER_POLL_INTERVAL: 300
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_INCLUDE_STOPPED: "false"
# Notifications disabled - configure Matrix if needed
# ============================================
# Volumes (Naming: mana-{service}-data)
# ============================================
# ============================================
# GlitchTip Error Tracking (Sentry-compatible)
# ============================================
glitchtip:
image: glitchtip/glitchtip:latest
container_name: mana-mon-glitchtip
restart: always
mem_limit: 256m
environment:
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/glitchtip
REDIS_URL: redis://:${REDIS_PASSWORD:-redis123}@redis:6379/1
SECRET_KEY: ${GLITCHTIP_SECRET_KEY:-change-me-in-production}
PORT: "8020"
GLITCHTIP_DOMAIN: https://glitchtip.mana.how
DEFAULT_FROM_EMAIL: glitchtip@mana.how
CELERY_WORKER_AUTOSCALE: "1,3"
ENABLE_USER_REGISTRATION: "true"
ports:
- "8020:8020"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8020/_health/')"]
interval: 300s
timeout: 10s
retries: 3
start_period: 30s
glitchtip-worker:
image: glitchtip/glitchtip:latest
container_name: mana-mon-glitchtip-worker
restart: always
mem_limit: 192m
command: ./bin/run-celery-with-beat.sh
environment:
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-mana123}@postgres:5432/glitchtip
REDIS_URL: redis://:${REDIS_PASSWORD:-redis123}@redis:6379/1
SECRET_KEY: ${GLITCHTIP_SECRET_KEY:-change-me-in-production}
GLITCHTIP_DOMAIN: https://glitchtip.mana.how
CELERY_WORKER_AUTOSCALE: "1,3"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# ============================================
# Games
# ============================================
whopixels:
build:
context: .
dockerfile: games/whopixels/Dockerfile
container_name: mana-game-whopixels
restart: unless-stopped
mem_limit: 128m
environment:
PORT: 5100
AZURE_OPENAI_API_KEY: ${AZURE_OPENAI_API_KEY:-}
AZURE_OPENAI_ENDPOINT: ${AZURE_OPENAI_ENDPOINT:-}
AZURE_OPENAI_DEPLOYMENT: ${AZURE_OPENAI_DEPLOYMENT:-}
AZURE_OPENAI_API_VERSION: ${AZURE_OPENAI_API_VERSION:-}
ports:
- "5100:5100"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5100/"]
interval: 180s
timeout: 10s
retries: 3
start_period: 45s
volumes:
redis_data:
name: mana-redis-data
victoriametrics_data:
name: mana-victoria-data
alertmanager_data:
name: mana-alertmanager-data
grafana_data:
name: mana-grafana-data
analytics_data:
name: mana-analytics-data
loki_data:
name: mana-loki-data
matrix_bots_data:
name: mana-matrix-bots-data