Merge develop into main — branch consolidation #432
84
.env.example
84
.env.example
@@ -19,7 +19,7 @@ NEXT_PUBLIC_API_URL=http://localhost:3001
|
||||
# ======================
|
||||
# PostgreSQL Database
|
||||
# ======================
|
||||
# Bundled PostgreSQL (when database profile enabled)
|
||||
# Bundled PostgreSQL
|
||||
# SECURITY: Change POSTGRES_PASSWORD to a strong random password in production
|
||||
DATABASE_URL=postgresql://mosaic:REPLACE_WITH_SECURE_PASSWORD@postgres:5432/mosaic
|
||||
POSTGRES_USER=mosaic
|
||||
@@ -28,7 +28,7 @@ POSTGRES_DB=mosaic
|
||||
POSTGRES_PORT=5432
|
||||
|
||||
# External PostgreSQL (managed service)
|
||||
# Disable 'database' profile and point DATABASE_URL to your external instance
|
||||
# To use an external instance, update DATABASE_URL above
|
||||
# Example: DATABASE_URL=postgresql://user:pass@rds.amazonaws.com:5432/mosaic
|
||||
|
||||
# PostgreSQL Performance Tuning (Optional)
|
||||
@@ -39,7 +39,7 @@ POSTGRES_MAX_CONNECTIONS=100
|
||||
# ======================
|
||||
# Valkey Cache (Redis-compatible)
|
||||
# ======================
|
||||
# Bundled Valkey (when cache profile enabled)
|
||||
# Bundled Valkey
|
||||
VALKEY_URL=redis://valkey:6379
|
||||
VALKEY_HOST=valkey
|
||||
VALKEY_PORT=6379
|
||||
@@ -47,7 +47,7 @@ VALKEY_PORT=6379
|
||||
VALKEY_MAXMEMORY=256mb
|
||||
|
||||
# External Redis/Valkey (managed service)
|
||||
# Disable 'cache' profile and point VALKEY_URL to your external instance
|
||||
# To use an external instance, update VALKEY_URL above
|
||||
# Example: VALKEY_URL=redis://elasticache.amazonaws.com:6379
|
||||
# Example with auth: VALKEY_URL=redis://:password@redis.example.com:6379
|
||||
|
||||
@@ -244,12 +244,16 @@ MOSAIC_API_DOMAIN=api.mosaic.local
|
||||
MOSAIC_WEB_DOMAIN=mosaic.local
|
||||
MOSAIC_AUTH_DOMAIN=auth.mosaic.local
|
||||
|
||||
# External Traefik network name (for upstream mode)
|
||||
# External Traefik network name (for upstream mode and swarm)
|
||||
# Must match the network name of your existing Traefik instance
|
||||
TRAEFIK_NETWORK=traefik-public
|
||||
TRAEFIK_DOCKER_NETWORK=traefik-public
|
||||
|
||||
# TLS/SSL Configuration
|
||||
TRAEFIK_TLS_ENABLED=true
|
||||
TRAEFIK_ENTRYPOINT=websecure
|
||||
# Cert resolver name (leave empty if TLS is handled externally or using self-signed certs)
|
||||
TRAEFIK_CERTRESOLVER=
|
||||
# For Let's Encrypt (production):
|
||||
TRAEFIK_ACME_EMAIL=admin@example.com
|
||||
# For self-signed certificates (development), leave TRAEFIK_ACME_EMAIL empty
|
||||
@@ -285,6 +289,15 @@ GITEA_WEBHOOK_SECRET=REPLACE_WITH_RANDOM_WEBHOOK_SECRET
|
||||
# The coordinator service uses this key to authenticate with the API
|
||||
COORDINATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
|
||||
|
||||
# Anthropic API Key (used by coordinator for issue parsing)
|
||||
# Get your API key from: https://console.anthropic.com/
|
||||
ANTHROPIC_API_KEY=REPLACE_WITH_ANTHROPIC_API_KEY
|
||||
|
||||
# Coordinator tuning
|
||||
COORDINATOR_POLL_INTERVAL=5.0
|
||||
COORDINATOR_MAX_CONCURRENT_AGENTS=10
|
||||
COORDINATOR_ENABLED=true
|
||||
|
||||
# ======================
|
||||
# Rate Limiting
|
||||
# ======================
|
||||
@@ -329,16 +342,34 @@ RATE_LIMIT_STORAGE=redis
|
||||
# ======================
|
||||
# Matrix bot integration for chat-based control via Matrix protocol
|
||||
# Requires a Matrix account with an access token for the bot user
|
||||
# MATRIX_HOMESERVER_URL=https://matrix.example.com
|
||||
# MATRIX_ACCESS_TOKEN=
|
||||
# MATRIX_BOT_USER_ID=@mosaic-bot:example.com
|
||||
# MATRIX_CONTROL_ROOM_ID=!roomid:example.com
|
||||
# MATRIX_WORKSPACE_ID=your-workspace-uuid
|
||||
# Set these AFTER deploying Synapse and creating the bot account.
|
||||
#
|
||||
# SECURITY: MATRIX_WORKSPACE_ID must be a valid workspace UUID from your database.
|
||||
# All Matrix commands will execute within this workspace context for proper
|
||||
# multi-tenant isolation. Each Matrix bot instance should be configured for
|
||||
# a single workspace.
|
||||
MATRIX_HOMESERVER_URL=http://synapse:8008
|
||||
MATRIX_ACCESS_TOKEN=
|
||||
MATRIX_BOT_USER_ID=@mosaic-bot:matrix.example.com
|
||||
MATRIX_SERVER_NAME=matrix.example.com
|
||||
# MATRIX_CONTROL_ROOM_ID=!roomid:matrix.example.com
|
||||
# MATRIX_WORKSPACE_ID=your-workspace-uuid
|
||||
|
||||
# ======================
|
||||
# Matrix / Synapse Deployment
|
||||
# ======================
|
||||
# Domains for Traefik routing to Matrix services
|
||||
MATRIX_DOMAIN=matrix.example.com
|
||||
ELEMENT_DOMAIN=chat.example.com
|
||||
|
||||
# Synapse database (created automatically by synapse-db-init in the swarm compose)
|
||||
SYNAPSE_POSTGRES_DB=synapse
|
||||
SYNAPSE_POSTGRES_USER=synapse
|
||||
SYNAPSE_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_SYNAPSE_DB_PASSWORD
|
||||
|
||||
# Image tags for Matrix services
|
||||
SYNAPSE_IMAGE_TAG=latest
|
||||
ELEMENT_IMAGE_TAG=latest
|
||||
|
||||
# ======================
|
||||
# Orchestrator Configuration
|
||||
@@ -363,11 +394,11 @@ AI_PROVIDER=ollama
|
||||
# For remote Ollama: http://your-ollama-server:11434
|
||||
OLLAMA_MODEL=llama3.1:latest
|
||||
|
||||
# Claude API Configuration (when AI_PROVIDER=claude)
|
||||
# OPTIONAL: Only required if AI_PROVIDER=claude
|
||||
# Claude API Key
|
||||
# Required by the orchestrator service in swarm deployment.
|
||||
# Also used when AI_PROVIDER=claude for other services.
|
||||
# Get your API key from: https://console.anthropic.com/
|
||||
# Note: Claude Max subscription users should use AI_PROVIDER=ollama instead
|
||||
# CLAUDE_API_KEY=sk-ant-...
|
||||
CLAUDE_API_KEY=REPLACE_WITH_CLAUDE_API_KEY
|
||||
|
||||
# OpenAI API Configuration (when AI_PROVIDER=openai)
|
||||
# OPTIONAL: Only required if AI_PROVIDER=openai
|
||||
@@ -405,6 +436,9 @@ TTS_PREMIUM_URL=http://chatterbox-tts:8881/v1
|
||||
TTS_FALLBACK_ENABLED=false
|
||||
TTS_FALLBACK_URL=http://openedai-speech:8000/v1
|
||||
|
||||
# Whisper model for Speaches STT engine
|
||||
SPEACHES_WHISPER_MODEL=Systran/faster-whisper-large-v3-turbo
|
||||
|
||||
# Speech Service Limits
|
||||
# Maximum upload file size in bytes (default: 25MB)
|
||||
SPEECH_MAX_UPLOAD_SIZE=25000000
|
||||
@@ -439,28 +473,6 @@ MOSAIC_TELEMETRY_INSTANCE_ID=your-instance-uuid-here
|
||||
# Useful for development and debugging telemetry payloads
|
||||
MOSAIC_TELEMETRY_DRY_RUN=false
|
||||
|
||||
# ======================
|
||||
# Matrix Dev Environment (docker-compose.matrix.yml overlay)
|
||||
# ======================
|
||||
# These variables configure the local Matrix dev environment.
|
||||
# Only used when running: docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml up
|
||||
#
|
||||
# Synapse homeserver
|
||||
# SYNAPSE_CLIENT_PORT=8008
|
||||
# SYNAPSE_FEDERATION_PORT=8448
|
||||
# SYNAPSE_POSTGRES_DB=synapse
|
||||
# SYNAPSE_POSTGRES_USER=synapse
|
||||
# SYNAPSE_POSTGRES_PASSWORD=synapse_dev_password
|
||||
#
|
||||
# Element Web client
|
||||
# ELEMENT_PORT=8501
|
||||
#
|
||||
# Matrix bridge connection (set after running docker/matrix/scripts/setup-bot.sh)
|
||||
# MATRIX_HOMESERVER_URL=http://localhost:8008
|
||||
# MATRIX_ACCESS_TOKEN=<obtained from setup-bot.sh>
|
||||
# MATRIX_BOT_USER_ID=@mosaic-bot:localhost
|
||||
# MATRIX_SERVER_NAME=localhost
|
||||
|
||||
# ======================
|
||||
# Logging & Debugging
|
||||
# ======================
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack Production Environment
|
||||
# ==============================================
|
||||
# Copy to .env and configure for production deployment
|
||||
|
||||
# ======================
|
||||
# PostgreSQL Database
|
||||
# ======================
|
||||
# CRITICAL: Use a strong, unique password
|
||||
POSTGRES_USER=mosaic
|
||||
POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
|
||||
POSTGRES_DB=mosaic
|
||||
POSTGRES_SHARED_BUFFERS=256MB
|
||||
POSTGRES_EFFECTIVE_CACHE_SIZE=1GB
|
||||
POSTGRES_MAX_CONNECTIONS=100
|
||||
|
||||
# ======================
|
||||
# Valkey Cache
|
||||
# ======================
|
||||
VALKEY_MAXMEMORY=256mb
|
||||
|
||||
# ======================
|
||||
# API Configuration
|
||||
# ======================
|
||||
API_PORT=3001
|
||||
API_HOST=0.0.0.0
|
||||
|
||||
# ======================
|
||||
# Web Configuration
|
||||
# ======================
|
||||
WEB_PORT=3000
|
||||
NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev
|
||||
|
||||
# ======================
|
||||
# Authentication (Authentik OIDC)
|
||||
# ======================
|
||||
OIDC_ISSUER=https://auth.diversecanvas.com/application/o/mosaic-stack/
|
||||
OIDC_CLIENT_ID=your-client-id
|
||||
OIDC_CLIENT_SECRET=your-client-secret
|
||||
OIDC_REDIRECT_URI=https://api.mosaicstack.dev/auth/callback/authentik
|
||||
|
||||
# ======================
|
||||
# JWT Configuration
|
||||
# ======================
|
||||
# CRITICAL: Generate a random secret (openssl rand -base64 32)
|
||||
JWT_SECRET=REPLACE_WITH_RANDOM_SECRET
|
||||
JWT_EXPIRATION=24h
|
||||
|
||||
# ======================
|
||||
# Traefik Integration
|
||||
# ======================
|
||||
# Set to true if using external Traefik
|
||||
TRAEFIK_ENABLE=true
|
||||
TRAEFIK_ENTRYPOINT=websecure
|
||||
TRAEFIK_TLS_ENABLED=true
|
||||
TRAEFIK_DOCKER_NETWORK=traefik-public
|
||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||
|
||||
# Domain configuration
|
||||
MOSAIC_API_DOMAIN=api.mosaicstack.dev
|
||||
MOSAIC_WEB_DOMAIN=app.mosaicstack.dev
|
||||
|
||||
# ======================
|
||||
# Optional: Ollama
|
||||
# ======================
|
||||
# OLLAMA_ENDPOINT=http://ollama.diversecanvas.com:11434
|
||||
@@ -1,161 +0,0 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - Docker Swarm Configuration
|
||||
# ==============================================
|
||||
# Copy this file to .env for Docker Swarm deployment
|
||||
|
||||
# ======================
|
||||
# Application Ports (Internal)
|
||||
# ======================
|
||||
API_PORT=3001
|
||||
API_HOST=0.0.0.0
|
||||
WEB_PORT=3000
|
||||
|
||||
# ======================
|
||||
# Domain Configuration (Traefik)
|
||||
# ======================
|
||||
# These domains must be configured in your DNS or /etc/hosts
|
||||
MOSAIC_API_DOMAIN=api.mosaicstack.dev
|
||||
MOSAIC_WEB_DOMAIN=mosaic.mosaicstack.dev
|
||||
MOSAIC_AUTH_DOMAIN=auth.mosaicstack.dev
|
||||
|
||||
# ======================
|
||||
# Web Configuration
|
||||
# ======================
|
||||
# Use the Traefik domain for the API URL
|
||||
NEXT_PUBLIC_APP_URL=http://mosaic.mosaicstack.dev
|
||||
NEXT_PUBLIC_API_URL=http://api.mosaicstack.dev
|
||||
|
||||
# ======================
|
||||
# PostgreSQL Database
|
||||
# ======================
|
||||
DATABASE_URL=postgresql://mosaic:REPLACE_WITH_SECURE_PASSWORD@postgres:5432/mosaic
|
||||
POSTGRES_USER=mosaic
|
||||
POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
|
||||
POSTGRES_DB=mosaic
|
||||
POSTGRES_PORT=5432
|
||||
|
||||
# PostgreSQL Performance Tuning
|
||||
POSTGRES_SHARED_BUFFERS=256MB
|
||||
POSTGRES_EFFECTIVE_CACHE_SIZE=1GB
|
||||
POSTGRES_MAX_CONNECTIONS=100
|
||||
|
||||
# ======================
|
||||
# Valkey Cache
|
||||
# ======================
|
||||
VALKEY_URL=redis://valkey:6379
|
||||
VALKEY_HOST=valkey
|
||||
VALKEY_PORT=6379
|
||||
VALKEY_MAXMEMORY=256mb
|
||||
|
||||
# Knowledge Module Cache Configuration
|
||||
KNOWLEDGE_CACHE_ENABLED=true
|
||||
KNOWLEDGE_CACHE_TTL=300
|
||||
|
||||
# ======================
|
||||
# Authentication (Authentik OIDC)
|
||||
# ======================
|
||||
# NOTE: Authentik services are COMMENTED OUT in docker-compose.swarm.yml by default
|
||||
# Uncomment those services if you want to run Authentik internally
|
||||
# Otherwise, use external Authentik by configuring OIDC_* variables below
|
||||
|
||||
# External Authentik Configuration (default)
|
||||
OIDC_ENABLED=true
|
||||
OIDC_ISSUER=https://auth.example.com/application/o/mosaic-stack/
|
||||
OIDC_CLIENT_ID=your-client-id-here
|
||||
OIDC_CLIENT_SECRET=your-client-secret-here
|
||||
OIDC_REDIRECT_URI=https://api.mosaicstack.dev/auth/callback/authentik
|
||||
|
||||
# Internal Authentik Configuration (only needed if uncommenting Authentik services)
|
||||
# Authentik PostgreSQL Database
|
||||
AUTHENTIK_POSTGRES_USER=authentik
|
||||
AUTHENTIK_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
|
||||
AUTHENTIK_POSTGRES_DB=authentik
|
||||
|
||||
# Authentik Server Configuration
|
||||
AUTHENTIK_SECRET_KEY=REPLACE_WITH_RANDOM_SECRET_MINIMUM_50_CHARS
|
||||
AUTHENTIK_ERROR_REPORTING=false
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
|
||||
AUTHENTIK_BOOTSTRAP_EMAIL=admin@mosaicstack.dev
|
||||
AUTHENTIK_COOKIE_DOMAIN=.mosaicstack.dev
|
||||
|
||||
# ======================
|
||||
# JWT Configuration
|
||||
# ======================
|
||||
JWT_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
|
||||
JWT_EXPIRATION=24h
|
||||
|
||||
# ======================
|
||||
# Encryption (Credential Security)
|
||||
# ======================
|
||||
# Generate with: openssl rand -hex 32
|
||||
ENCRYPTION_KEY=REPLACE_WITH_64_CHAR_HEX_STRING_GENERATE_WITH_OPENSSL_RAND_HEX_32
|
||||
|
||||
# ======================
|
||||
# OpenBao Secrets Management
|
||||
# ======================
|
||||
OPENBAO_ADDR=http://openbao:8200
|
||||
OPENBAO_PORT=8200
|
||||
# For development only - remove in production
|
||||
OPENBAO_DEV_ROOT_TOKEN_ID=root
|
||||
|
||||
# ======================
|
||||
# Ollama (Optional AI Service)
|
||||
# ======================
|
||||
OLLAMA_ENDPOINT=http://ollama:11434
|
||||
OLLAMA_PORT=11434
|
||||
OLLAMA_EMBEDDING_MODEL=mxbai-embed-large
|
||||
|
||||
# Semantic Search Configuration
|
||||
SEMANTIC_SEARCH_SIMILARITY_THRESHOLD=0.5
|
||||
|
||||
# ======================
|
||||
# OpenAI API (Optional)
|
||||
# ======================
|
||||
# OPENAI_API_KEY=sk-...
|
||||
|
||||
# ======================
|
||||
# Application Environment
|
||||
# ======================
|
||||
NODE_ENV=production
|
||||
|
||||
# ======================
|
||||
# Gitea Integration (Coordinator)
|
||||
# ======================
|
||||
GITEA_URL=https://git.mosaicstack.dev
|
||||
GITEA_BOT_USERNAME=mosaic
|
||||
GITEA_BOT_TOKEN=REPLACE_WITH_COORDINATOR_BOT_API_TOKEN
|
||||
GITEA_BOT_PASSWORD=REPLACE_WITH_COORDINATOR_BOT_PASSWORD
|
||||
GITEA_REPO_OWNER=mosaic
|
||||
GITEA_REPO_NAME=stack
|
||||
GITEA_WEBHOOK_SECRET=REPLACE_WITH_RANDOM_WEBHOOK_SECRET
|
||||
COORDINATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
|
||||
|
||||
# ======================
|
||||
# Coordinator Service
|
||||
# ======================
|
||||
ANTHROPIC_API_KEY=REPLACE_WITH_ANTHROPIC_API_KEY
|
||||
COORDINATOR_POLL_INTERVAL=5.0
|
||||
COORDINATOR_MAX_CONCURRENT_AGENTS=10
|
||||
COORDINATOR_ENABLED=true
|
||||
|
||||
# ======================
|
||||
# Rate Limiting
|
||||
# ======================
|
||||
RATE_LIMIT_TTL=60
|
||||
RATE_LIMIT_GLOBAL_LIMIT=100
|
||||
RATE_LIMIT_WEBHOOK_LIMIT=60
|
||||
RATE_LIMIT_COORDINATOR_LIMIT=100
|
||||
RATE_LIMIT_HEALTH_LIMIT=300
|
||||
RATE_LIMIT_STORAGE=redis
|
||||
|
||||
# ======================
|
||||
# Orchestrator Configuration
|
||||
# ======================
|
||||
ORCHESTRATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
|
||||
CLAUDE_API_KEY=REPLACE_WITH_CLAUDE_API_KEY
|
||||
|
||||
# ======================
|
||||
# Logging & Debugging
|
||||
# ======================
|
||||
LOG_LEVEL=info
|
||||
DEBUG=false
|
||||
@@ -112,7 +112,7 @@ steps:
|
||||
ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
commands:
|
||||
- *use_deps
|
||||
- pnpm --filter "@mosaic/api" exec vitest run --exclude 'src/auth/auth-rls.integration.spec.ts' --exclude 'src/credentials/user-credential.model.spec.ts' --exclude 'src/job-events/job-events.performance.spec.ts' --exclude 'src/knowledge/services/fulltext-search.spec.ts'
|
||||
- pnpm --filter "@mosaic/api" exec vitest run --exclude 'src/auth/auth-rls.integration.spec.ts' --exclude 'src/credentials/user-credential.model.spec.ts' --exclude 'src/job-events/job-events.performance.spec.ts' --exclude 'src/knowledge/services/fulltext-search.spec.ts' --exclude 'src/mosaic-telemetry/mosaic-telemetry.module.spec.ts'
|
||||
depends_on:
|
||||
- prisma-migrate
|
||||
|
||||
@@ -154,7 +154,7 @@ steps:
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-api:dev"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile apps/api/Dockerfile $DESTINATIONS
|
||||
/kaniko/executor --context . --dockerfile apps/api/Dockerfile --snapshot-mode=redo $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
|
||||
@@ -95,7 +95,7 @@ steps:
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-coordinator:dev"
|
||||
fi
|
||||
/kaniko/executor --context apps/coordinator --dockerfile apps/coordinator/Dockerfile $DESTINATIONS
|
||||
/kaniko/executor --context apps/coordinator --dockerfile apps/coordinator/Dockerfile --snapshot-mode=redo $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
|
||||
@@ -39,7 +39,7 @@ steps:
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-postgres:dev"
|
||||
fi
|
||||
/kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile $DESTINATIONS
|
||||
/kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile --snapshot-mode=redo $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
@@ -64,7 +64,7 @@ steps:
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-openbao:dev"
|
||||
fi
|
||||
/kaniko/executor --context docker/openbao --dockerfile docker/openbao/Dockerfile $DESTINATIONS
|
||||
/kaniko/executor --context docker/openbao --dockerfile docker/openbao/Dockerfile --snapshot-mode=redo $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
|
||||
@@ -111,7 +111,7 @@ steps:
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-orchestrator:dev"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile apps/orchestrator/Dockerfile $DESTINATIONS
|
||||
/kaniko/executor --context . --dockerfile apps/orchestrator/Dockerfile --snapshot-mode=redo $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
|
||||
@@ -122,7 +122,7 @@ steps:
|
||||
elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-web:dev"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile apps/web/Dockerfile --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS
|
||||
/kaniko/executor --context . --dockerfile apps/web/Dockerfile --snapshot-mode=redo --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS
|
||||
when:
|
||||
- branch: [main, develop]
|
||||
event: [push, manual, tag]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Enable BuildKit features for cache mounts
|
||||
|
||||
# Base image for all stages
|
||||
# Uses Debian slim (glibc) instead of Alpine (musl) because native Node.js addons
|
||||
# (matrix-sdk-crypto-nodejs, Prisma engines) require glibc-compatible binaries.
|
||||
@@ -27,9 +24,8 @@ COPY packages/ui/package.json ./packages/ui/
|
||||
COPY packages/config/package.json ./packages/config/
|
||||
COPY apps/api/package.json ./apps/api/
|
||||
|
||||
# Install dependencies with pnpm store cache
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
# Install dependencies (no cache mount — Kaniko builds are ephemeral in CI)
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# ======================
|
||||
# Builder stage
|
||||
@@ -57,15 +53,14 @@ RUN pnpm turbo build --filter=@mosaic/api --force
|
||||
# ======================
|
||||
FROM node:24-slim AS production
|
||||
|
||||
# Remove npm (unused in production — we use pnpm) to reduce attack surface
|
||||
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
|
||||
# Install dumb-init for proper signal handling (static binary from GitHub,
|
||||
# avoids apt-get which fails under Kaniko with bookworm GPG signature errors)
|
||||
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 /usr/local/bin/dumb-init
|
||||
|
||||
# Install dumb-init for proper signal handling
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends dumb-init \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs
|
||||
# Single RUN to minimize Kaniko filesystem snapshots (each RUN = full snapshot)
|
||||
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \
|
||||
&& chmod 755 /usr/local/bin/dumb-init \
|
||||
&& groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
import { genericOAuth } from "better-auth/plugins";
|
||||
@@ -216,6 +217,7 @@ export function createAuth(prisma: PrismaClient) {
|
||||
updateAge: 60 * 60 * 2, // 2 hours — minimum session age before BetterAuth refreshes the expiry on next request
|
||||
},
|
||||
advanced: {
|
||||
generateId: () => randomUUID(),
|
||||
defaultCookieAttributes: {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
|
||||
@@ -49,8 +49,10 @@ async function bootstrap() {
|
||||
|
||||
// Configure CORS for cookie-based authentication
|
||||
// Origin list is shared with BetterAuth trustedOrigins via getTrustedOrigins()
|
||||
const trustedOrigins = getTrustedOrigins();
|
||||
console.log(`[CORS] Trusted origins: ${JSON.stringify(trustedOrigins)}`);
|
||||
app.enableCors({
|
||||
origin: getTrustedOrigins(),
|
||||
origin: trustedOrigins,
|
||||
credentials: true, // Required for cookie-based authentication
|
||||
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||
allowedHeaders: ["Content-Type", "Authorization", "Cookie", "X-CSRF-Token", "X-Workspace-Id"],
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
# Multi-stage build for mosaic-coordinator
|
||||
FROM python:3.11-slim AS builder
|
||||
# Builder uses the full Python image which already includes gcc/g++/make,
|
||||
# avoiding a 336 MB build-essential install that exceeds Kaniko disk budget.
|
||||
FROM python:3.11 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy dependency files and private registry config
|
||||
COPY pyproject.toml .
|
||||
COPY pip.conf /etc/pip.conf
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Enable BuildKit features for cache mounts
|
||||
|
||||
# Base image for all stages
|
||||
# Uses Debian slim (glibc) instead of Alpine (musl) for native addon compatibility.
|
||||
FROM node:24-slim AS base
|
||||
@@ -26,9 +23,8 @@ COPY packages/config/package.json ./packages/config/
|
||||
COPY apps/orchestrator/package.json ./apps/orchestrator/
|
||||
|
||||
# Install ALL dependencies (not just production)
|
||||
# This ensures NestJS packages and other required deps are available
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
# No cache mount — Kaniko builds are ephemeral in CI
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# ======================
|
||||
# Builder stage
|
||||
@@ -69,15 +65,14 @@ LABEL org.opencontainers.image.vendor="Mosaic Stack"
|
||||
LABEL org.opencontainers.image.title="Mosaic Orchestrator"
|
||||
LABEL org.opencontainers.image.description="Agent orchestration service for Mosaic Stack"
|
||||
|
||||
# Remove npm (unused in production — we use pnpm) to reduce attack surface
|
||||
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
|
||||
# Install dumb-init for proper signal handling (static binary from GitHub,
|
||||
# avoids apt-get which fails under Kaniko with bookworm GPG signature errors)
|
||||
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 /usr/local/bin/dumb-init
|
||||
|
||||
# Install wget and dumb-init
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends wget dumb-init \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs
|
||||
# Single RUN to minimize Kaniko filesystem snapshots (each RUN = full snapshot)
|
||||
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \
|
||||
&& chmod 755 /usr/local/bin/dumb-init \
|
||||
&& groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -105,7 +100,7 @@ EXPOSE 3001
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1
|
||||
CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||
|
||||
# Use dumb-init to handle signals properly
|
||||
ENTRYPOINT ["dumb-init", "--"]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Enable BuildKit features for cache mounts
|
||||
|
||||
# Base image for all stages
|
||||
# Uses Debian slim (glibc) for consistency with API/orchestrator and to prevent
|
||||
# future native addon compatibility issues with Alpine's musl libc.
|
||||
@@ -27,9 +24,8 @@ COPY packages/ui/package.json ./packages/ui/
|
||||
COPY packages/config/package.json ./packages/config/
|
||||
COPY apps/web/package.json ./apps/web/
|
||||
|
||||
# Install dependencies with pnpm store cache
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
# Install dependencies (no cache mount — Kaniko builds are ephemeral in CI)
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# ======================
|
||||
# Builder stage
|
||||
@@ -79,18 +75,15 @@ RUN mkdir -p ./apps/web/public
|
||||
# ======================
|
||||
FROM node:24-slim AS production
|
||||
|
||||
# Remove npm (unused in production — we use pnpm) to reduce attack surface
|
||||
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
|
||||
# Install dumb-init for proper signal handling (static binary from GitHub,
|
||||
# avoids apt-get which fails under Kaniko with bookworm GPG signature errors)
|
||||
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 /usr/local/bin/dumb-init
|
||||
|
||||
# Install pnpm (needed for pnpm start command)
|
||||
RUN corepack enable && corepack prepare pnpm@10.27.0 --activate
|
||||
|
||||
# Install dumb-init for proper signal handling
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends dumb-init \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nextjs
|
||||
# Single RUN to minimize Kaniko filesystem snapshots (each RUN = full snapshot)
|
||||
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \
|
||||
&& corepack enable && corepack prepare pnpm@10.27.0 --activate \
|
||||
&& chmod 755 /usr/local/bin/dumb-init \
|
||||
&& groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nextjs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
# ==============================================
|
||||
# OpenBao Standalone Deployment - Portainer Version
|
||||
# ==============================================
|
||||
#
|
||||
# This file is optimized for Portainer deployment:
|
||||
# - No env_file directive (define variables in Portainer's environment editor)
|
||||
# - Port exposed on all interfaces (Portainer limitation)
|
||||
# - All environment variables explicitly defined
|
||||
#
|
||||
# Usage in Portainer:
|
||||
# 1. Stacks -> Add Stack
|
||||
# 2. Name: mosaic-openbao
|
||||
# 3. Paste this file content
|
||||
# 4. Add environment variables in "Environment variables" section:
|
||||
# - IMAGE_TAG=dev
|
||||
# - OPENBAO_PORT=8200
|
||||
# 5. Deploy
|
||||
#
|
||||
# SECURITY NOTE: Port 8200 will be exposed on 0.0.0.0 (all interfaces)
|
||||
# Use firewall rules to restrict access if needed.
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# ======================
|
||||
# OpenBao Secrets Vault
|
||||
# ======================
|
||||
openbao:
|
||||
image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-dev}
|
||||
container_name: mosaic-openbao
|
||||
entrypoint: ["dumb-init", "--"]
|
||||
command: ["bao", "server", "-config=/openbao/config/config.hcl"]
|
||||
environment:
|
||||
OPENBAO_ADDR: http://0.0.0.0:8200
|
||||
ports:
|
||||
- "${OPENBAO_PORT:-8200}:8200"
|
||||
volumes:
|
||||
- openbao_data:/openbao/data
|
||||
- openbao_logs:/openbao/logs
|
||||
- openbao_init:/openbao/init
|
||||
cap_add:
|
||||
- IPC_LOCK
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"wget --spider --quiet 'http://localhost:8200/v1/sys/health?standbyok=true&uninitcode=200&sealedcode=200'",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mosaic_internal
|
||||
|
||||
# ======================
|
||||
# OpenBao Init Sidecar
|
||||
# ======================
|
||||
# Auto-initializes and unseals OpenBao on first run
|
||||
openbao-init:
|
||||
image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-dev}
|
||||
container_name: mosaic-openbao-init
|
||||
command: /openbao/init.sh
|
||||
environment:
|
||||
OPENBAO_ADDR: http://openbao:8200
|
||||
volumes:
|
||||
- openbao_init:/openbao/init
|
||||
depends_on:
|
||||
- openbao
|
||||
restart: "no"
|
||||
networks:
|
||||
- mosaic_internal
|
||||
|
||||
# ======================
|
||||
# Volumes
|
||||
# ======================
|
||||
volumes:
|
||||
openbao_data:
|
||||
name: mosaic-openbao-data
|
||||
driver: local
|
||||
openbao_logs:
|
||||
name: mosaic-openbao-logs
|
||||
driver: local
|
||||
openbao_init:
|
||||
name: mosaic-openbao-init
|
||||
driver: local
|
||||
|
||||
# ======================
|
||||
# Networks
|
||||
# ======================
|
||||
# Connect to the swarm stack's internal network
|
||||
networks:
|
||||
mosaic_internal:
|
||||
external: true
|
||||
name: mosaic_internal
|
||||
@@ -1,180 +0,0 @@
|
||||
# Production Docker Compose - Uses pre-built images from Gitea Packages
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Images built and pushed to git.mosaicstack.dev/mosaic/*
|
||||
# - .env file configured with production values
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.prod.yml up -d
|
||||
#
|
||||
# For Portainer:
|
||||
# - Stack → Add Stack → Repository
|
||||
# - Compose file: docker-compose.prod.yml
|
||||
|
||||
services:
|
||||
# ======================
|
||||
# PostgreSQL Database
|
||||
# ======================
|
||||
postgres:
|
||||
image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest}
|
||||
container_name: mosaic-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-mosaic}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-mosaic}
|
||||
POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB}
|
||||
POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
|
||||
POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
- "com.mosaic.service=database"
|
||||
- "com.mosaic.description=PostgreSQL 17 with pgvector"
|
||||
|
||||
# ======================
|
||||
# Valkey Cache
|
||||
# ======================
|
||||
valkey:
|
||||
image: valkey/valkey:8-alpine
|
||||
container_name: mosaic-valkey
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- valkey-server
|
||||
- --maxmemory ${VALKEY_MAXMEMORY:-256mb}
|
||||
- --maxmemory-policy noeviction
|
||||
- --appendonly yes
|
||||
volumes:
|
||||
- valkey_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "valkey-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
- "com.mosaic.service=cache"
|
||||
- "com.mosaic.description=Valkey Redis-compatible cache"
|
||||
|
||||
# ======================
|
||||
# Mosaic API
|
||||
# ======================
|
||||
api:
|
||||
image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest}
|
||||
container_name: mosaic-api
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${API_PORT:-3001}
|
||||
API_HOST: ${API_HOST:-0.0.0.0}
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-mosaic}
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
||||
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
|
||||
ports:
|
||||
- "${API_PORT:-3001}:${API_PORT:-3001}"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
valkey:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
- mosaic-public
|
||||
labels:
|
||||
- "com.mosaic.service=api"
|
||||
- "com.mosaic.description=Mosaic NestJS API"
|
||||
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
|
||||
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)"
|
||||
- "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
|
||||
- "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
|
||||
# ======================
|
||||
# Mosaic Web
|
||||
# ======================
|
||||
web:
|
||||
image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest}
|
||||
container_name: mosaic-web
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${WEB_PORT:-3000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.mosaicstack.dev}
|
||||
ports:
|
||||
- "${WEB_PORT:-3000}:${WEB_PORT:-3000}"
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- mosaic-public
|
||||
labels:
|
||||
- "com.mosaic.service=web"
|
||||
- "com.mosaic.description=Mosaic Next.js Web App"
|
||||
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
|
||||
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-app.mosaicstack.dev}`)"
|
||||
- "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
|
||||
- "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
|
||||
# ======================
|
||||
# Volumes
|
||||
# ======================
|
||||
volumes:
|
||||
postgres_data:
|
||||
name: mosaic-postgres-data
|
||||
driver: local
|
||||
valkey_data:
|
||||
name: mosaic-valkey-data
|
||||
driver: local
|
||||
|
||||
# ======================
|
||||
# Networks
|
||||
# ======================
|
||||
networks:
|
||||
mosaic-internal:
|
||||
name: mosaic-internal
|
||||
driver: bridge
|
||||
mosaic-public:
|
||||
name: mosaic-public
|
||||
driver: bridge
|
||||
@@ -1,113 +0,0 @@
|
||||
# ==============================================
|
||||
# Speech Services - Docker Compose Dev Overlay
|
||||
# ==============================================
|
||||
#
|
||||
# Adds STT and TTS services for local development.
|
||||
#
|
||||
# Usage:
|
||||
# Basic (STT + default TTS):
|
||||
# docker compose -f docker-compose.yml -f docker-compose.speech.yml up -d
|
||||
#
|
||||
# With premium TTS (requires GPU):
|
||||
# docker compose -f docker-compose.yml -f docker-compose.speech.yml --profile premium-tts up -d
|
||||
#
|
||||
# Or use Makefile targets:
|
||||
# make speech-up # Basic speech services
|
||||
# make speech-down # Stop speech services
|
||||
# make speech-logs # View speech service logs
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# ======================
|
||||
# Speaches (STT + basic TTS)
|
||||
# ======================
|
||||
speaches:
|
||||
image: ghcr.io/speaches-ai/speaches:latest
|
||||
container_name: mosaic-speaches
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
WHISPER__MODEL: ${SPEACHES_WHISPER_MODEL:-Systran/faster-whisper-large-v3-turbo}
|
||||
ports:
|
||||
- "${SPEACHES_PORT:-8090}:8000"
|
||||
volumes:
|
||||
- speaches_models:/root/.cache/huggingface
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 120s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
- "com.mosaic.service=speech-stt"
|
||||
- "com.mosaic.description=Speaches STT (Whisper) and basic TTS"
|
||||
|
||||
# ======================
|
||||
# Kokoro TTS (Default TTS)
|
||||
# ======================
|
||||
kokoro-tts:
|
||||
image: ghcr.io/remsky/kokoro-fastapi:latest-cpu
|
||||
container_name: mosaic-kokoro-tts
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${KOKORO_TTS_PORT:-8880}:8880"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8880/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 120s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
- "com.mosaic.service=speech-tts"
|
||||
- "com.mosaic.description=Kokoro FastAPI TTS engine"
|
||||
|
||||
# ======================
|
||||
# Chatterbox TTS (Premium TTS - Optional)
|
||||
# ======================
|
||||
# Only starts with: --profile premium-tts
|
||||
# Requires NVIDIA GPU with docker nvidia runtime
|
||||
chatterbox-tts:
|
||||
image: devnen/chatterbox-tts-server:latest
|
||||
container_name: mosaic-chatterbox-tts
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${CHATTERBOX_TTS_PORT:-8881}:8000"
|
||||
profiles:
|
||||
- premium-tts
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 180s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
- "com.mosaic.service=speech-tts-premium"
|
||||
- "com.mosaic.description=Chatterbox premium TTS with voice cloning (GPU)"
|
||||
|
||||
# ======================
|
||||
# Volumes
|
||||
# ======================
|
||||
volumes:
|
||||
speaches_models:
|
||||
name: mosaic-speaches-models
|
||||
driver: local
|
||||
|
||||
# ======================
|
||||
# Networks
|
||||
# ======================
|
||||
networks:
|
||||
mosaic-internal:
|
||||
external: true
|
||||
name: mosaic-internal
|
||||
@@ -1,48 +1,63 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - Docker Swarm Deployment
|
||||
# Mosaic Stack — Docker Swarm / Portainer
|
||||
# ==============================================
|
||||
#
|
||||
# IMPORTANT: Docker Swarm does NOT support docker-compose profiles
|
||||
# To disable services (e.g., for external alternatives), manually comment them out
|
||||
# The canonical deployment file for Mosaic Stack on Docker Swarm.
|
||||
# Includes all services except OpenBao (standalone) and external services.
|
||||
#
|
||||
# Current Configuration:
|
||||
# - PostgreSQL: ENABLED (internal)
|
||||
# - Valkey: ENABLED (internal)
|
||||
# - Coordinator: ENABLED (internal)
|
||||
# - OpenBao: DISABLED (must use standalone - see docker-compose.openbao.yml)
|
||||
# - Authentik: DISABLED (commented out - using external OIDC)
|
||||
# - Ollama: DISABLED (commented out - using external Ollama)
|
||||
# External services (not in this file):
|
||||
# - OpenBao: Standalone container (see docker-compose.openbao.yml)
|
||||
# - Authentik: External OIDC provider
|
||||
# - Ollama: External AI inference
|
||||
#
|
||||
# For detailed deployment instructions, see:
|
||||
# docs/SWARM-DEPLOYMENT.md
|
||||
# Usage (Portainer):
|
||||
# 1. Stacks -> Add Stack -> Upload or paste
|
||||
# 2. Set environment variables (see .env.example for full reference)
|
||||
# 3. Deploy
|
||||
#
|
||||
# Quick Start:
|
||||
# 1. cp .env.swarm.example .env
|
||||
# 2. nano .env # Configure environment
|
||||
# 3. ./scripts/deploy-swarm.sh mosaic
|
||||
# 4. Initialize OpenBao manually (see docs/SWARM-DEPLOYMENT.md)
|
||||
# Usage (CLI):
|
||||
# docker stack deploy -c docker-compose.swarm.portainer.yml mosaic
|
||||
#
|
||||
# Host paths required for Matrix:
|
||||
# /opt/mosaic/synapse/homeserver.yaml
|
||||
# /opt/mosaic/synapse/element-config.json
|
||||
# /opt/mosaic/synapse/media_store/ (auto-populated)
|
||||
# /opt/mosaic/synapse/keys/ (auto-populated)
|
||||
#
|
||||
# ==============================================
|
||||
# ENVIRONMENT VARIABLE CONVENTION
|
||||
# ==============================================
|
||||
#
|
||||
# ${VAR} — REQUIRED. Must be set in Portainer env vars.
|
||||
# ${VAR:-default} — OPTIONAL. Falls back to a sensible default.
|
||||
# ${VAR:-} — OPTIONAL. Empty string is acceptable.
|
||||
#
|
||||
# NOTE: Portainer does not support ${VAR:?msg} syntax.
|
||||
# Required vars use plain ${VAR} — the app validates at startup.
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# ============================================
|
||||
# CORE INFRASTRUCTURE
|
||||
# ============================================
|
||||
|
||||
# ======================
|
||||
# PostgreSQL Database
|
||||
# ======================
|
||||
postgres:
|
||||
image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest}
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-mosaic}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-mosaic}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB}
|
||||
POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
|
||||
POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
# Note: init-scripts bind mount removed for Portainer compatibility
|
||||
# Init scripts are baked into the postgres image at build time
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"]
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -77,168 +92,113 @@ services:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# OpenBao Secrets Vault - COMMENTED OUT
|
||||
# ======================
|
||||
# IMPORTANT: OpenBao CANNOT run in swarm mode due to port binding conflicts.
|
||||
# Deploy OpenBao as a standalone container instead:
|
||||
# docker compose -f docker-compose.openbao.yml up -d
|
||||
#
|
||||
# Alternative: Use external HashiCorp Vault or managed secrets service
|
||||
#
|
||||
# openbao:
|
||||
# image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest}
|
||||
# environment:
|
||||
# OPENBAO_ADDR: ${OPENBAO_ADDR:-http://0.0.0.0:8200}
|
||||
# OPENBAO_DEV_ROOT_TOKEN_ID: ${OPENBAO_DEV_ROOT_TOKEN_ID:-root}
|
||||
# volumes:
|
||||
# - openbao_data:/openbao/data
|
||||
# - openbao_logs:/openbao/logs
|
||||
# - openbao_init:/openbao/init
|
||||
# cap_add:
|
||||
# - IPC_LOCK
|
||||
# healthcheck:
|
||||
# test:
|
||||
# ["CMD", "wget", "--spider", "--quiet", "http://localhost:8200/v1/sys/health?standbyok=true"]
|
||||
# interval: 10s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
# start_period: 30s
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
# ============================================
|
||||
# MOSAIC APPLICATION
|
||||
# ============================================
|
||||
|
||||
# ======================
|
||||
# Authentik - COMMENTED OUT (Using External Authentik)
|
||||
# Mosaic API
|
||||
# ======================
|
||||
# Uncomment these services if you want to run Authentik internally
|
||||
# For external Authentik, configure OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET in .env
|
||||
#
|
||||
# authentik-postgres:
|
||||
# image: postgres:17.7-alpine3.22
|
||||
# environment:
|
||||
# POSTGRES_USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
# POSTGRES_PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
# POSTGRES_DB: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
# volumes:
|
||||
# - authentik_postgres_data:/var/lib/postgresql/data
|
||||
# healthcheck:
|
||||
# test: ["CMD-SHELL", "pg_isready -U ${AUTHENTIK_POSTGRES_USER:-authentik}"]
|
||||
# interval: 10s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
# start_period: 20s
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
#
|
||||
# authentik-redis:
|
||||
# image: valkey/valkey:8-alpine
|
||||
# command: valkey-server --save 60 1 --loglevel warning
|
||||
# volumes:
|
||||
# - authentik_redis_data:/data
|
||||
# healthcheck:
|
||||
# test: ["CMD", "valkey-cli", "ping"]
|
||||
# interval: 10s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
# start_period: 10s
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
#
|
||||
# authentik-server:
|
||||
# image: ghcr.io/goauthentik/server:2024.12.1
|
||||
# command: server
|
||||
# environment:
|
||||
# AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
|
||||
# AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
|
||||
# AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||
# AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||
# AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
# AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
# AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
# AUTHENTIK_REDIS__HOST: authentik-redis
|
||||
# AUTHENTIK_REDIS__PORT: 6379
|
||||
# AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-admin}
|
||||
# AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL:-admin@localhost}
|
||||
# AUTHENTIK_COOKIE_DOMAIN: ${AUTHENTIK_COOKIE_DOMAIN:-.mosaicstack.dev}
|
||||
# volumes:
|
||||
# - authentik_media:/media
|
||||
# - authentik_templates:/templates
|
||||
# healthcheck:
|
||||
# test:
|
||||
# [
|
||||
# "CMD",
|
||||
# "wget",
|
||||
# "--no-verbose",
|
||||
# "--tries=1",
|
||||
# "--spider",
|
||||
# "http://localhost:9000/-/health/live/",
|
||||
# ]
|
||||
# interval: 30s
|
||||
# timeout: 10s
|
||||
# retries: 3
|
||||
# start_period: 90s
|
||||
# networks:
|
||||
# - internal
|
||||
# - traefik-public
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.http.routers.mosaic-auth.rule=Host(`${MOSAIC_AUTH_DOMAIN:-auth.mosaicstack.dev}`)"
|
||||
# - "traefik.http.routers.mosaic-auth.entrypoints=web"
|
||||
# - "traefik.http.services.mosaic-auth.loadbalancer.server.port=9000"
|
||||
#
|
||||
# authentik-worker:
|
||||
# image: ghcr.io/goauthentik/server:2024.12.1
|
||||
# command: worker
|
||||
# environment:
|
||||
# AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
|
||||
# AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
|
||||
# AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||
# AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||
# AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
# AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
# AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
# AUTHENTIK_REDIS__HOST: authentik-redis
|
||||
# AUTHENTIK_REDIS__PORT: 6379
|
||||
# volumes:
|
||||
# - authentik_media:/media
|
||||
# - authentik_certs:/certs
|
||||
# - authentik_templates:/templates
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
api:
|
||||
image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest}
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${API_PORT:-3001}
|
||||
API_HOST: ${API_HOST:-0.0.0.0}
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
# Auth (external Authentik)
|
||||
OIDC_ENABLED: ${OIDC_ENABLED:-false}
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-}
|
||||
JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret}
|
||||
JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
||||
CSRF_SECRET: ${CSRF_SECRET}
|
||||
# External services
|
||||
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT}
|
||||
OPENBAO_ADDR: ${OPENBAO_ADDR}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||
# Matrix bridge (optional — configure after Synapse is running)
|
||||
MATRIX_HOMESERVER_URL: ${MATRIX_HOMESERVER_URL:-http://synapse:8008}
|
||||
MATRIX_ACCESS_TOKEN: ${MATRIX_ACCESS_TOKEN:-}
|
||||
MATRIX_BOT_USER_ID: ${MATRIX_BOT_USER_ID:-}
|
||||
MATRIX_CONTROL_ROOM_ID: ${MATRIX_CONTROL_ROOM_ID:-}
|
||||
MATRIX_WORKSPACE_ID: ${MATRIX_WORKSPACE_ID:-}
|
||||
MATRIX_SERVER_NAME: ${MATRIX_SERVER_NAME:-}
|
||||
# Speech
|
||||
SPEECH_MAX_UPLOAD_SIZE: ${SPEECH_MAX_UPLOAD_SIZE:-25000000}
|
||||
SPEECH_MAX_DURATION_SECONDS: ${SPEECH_MAX_DURATION_SECONDS:-600}
|
||||
SPEECH_MAX_TEXT_LENGTH: ${SPEECH_MAX_TEXT_LENGTH:-4096}
|
||||
# Telemetry (disabled by default)
|
||||
MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false}
|
||||
MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-}
|
||||
MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-}
|
||||
MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-}
|
||||
MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false}
|
||||
# Frontend URLs (for CORS and auth redirects)
|
||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
|
||||
TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-}
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN}`)"
|
||||
- "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
- "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
|
||||
# ======================
|
||||
# Ollama (Optional AI Service)
|
||||
# Mosaic Web
|
||||
# ======================
|
||||
# ollama:
|
||||
# image: ollama/ollama:latest
|
||||
# volumes:
|
||||
# - ollama_data:/root/.ollama
|
||||
# healthcheck:
|
||||
# test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
||||
# interval: 30s
|
||||
# timeout: 10s
|
||||
# retries: 3
|
||||
# start_period: 60s
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
web:
|
||||
image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest}
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${WEB_PORT:-3000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN}`)"
|
||||
- "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
- "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
|
||||
# ======================
|
||||
# Mosaic Coordinator
|
||||
@@ -247,7 +207,7 @@ services:
|
||||
image: git.mosaicstack.dev/mosaic/stack-coordinator:${IMAGE_TAG:-latest}
|
||||
environment:
|
||||
GITEA_WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET}
|
||||
GITEA_URL: ${GITEA_URL:-https://git.mosaicstack.dev}
|
||||
GITEA_URL: ${GITEA_URL}
|
||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
HOST: 0.0.0.0
|
||||
@@ -255,9 +215,9 @@ services:
|
||||
COORDINATOR_POLL_INTERVAL: ${COORDINATOR_POLL_INTERVAL:-5.0}
|
||||
COORDINATOR_MAX_CONCURRENT_AGENTS: ${COORDINATOR_MAX_CONCURRENT_AGENTS:-10}
|
||||
COORDINATOR_ENABLED: ${COORDINATOR_ENABLED:-true}
|
||||
# Telemetry (task completion tracking & predictions)
|
||||
# Telemetry (disabled by default)
|
||||
MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false}
|
||||
MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-https://tel-api.mosaicstack.dev}
|
||||
MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-}
|
||||
MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-}
|
||||
MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-}
|
||||
MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false}
|
||||
@@ -279,56 +239,6 @@ services:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# Mosaic API
|
||||
# ======================
|
||||
api:
|
||||
image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest}
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${API_PORT:-3001}
|
||||
API_HOST: ${API_HOST:-0.0.0.0}
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_password}@postgres:5432/${POSTGRES_DB:-mosaic}
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-}
|
||||
JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret}
|
||||
JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
||||
CSRF_SECRET: ${CSRF_SECRET}
|
||||
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
|
||||
OPENBAO_ADDR: ${OPENBAO_ADDR:-http://openbao:8200}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||
# Telemetry (task completion tracking & predictions)
|
||||
MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false}
|
||||
MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-https://tel-api.mosaicstack.dev}
|
||||
MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-}
|
||||
MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-}
|
||||
MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false}
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)"
|
||||
- "traefik.http.routers.mosaic-api.entrypoints=web"
|
||||
- "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
|
||||
|
||||
# ======================
|
||||
# Mosaic Orchestrator
|
||||
# ======================
|
||||
@@ -350,15 +260,16 @@ services:
|
||||
- orchestrator_workspace:/workspace
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"]
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- internal
|
||||
# Note: security_opt not supported in swarm mode
|
||||
# Security hardening done via cap_drop/cap_add
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
@@ -369,35 +280,152 @@ services:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ============================================
|
||||
# MATRIX (Synapse + Element Web)
|
||||
# ============================================
|
||||
|
||||
# ======================
|
||||
# Mosaic Web
|
||||
# Synapse Database Init
|
||||
# ======================
|
||||
web:
|
||||
image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest}
|
||||
# Creates the 'synapse' database in the shared PostgreSQL instance.
|
||||
# Runs once and exits. Idempotent — safe to run on every deploy.
|
||||
synapse-db-init:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${WEB_PORT:-3000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
PGHOST: postgres
|
||||
PGPORT: 5432
|
||||
PGUSER: ${POSTGRES_USER}
|
||||
PGPASSWORD: ${POSTGRES_PASSWORD}
|
||||
SYNAPSE_DB: ${SYNAPSE_POSTGRES_DB}
|
||||
SYNAPSE_USER: ${SYNAPSE_POSTGRES_USER}
|
||||
SYNAPSE_PASSWORD: ${SYNAPSE_POSTGRES_PASSWORD}
|
||||
entrypoint: ["sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
until pg_isready -h postgres -p 5432 -U $${PGUSER}; do
|
||||
echo "Waiting for PostgreSQL..."
|
||||
sleep 2
|
||||
done
|
||||
echo "PostgreSQL is ready. Creating Synapse database and user..."
|
||||
|
||||
psql -h postgres -U $${PGUSER} -tc "SELECT 1 FROM pg_roles WHERE rolname='$${SYNAPSE_USER}'" | grep -q 1 || \
|
||||
psql -h postgres -U $${PGUSER} -c "CREATE USER $${SYNAPSE_USER} WITH PASSWORD '$${SYNAPSE_PASSWORD}';"
|
||||
|
||||
psql -h postgres -U $${PGUSER} -tc "SELECT 1 FROM pg_database WHERE datname='$${SYNAPSE_DB}'" | grep -q 1 || \
|
||||
psql -h postgres -U $${PGUSER} -c "CREATE DATABASE $${SYNAPSE_DB} OWNER $${SYNAPSE_USER} ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE template0;"
|
||||
|
||||
echo "Synapse database ready: $${SYNAPSE_DB}"
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 5s
|
||||
max_attempts: 5
|
||||
|
||||
# ======================
|
||||
# Synapse (Matrix Homeserver)
|
||||
# ======================
|
||||
synapse:
|
||||
image: matrixdotorg/synapse:${SYNAPSE_IMAGE_TAG:-latest}
|
||||
environment:
|
||||
SYNAPSE_CONFIG_DIR: /data
|
||||
SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
|
||||
volumes:
|
||||
- /opt/mosaic/synapse:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 10s
|
||||
max_attempts: 10
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.mosaicstack.dev}`)"
|
||||
- "traefik.http.routers.mosaic-web.entrypoints=web"
|
||||
- "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
|
||||
- "traefik.http.routers.matrix.rule=Host(`${MATRIX_DOMAIN}`)"
|
||||
- "traefik.http.routers.matrix.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.matrix.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.routers.matrix.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
- "traefik.http.services.matrix.loadbalancer.server.port=8008"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
|
||||
# ======================
|
||||
# Element Web (Matrix Client)
|
||||
# ======================
|
||||
element-web:
|
||||
image: vectorim/element-web:${ELEMENT_IMAGE_TAG:-latest}
|
||||
volumes:
|
||||
- /opt/mosaic/synapse/element-config.json:/app/config.json:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 5s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.element.rule=Host(`${ELEMENT_DOMAIN}`)"
|
||||
- "traefik.http.routers.element.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.element.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.routers.element.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
- "traefik.http.services.element.loadbalancer.server.port=80"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
|
||||
# ============================================
|
||||
# SPEECH SERVICES
|
||||
# ============================================
|
||||
|
||||
# ======================
|
||||
# Speaches (STT + basic TTS)
|
||||
# ======================
|
||||
speaches:
|
||||
image: ghcr.io/speaches-ai/speaches:latest-cpu
|
||||
environment:
|
||||
WHISPER__MODEL: ${SPEACHES_WHISPER_MODEL:-Systran/faster-whisper-large-v3-turbo}
|
||||
volumes:
|
||||
- speaches_models:/root/.cache/huggingface
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 120s
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# Kokoro TTS
|
||||
# ======================
|
||||
kokoro-tts:
|
||||
image: ghcr.io/remsky/kokoro-fastapi-cpu:latest
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8880/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 120s
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# Volumes
|
||||
@@ -405,19 +433,8 @@ services:
|
||||
volumes:
|
||||
postgres_data:
|
||||
valkey_data:
|
||||
# OpenBao volumes - commented out (using standalone deployment)
|
||||
# openbao_data:
|
||||
# openbao_logs:
|
||||
# openbao_init:
|
||||
# Authentik volumes - commented out (using external Authentik)
|
||||
# authentik_postgres_data:
|
||||
# authentik_redis_data:
|
||||
# authentik_media:
|
||||
# authentik_certs:
|
||||
# authentik_templates:
|
||||
# Ollama volume - commented out (using external Ollama)
|
||||
# ollama_data:
|
||||
orchestrator_workspace:
|
||||
speaches_models:
|
||||
|
||||
# ======================
|
||||
# Networks
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - Docker Swarm Deployment
|
||||
# ==============================================
|
||||
#
|
||||
# IMPORTANT: Docker Swarm does NOT support docker-compose profiles
|
||||
# To disable services (e.g., for external alternatives), manually comment them out
|
||||
#
|
||||
# Current Configuration:
|
||||
# - PostgreSQL: ENABLED (internal)
|
||||
# - Valkey: ENABLED (internal)
|
||||
# - Coordinator: ENABLED (internal)
|
||||
# - OpenBao: DISABLED (must use standalone - see docker-compose.openbao.yml)
|
||||
# - Authentik: DISABLED (commented out - using external OIDC)
|
||||
# - Ollama: DISABLED (commented out - using external Ollama)
|
||||
#
|
||||
# For detailed deployment instructions, see:
|
||||
# docs/SWARM-DEPLOYMENT.md
|
||||
#
|
||||
# Quick Start:
|
||||
# 1. cp .env.swarm.example .env
|
||||
# 2. nano .env # Configure environment
|
||||
# 3. ./scripts/deploy-swarm.sh mosaic
|
||||
# 4. Initialize OpenBao manually (see docs/SWARM-DEPLOYMENT.md)
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# ======================
|
||||
# PostgreSQL Database
|
||||
# ======================
|
||||
postgres:
|
||||
image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest}
|
||||
env_file: .env
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-mosaic}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-mosaic}
|
||||
POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB}
|
||||
POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
|
||||
POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
# Note: init-scripts bind mount removed for Portainer compatibility
|
||||
# Init scripts are baked into the postgres image at build time
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# Valkey Cache
|
||||
# ======================
|
||||
valkey:
|
||||
image: valkey/valkey:8-alpine
|
||||
env_file: .env
|
||||
command:
|
||||
- valkey-server
|
||||
- --maxmemory ${VALKEY_MAXMEMORY:-256mb}
|
||||
- --maxmemory-policy noeviction
|
||||
- --appendonly yes
|
||||
volumes:
|
||||
- valkey_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "valkey-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# OpenBao Secrets Vault
|
||||
# ======================
|
||||
openbao:
|
||||
image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest}
|
||||
entrypoint: ["dumb-init", "--"]
|
||||
command: ["bao", "server", "-config=/openbao/config/config.hcl"]
|
||||
env_file: .env
|
||||
environment:
|
||||
OPENBAO_ADDR: http://0.0.0.0:8200
|
||||
volumes:
|
||||
- openbao_data:/openbao/data
|
||||
- openbao_logs:/openbao/logs
|
||||
- openbao_init:/openbao/init
|
||||
cap_add:
|
||||
- IPC_LOCK
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"wget --spider --quiet 'http://localhost:8200/v1/sys/health?standbyok=true&uninitcode=200&sealedcode=200'",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# OpenBao Init Sidecar
|
||||
# ======================
|
||||
# Auto-initializes and unseals OpenBao on first run.
|
||||
# The init script has built-in retry logic (waits for OpenBao API).
|
||||
openbao-init:
|
||||
image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest}
|
||||
command: /openbao/init.sh
|
||||
env_file: .env
|
||||
environment:
|
||||
VAULT_ADDR: http://openbao:8200
|
||||
volumes:
|
||||
- openbao_init:/openbao/init
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
max_attempts: 5
|
||||
delay: 10s
|
||||
|
||||
# ======================
|
||||
# Authentik - COMMENTED OUT (Using External Authentik)
|
||||
# ======================
|
||||
# Uncomment these services if you want to run Authentik internally
|
||||
# For external Authentik, configure OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET in .env
|
||||
#
|
||||
# authentik-postgres:
|
||||
# image: postgres:17.7-alpine3.22
|
||||
# env_file: .env
|
||||
# environment:
|
||||
# POSTGRES_USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
# POSTGRES_PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
# POSTGRES_DB: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
# volumes:
|
||||
# - authentik_postgres_data:/var/lib/postgresql/data
|
||||
# healthcheck:
|
||||
# test: ["CMD-SHELL", "pg_isready -U ${AUTHENTIK_POSTGRES_USER:-authentik}"]
|
||||
# interval: 10s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
# start_period: 20s
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
#
|
||||
# authentik-redis:
|
||||
# image: valkey/valkey:8-alpine
|
||||
# env_file: .env
|
||||
# command: valkey-server --save 60 1 --loglevel warning
|
||||
# volumes:
|
||||
# - authentik_redis_data:/data
|
||||
# healthcheck:
|
||||
# test: ["CMD", "valkey-cli", "ping"]
|
||||
# interval: 10s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
# start_period: 10s
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
#
|
||||
# authentik-server:
|
||||
# image: ghcr.io/goauthentik/server:2024.12.1
|
||||
# env_file: .env
|
||||
# command: server
|
||||
# environment:
|
||||
# AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
|
||||
# AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
|
||||
# AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||
# AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||
# AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
# AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
# AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
# AUTHENTIK_REDIS__HOST: authentik-redis
|
||||
# AUTHENTIK_REDIS__PORT: 6379
|
||||
# AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-admin}
|
||||
# AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL:-admin@localhost}
|
||||
# AUTHENTIK_COOKIE_DOMAIN: ${AUTHENTIK_COOKIE_DOMAIN:-.mosaicstack.dev}
|
||||
# volumes:
|
||||
# - authentik_media:/media
|
||||
# - authentik_templates:/templates
|
||||
# healthcheck:
|
||||
# test:
|
||||
# [
|
||||
# "CMD",
|
||||
# "wget",
|
||||
# "--no-verbose",
|
||||
# "--tries=1",
|
||||
# "--spider",
|
||||
# "http://localhost:9000/-/health/live/",
|
||||
# ]
|
||||
# interval: 30s
|
||||
# timeout: 10s
|
||||
# retries: 3
|
||||
# start_period: 90s
|
||||
# networks:
|
||||
# - internal
|
||||
# - traefik-public
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.http.routers.mosaic-auth.rule=Host(`${MOSAIC_AUTH_DOMAIN:-auth.mosaicstack.dev}`)"
|
||||
# - "traefik.http.routers.mosaic-auth.entrypoints=web"
|
||||
# - "traefik.http.services.mosaic-auth.loadbalancer.server.port=9000"
|
||||
#
|
||||
# authentik-worker:
|
||||
# image: ghcr.io/goauthentik/server:2024.12.1
|
||||
# env_file: .env
|
||||
# command: worker
|
||||
# environment:
|
||||
# AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
|
||||
# AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
|
||||
# AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||
# AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||
# AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||
# AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||
# AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||
# AUTHENTIK_REDIS__HOST: authentik-redis
|
||||
# AUTHENTIK_REDIS__PORT: 6379
|
||||
# volumes:
|
||||
# - authentik_media:/media
|
||||
# - authentik_certs:/certs
|
||||
# - authentik_templates:/templates
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
|
||||
# ======================
|
||||
# Ollama (Optional AI Service)
|
||||
# ======================
|
||||
# ollama:
|
||||
# image: ollama/ollama:latest
|
||||
# env_file: .env
|
||||
# volumes:
|
||||
# - ollama_data:/root/.ollama
|
||||
# healthcheck:
|
||||
# test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
||||
# interval: 30s
|
||||
# timeout: 10s
|
||||
# retries: 3
|
||||
# start_period: 60s
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
|
||||
# ======================
|
||||
# Mosaic Coordinator
|
||||
# ======================
|
||||
coordinator:
|
||||
image: git.mosaicstack.dev/mosaic/stack-coordinator:${IMAGE_TAG:-latest}
|
||||
env_file: .env
|
||||
environment:
|
||||
GITEA_WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET}
|
||||
GITEA_URL: ${GITEA_URL:-https://git.mosaicstack.dev}
|
||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
HOST: 0.0.0.0
|
||||
PORT: 8000
|
||||
COORDINATOR_POLL_INTERVAL: ${COORDINATOR_POLL_INTERVAL:-5.0}
|
||||
COORDINATOR_MAX_CONCURRENT_AGENTS: ${COORDINATOR_MAX_CONCURRENT_AGENTS:-10}
|
||||
COORDINATOR_ENABLED: ${COORDINATOR_ENABLED:-true}
|
||||
# Telemetry (task completion tracking & predictions)
|
||||
MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false}
|
||||
MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-https://tel-api.mosaicstack.dev}
|
||||
MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-}
|
||||
MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-}
|
||||
MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false}
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"python",
|
||||
"-c",
|
||||
"import urllib.request; urllib.request.urlopen('http://localhost:8000/health')",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# Mosaic API
|
||||
# ======================
|
||||
api:
|
||||
image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest}
|
||||
env_file: .env
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${API_PORT:-3001}
|
||||
API_HOST: ${API_HOST:-0.0.0.0}
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_password}@postgres:5432/${POSTGRES_DB:-mosaic}
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-http://localhost:3001/auth/callback}
|
||||
JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret}
|
||||
JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
||||
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
|
||||
OPENBAO_ADDR: ${OPENBAO_ADDR:-http://openbao:8200}
|
||||
ORCHESTRATOR_URL: ${ORCHESTRATOR_URL:-http://orchestrator:3001}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||
# Telemetry (task completion tracking & predictions)
|
||||
MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false}
|
||||
MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-https://tel-api.mosaicstack.dev}
|
||||
MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-}
|
||||
MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-}
|
||||
MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false}
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)"
|
||||
- "traefik.http.routers.mosaic-api.entrypoints=web"
|
||||
- "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
|
||||
|
||||
# ======================
|
||||
# Mosaic Orchestrator
|
||||
# ======================
|
||||
orchestrator:
|
||||
image: git.mosaicstack.dev/mosaic/stack-orchestrator:${IMAGE_TAG:-latest}
|
||||
env_file: .env
|
||||
user: "1000:1000"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
ORCHESTRATOR_PORT: 3001
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
||||
DOCKER_SOCKET: /var/run/docker.sock
|
||||
GIT_USER_NAME: "Mosaic Orchestrator"
|
||||
GIT_USER_EMAIL: "orchestrator@mosaicstack.dev"
|
||||
KILLSWITCH_ENABLED: "true"
|
||||
SANDBOX_ENABLED: "true"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- orchestrator_workspace:/workspace
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- internal
|
||||
# Note: security_opt not supported in swarm mode
|
||||
# Security hardening done via cap_drop/cap_add
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
tmpfs:
|
||||
- /tmp:noexec,nosuid,size=100m
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# Mosaic Web
|
||||
# ======================
|
||||
web:
|
||||
image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest}
|
||||
env_file: .env
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${WEB_PORT:-3000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.mosaicstack.dev}`)"
|
||||
- "traefik.http.routers.mosaic-web.entrypoints=web"
|
||||
- "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
|
||||
|
||||
# ======================
|
||||
# Volumes
|
||||
# ======================
|
||||
volumes:
|
||||
postgres_data:
|
||||
valkey_data:
|
||||
openbao_data:
|
||||
openbao_logs:
|
||||
openbao_init:
|
||||
# Authentik volumes - commented out (using external Authentik)
|
||||
# authentik_postgres_data:
|
||||
# authentik_redis_data:
|
||||
# authentik_media:
|
||||
# authentik_certs:
|
||||
# authentik_templates:
|
||||
# Ollama volume - commented out (using external Ollama)
|
||||
# ollama_data:
|
||||
orchestrator_workspace:
|
||||
|
||||
# ======================
|
||||
# Networks
|
||||
# ======================
|
||||
networks:
|
||||
internal:
|
||||
driver: overlay
|
||||
traefik-public:
|
||||
external: true
|
||||
@@ -27,9 +27,6 @@ services:
|
||||
start_period: 30s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- database
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=database"
|
||||
- "com.mosaic.description=PostgreSQL 17 with pgvector"
|
||||
@@ -58,9 +55,6 @@ services:
|
||||
start_period: 10s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
profiles:
|
||||
- cache
|
||||
- full
|
||||
labels:
|
||||
- "com.mosaic.service=cache"
|
||||
- "com.mosaic.description=Valkey Redis-compatible cache"
|
||||
@@ -384,6 +378,10 @@ services:
|
||||
MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-}
|
||||
MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-}
|
||||
MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false}
|
||||
# Frontend URLs (for CORS and auth redirects)
|
||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||
TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-}
|
||||
volumes:
|
||||
- openbao_init:/openbao/init:ro
|
||||
ports:
|
||||
@@ -456,7 +454,10 @@ services:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"]
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -465,7 +465,10 @@ services:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"]
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Development overrides for docker-compose.yml
|
||||
# Usage: docker compose -f docker-compose.yml -f docker-compose.dev.yml up
|
||||
|
||||
services:
|
||||
postgres:
|
||||
environment:
|
||||
POSTGRES_USER: mosaic
|
||||
POSTGRES_PASSWORD: mosaic_dev_password
|
||||
POSTGRES_DB: mosaic
|
||||
ports:
|
||||
- "5432:5432"
|
||||
# Enable query logging for development
|
||||
command:
|
||||
- "postgres"
|
||||
- "-c"
|
||||
- "log_statement=all"
|
||||
- "-c"
|
||||
- "log_duration=on"
|
||||
|
||||
valkey:
|
||||
ports:
|
||||
- "6379:6379"
|
||||
@@ -1,126 +0,0 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - External Services Deployment Example
|
||||
# ==============================================
|
||||
# This example shows a production deployment using external managed services.
|
||||
# All infrastructure (database, cache, secrets, auth, AI) is managed externally.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy this file to docker-compose.override.yml
|
||||
# 2. Set COMPOSE_PROFILES= (empty) in .env
|
||||
# 3. Configure external service URLs in .env (see below)
|
||||
# 4. Run: docker compose up -d
|
||||
#
|
||||
# Or run directly:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.example.external.yml up -d
|
||||
#
|
||||
# Services Included:
|
||||
# - API (NestJS) - configured to use external services
|
||||
# - Web (Next.js)
|
||||
# - Orchestrator (Agent management)
|
||||
#
|
||||
# External Services (configured via .env):
|
||||
# - PostgreSQL (e.g., AWS RDS, Google Cloud SQL, Azure Database)
|
||||
# - Redis/Valkey (e.g., AWS ElastiCache, Google Memorystore, Azure Cache)
|
||||
# - OpenBao/Vault (e.g., HashiCorp Vault Cloud, self-hosted)
|
||||
# - OIDC Provider (e.g., Auth0, Okta, Google, Azure AD)
|
||||
# - LLM Service (e.g., hosted Ollama, OpenAI, Anthropic)
|
||||
#
|
||||
# Required Environment Variables (.env):
|
||||
# COMPOSE_PROFILES= # Empty - no bundled services
|
||||
# IMAGE_TAG=latest
|
||||
#
|
||||
# # External Database
|
||||
# DATABASE_URL=postgresql://user:password@rds.example.com:5432/mosaic
|
||||
#
|
||||
# # External Cache
|
||||
# VALKEY_URL=redis://elasticache.example.com:6379
|
||||
#
|
||||
# # External Secrets (OpenBao/Vault)
|
||||
# OPENBAO_ADDR=https://vault.example.com:8200
|
||||
# OPENBAO_ROLE_ID=your-role-id
|
||||
# OPENBAO_SECRET_ID=your-secret-id
|
||||
#
|
||||
# # External OIDC Authentication
|
||||
# OIDC_ENABLED=true
|
||||
# OIDC_ISSUER=https://auth.example.com/
|
||||
# OIDC_CLIENT_ID=your-client-id
|
||||
# OIDC_CLIENT_SECRET=your-client-secret
|
||||
#
|
||||
# # External LLM Service
|
||||
# OLLAMA_ENDPOINT=https://ollama.example.com:11434
|
||||
# # Or use OpenAI:
|
||||
# # AI_PROVIDER=openai
|
||||
# # OPENAI_API_KEY=sk-...
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# Disable all bundled infrastructure services
|
||||
postgres:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
valkey:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
openbao:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
openbao-init:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-postgres:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-redis:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-server:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-worker:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
ollama:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
# Configure API to use external services
|
||||
api:
|
||||
environment:
|
||||
# External database (e.g., AWS RDS)
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
|
||||
# External cache (e.g., AWS ElastiCache)
|
||||
VALKEY_URL: ${VALKEY_URL}
|
||||
|
||||
# External secrets (e.g., HashiCorp Vault Cloud)
|
||||
OPENBAO_ADDR: ${OPENBAO_ADDR}
|
||||
OPENBAO_ROLE_ID: ${OPENBAO_ROLE_ID}
|
||||
OPENBAO_SECRET_ID: ${OPENBAO_SECRET_ID}
|
||||
|
||||
# External LLM (e.g., hosted Ollama or OpenAI)
|
||||
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT}
|
||||
|
||||
# External OIDC (e.g., Auth0, Okta, Google)
|
||||
OIDC_ENABLED: ${OIDC_ENABLED}
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
|
||||
# Security
|
||||
CSRF_SECRET: ${CSRF_SECRET}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||
|
||||
# Web app remains unchanged
|
||||
# web: (uses defaults from docker-compose.yml)
|
||||
|
||||
# Orchestrator remains unchanged
|
||||
# orchestrator: (uses defaults from docker-compose.yml)
|
||||
@@ -1,114 +0,0 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - Hybrid Deployment Example
|
||||
# ==============================================
|
||||
# This example shows a hybrid deployment mixing bundled and external services.
|
||||
# Common for staging environments: bundled database/cache, external auth/secrets.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy this file to docker-compose.override.yml
|
||||
# 2. Set COMPOSE_PROFILES=database,cache,ollama in .env
|
||||
# 3. Configure external service URLs in .env (see below)
|
||||
# 4. Run: docker compose up -d
|
||||
#
|
||||
# Or run directly:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.example.hybrid.yml up -d
|
||||
#
|
||||
# Services Included (Bundled):
|
||||
# - PostgreSQL 17 with pgvector
|
||||
# - Valkey (Redis-compatible cache)
|
||||
# - Ollama (local LLM)
|
||||
# - API (NestJS)
|
||||
# - Web (Next.js)
|
||||
# - Orchestrator (Agent management)
|
||||
#
|
||||
# Services Included (External):
|
||||
# - OpenBao/Vault (managed secrets)
|
||||
# - Authentik/OIDC (managed authentication)
|
||||
#
|
||||
# Environment Variables (.env):
|
||||
# COMPOSE_PROFILES=database,cache,ollama # Enable only these bundled services
|
||||
# IMAGE_TAG=dev
|
||||
#
|
||||
# # Bundled Database (default from docker-compose.yml)
|
||||
# DATABASE_URL=postgresql://mosaic:${POSTGRES_PASSWORD}@postgres:5432/mosaic
|
||||
#
|
||||
# # Bundled Cache (default from docker-compose.yml)
|
||||
# VALKEY_URL=redis://valkey:6379
|
||||
#
|
||||
# # Bundled Ollama (default from docker-compose.yml)
|
||||
# OLLAMA_ENDPOINT=http://ollama:11434
|
||||
#
|
||||
# # External Secrets (OpenBao/Vault)
|
||||
# OPENBAO_ADDR=https://vault.example.com:8200
|
||||
# OPENBAO_ROLE_ID=your-role-id
|
||||
# OPENBAO_SECRET_ID=your-secret-id
|
||||
#
|
||||
# # External OIDC Authentication
|
||||
# OIDC_ENABLED=true
|
||||
# OIDC_ISSUER=https://auth.example.com/
|
||||
# OIDC_CLIENT_ID=your-client-id
|
||||
# OIDC_CLIENT_SECRET=your-client-secret
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# Use bundled PostgreSQL and Valkey (enabled via database,cache profiles)
|
||||
# No overrides needed - profiles handle this
|
||||
|
||||
# Disable bundled Authentik - use external OIDC
|
||||
authentik-postgres:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-redis:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-server:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
authentik-worker:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
# Disable bundled OpenBao - use external vault
|
||||
openbao:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
openbao-init:
|
||||
profiles:
|
||||
- disabled
|
||||
|
||||
# Use bundled Ollama (enabled via ollama profile)
|
||||
# No override needed
|
||||
|
||||
# Configure API for hybrid deployment
|
||||
api:
|
||||
environment:
|
||||
# Bundled database (default)
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-mosaic}
|
||||
|
||||
# Bundled cache (default)
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
|
||||
# External secrets
|
||||
OPENBAO_ADDR: ${OPENBAO_ADDR}
|
||||
OPENBAO_ROLE_ID: ${OPENBAO_ROLE_ID}
|
||||
OPENBAO_SECRET_ID: ${OPENBAO_SECRET_ID}
|
||||
|
||||
# Bundled Ollama (default)
|
||||
OLLAMA_ENDPOINT: http://ollama:11434
|
||||
|
||||
# External OIDC
|
||||
OIDC_ENABLED: ${OIDC_ENABLED}
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
|
||||
# Security
|
||||
CSRF_SECRET: ${CSRF_SECRET}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||
|
||||
# Web and Orchestrator use defaults from docker-compose.yml
|
||||
@@ -1,43 +0,0 @@
|
||||
# ==============================================
|
||||
# Mosaic Stack - Turnkey Deployment Example
|
||||
# ==============================================
|
||||
# This example shows a complete all-in-one deployment with all services bundled.
|
||||
# Ideal for local development, testing, and demo environments.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy this file to docker-compose.override.yml (optional)
|
||||
# 2. Set COMPOSE_PROFILES=full in .env
|
||||
# 3. Run: docker compose up -d
|
||||
#
|
||||
# Or run directly:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.example.turnkey.yml up -d
|
||||
#
|
||||
# Services Included:
|
||||
# - PostgreSQL 17 with pgvector
|
||||
# - Valkey (Redis-compatible cache)
|
||||
# - OpenBao (secrets management)
|
||||
# - Authentik (OIDC authentication)
|
||||
# - Ollama (local LLM)
|
||||
# - Traefik (reverse proxy) - optional, requires traefik-bundled profile
|
||||
# - API (NestJS)
|
||||
# - Web (Next.js)
|
||||
# - Orchestrator (Agent management)
|
||||
#
|
||||
# Environment Variables (.env):
|
||||
# COMPOSE_PROFILES=full
|
||||
# IMAGE_TAG=dev # or latest
|
||||
#
|
||||
# All services run in Docker containers with no external dependencies.
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# No service overrides needed - the main docker-compose.yml handles everything
|
||||
# This file serves as documentation for turnkey deployment
|
||||
# Set COMPOSE_PROFILES=full in your .env file to enable all services
|
||||
|
||||
# Placeholder to make the file valid YAML
|
||||
# (Docker Compose requires at least one service definition)
|
||||
_placeholder:
|
||||
image: alpine:latest
|
||||
profiles:
|
||||
- never-used
|
||||
@@ -1,123 +0,0 @@
|
||||
# ==============================================
|
||||
# Matrix Dev Environment (Synapse + Element Web)
|
||||
# ==============================================
|
||||
#
|
||||
# Development-only overlay for testing the Matrix bridge locally.
|
||||
# NOT for production — use docker-compose.sample.matrix.yml for production.
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml up -d
|
||||
#
|
||||
# Or with Makefile:
|
||||
# make matrix-up
|
||||
#
|
||||
# This overlay:
|
||||
# - Adds Synapse homeserver (localhost:8008) using shared PostgreSQL
|
||||
# - Adds Element Web client (localhost:8501)
|
||||
# - Creates a separate 'synapse' database in the shared PostgreSQL instance
|
||||
# - Enables open registration for easy dev testing
|
||||
#
|
||||
# After first startup, create the bot account:
|
||||
# docker/matrix/scripts/setup-bot.sh
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# ======================
|
||||
# Synapse Database Init
|
||||
# ======================
|
||||
# Creates the 'synapse' database and user in the shared PostgreSQL instance.
|
||||
# Runs once and exits — idempotent, safe to run repeatedly.
|
||||
synapse-db-init:
|
||||
image: postgres:17-alpine
|
||||
container_name: mosaic-synapse-db-init
|
||||
restart: "no"
|
||||
environment:
|
||||
PGHOST: postgres
|
||||
PGPORT: 5432
|
||||
PGUSER: ${POSTGRES_USER:-mosaic}
|
||||
PGPASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password}
|
||||
SYNAPSE_DB: ${SYNAPSE_POSTGRES_DB:-synapse}
|
||||
SYNAPSE_USER: ${SYNAPSE_POSTGRES_USER:-synapse}
|
||||
SYNAPSE_PASSWORD: ${SYNAPSE_POSTGRES_PASSWORD:-synapse_dev_password}
|
||||
entrypoint: ["sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
until pg_isready -h postgres -p 5432 -U $${PGUSER}; do
|
||||
echo "Waiting for PostgreSQL..."
|
||||
sleep 2
|
||||
done
|
||||
echo "PostgreSQL is ready. Creating Synapse database and user..."
|
||||
|
||||
psql -h postgres -U $${PGUSER} -tc "SELECT 1 FROM pg_roles WHERE rolname='$${SYNAPSE_USER}'" | grep -q 1 || \
|
||||
psql -h postgres -U $${PGUSER} -c "CREATE USER $${SYNAPSE_USER} WITH PASSWORD '$${SYNAPSE_PASSWORD}';"
|
||||
|
||||
psql -h postgres -U $${PGUSER} -tc "SELECT 1 FROM pg_database WHERE datname='$${SYNAPSE_DB}'" | grep -q 1 || \
|
||||
psql -h postgres -U $${PGUSER} -c "CREATE DATABASE $${SYNAPSE_DB} OWNER $${SYNAPSE_USER} ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE template0;"
|
||||
|
||||
echo "Synapse database ready: $${SYNAPSE_DB}"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- mosaic-internal
|
||||
|
||||
# ======================
|
||||
# Synapse (Matrix Homeserver)
|
||||
# ======================
|
||||
synapse:
|
||||
image: matrixdotorg/synapse:latest
|
||||
container_name: mosaic-synapse
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
SYNAPSE_CONFIG_DIR: /data
|
||||
SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
|
||||
ports:
|
||||
- "${SYNAPSE_CLIENT_PORT:-8008}:8008"
|
||||
- "${SYNAPSE_FEDERATION_PORT:-8448}:8448"
|
||||
volumes:
|
||||
- /opt/mosaic/synapse/homeserver.yaml:/data/homeserver.yaml:ro
|
||||
- /opt/mosaic/synapse/media_store:/data/media_store
|
||||
- /opt/mosaic/synapse/keys:/data/keys
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
synapse-db-init:
|
||||
condition: service_completed_successfully
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
com.mosaic.service: "matrix-synapse"
|
||||
com.mosaic.description: "Matrix homeserver (dev)"
|
||||
|
||||
# ======================
|
||||
# Element Web (Matrix Client)
|
||||
# ======================
|
||||
element-web:
|
||||
image: vectorim/element-web:latest
|
||||
container_name: mosaic-element-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${ELEMENT_PORT:-8501}:80"
|
||||
volumes:
|
||||
- /opt/mosaic/synapse/element-config.json:/app/config.json:ro
|
||||
depends_on:
|
||||
synapse:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
com.mosaic.service: "matrix-element"
|
||||
com.mosaic.description: "Element Web client (dev)"
|
||||
@@ -1,182 +0,0 @@
|
||||
# Production Docker Compose - Uses pre-built images from Gitea Packages
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Images built and pushed to git.mosaicstack.dev/mosaic/*
|
||||
# - .env file configured with production values
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.prod.yml up -d
|
||||
#
|
||||
# For Portainer:
|
||||
# - Stack → Add Stack → Repository
|
||||
# - Compose file: docker-compose.prod.yml
|
||||
|
||||
services:
|
||||
# ======================
|
||||
# PostgreSQL Database
|
||||
# ======================
|
||||
postgres:
|
||||
image: git.mosaicstack.dev/mosaic/postgres:latest
|
||||
container_name: mosaic-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-mosaic}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-mosaic}
|
||||
POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB}
|
||||
POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
|
||||
POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
- "com.mosaic.service=database"
|
||||
- "com.mosaic.description=PostgreSQL 17 with pgvector"
|
||||
|
||||
# ======================
|
||||
# Valkey Cache
|
||||
# ======================
|
||||
valkey:
|
||||
image: valkey/valkey:8-alpine
|
||||
container_name: mosaic-valkey
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- valkey-server
|
||||
- --maxmemory ${VALKEY_MAXMEMORY:-256mb}
|
||||
- --maxmemory-policy noeviction
|
||||
- --appendonly yes
|
||||
volumes:
|
||||
- valkey_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "valkey-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
labels:
|
||||
- "com.mosaic.service=cache"
|
||||
- "com.mosaic.description=Valkey Redis-compatible cache"
|
||||
|
||||
# ======================
|
||||
# Mosaic API
|
||||
# ======================
|
||||
api:
|
||||
image: git.mosaicstack.dev/mosaic/api:latest
|
||||
container_name: mosaic-api
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${API_PORT:-3001}
|
||||
API_HOST: ${API_HOST:-0.0.0.0}
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-mosaic}
|
||||
VALKEY_URL: redis://valkey:6379
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
||||
CSRF_SECRET: ${CSRF_SECRET}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
|
||||
ports:
|
||||
- "${API_PORT:-3001}:${API_PORT:-3001}"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
valkey:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- mosaic-internal
|
||||
- mosaic-public
|
||||
labels:
|
||||
- "com.mosaic.service=api"
|
||||
- "com.mosaic.description=Mosaic NestJS API"
|
||||
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
|
||||
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)"
|
||||
- "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
|
||||
- "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
|
||||
# ======================
|
||||
# Mosaic Web
|
||||
# ======================
|
||||
web:
|
||||
image: git.mosaicstack.dev/mosaic/web:latest
|
||||
container_name: mosaic-web
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${WEB_PORT:-3000}
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.mosaicstack.dev}
|
||||
ports:
|
||||
- "${WEB_PORT:-3000}:${WEB_PORT:-3000}"
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- mosaic-public
|
||||
labels:
|
||||
- "com.mosaic.service=web"
|
||||
- "com.mosaic.description=Mosaic Next.js Web App"
|
||||
- "traefik.enable=${TRAEFIK_ENABLE:-false}"
|
||||
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-app.mosaicstack.dev}`)"
|
||||
- "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}"
|
||||
- "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
|
||||
# ======================
|
||||
# Volumes
|
||||
# ======================
|
||||
volumes:
|
||||
postgres_data:
|
||||
name: mosaic-postgres-data
|
||||
driver: local
|
||||
valkey_data:
|
||||
name: mosaic-valkey-data
|
||||
driver: local
|
||||
|
||||
# ======================
|
||||
# Networks
|
||||
# ======================
|
||||
networks:
|
||||
mosaic-internal:
|
||||
name: mosaic-internal
|
||||
driver: bridge
|
||||
mosaic-public:
|
||||
name: mosaic-public
|
||||
driver: bridge
|
||||
@@ -1,206 +0,0 @@
|
||||
# ==============================================
|
||||
# Matrix (Synapse + Element) - Sample Swarm Deployment
|
||||
# ==============================================
|
||||
#
|
||||
# Standalone Matrix homeserver deployment for use with Mosaic Stack.
|
||||
# This is SEPARATE infrastructure — not part of the Mosaic Stack itself.
|
||||
# Mosaic connects to it via MATRIX_HOMESERVER_URL environment variable.
|
||||
#
|
||||
# Also serves: personal communications, GoToSocial bridges, other projects.
|
||||
#
|
||||
# Usage (Docker Swarm via Portainer):
|
||||
# 1. Create a new stack in Portainer
|
||||
# 2. Paste this file or point to the repo
|
||||
# 3. Set environment variables in Portainer's env var section
|
||||
# 4. Deploy the stack
|
||||
#
|
||||
# Usage (Docker Swarm CLI):
|
||||
# 1. cp docker-compose.sample.matrix.env .env
|
||||
# 2. nano .env # Configure
|
||||
# 3. docker stack deploy -c docker-compose.sample.matrix.yml matrix
|
||||
#
|
||||
# Post-Deploy Setup:
|
||||
# 1. Generate Synapse config (first run only):
|
||||
# docker exec <synapse_container> python -m synapse.app.homeserver \
|
||||
# --server-name ${MATRIX_DOMAIN} --report-stats no \
|
||||
# --generate-config --config-path /data/homeserver.yaml
|
||||
#
|
||||
# 2. Create admin account:
|
||||
# docker exec -it <synapse_container> register_new_matrix_user \
|
||||
# -u admin -a -c /data/homeserver.yaml http://localhost:8008
|
||||
#
|
||||
# 3. Create Mosaic bot account:
|
||||
# docker exec -it <synapse_container> register_new_matrix_user \
|
||||
# -u mosaic-bot -c /data/homeserver.yaml http://localhost:8008
|
||||
#
|
||||
# 4. Generate bot access token:
|
||||
# curl -X POST http://localhost:8008/_matrix/client/v3/login \
|
||||
# -d '{"type":"m.login.password","user":"mosaic-bot","password":"<password>"}'
|
||||
#
|
||||
# 5. Set MATRIX_ACCESS_TOKEN in Mosaic Stack .env
|
||||
#
|
||||
# Required Environment Variables:
|
||||
# MATRIX_DOMAIN=matrix.example.com # Synapse server name (permanent!)
|
||||
# ELEMENT_DOMAIN=chat.example.com # Element Web domain
|
||||
# POSTGRES_PASSWORD=<strong-password> # Synapse database password
|
||||
#
|
||||
# Optional Environment Variables:
|
||||
# SYNAPSE_IMAGE_TAG=latest # Synapse version
|
||||
# ELEMENT_IMAGE_TAG=latest # Element Web version
|
||||
# POSTGRES_IMAGE_TAG=16-alpine # PostgreSQL version
|
||||
# TRAEFIK_ENTRYPOINT=websecure # Traefik entrypoint name
|
||||
# TRAEFIK_CERTRESOLVER=letsencrypt # Traefik cert resolver
|
||||
# TRAEFIK_DOCKER_NETWORK=traefik-public # Traefik network name
|
||||
# SYNAPSE_ENABLE_REGISTRATION=false # Public registration
|
||||
# SYNAPSE_REPORT_STATS=no # Anonymous stats reporting
|
||||
# SYNAPSE_MAX_UPLOAD_SIZE=50M # Max file upload size
|
||||
#
|
||||
# Connecting to Mosaic Stack:
|
||||
# Add to your Mosaic Stack .env:
|
||||
# MATRIX_HOMESERVER_URL=http://synapse:8008 (if same Docker network)
|
||||
# MATRIX_HOMESERVER_URL=https://matrix.example.com (if external)
|
||||
# MATRIX_ACCESS_TOKEN=<bot access token from step 4>
|
||||
# MATRIX_BOT_USER_ID=@mosaic-bot:matrix.example.com
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# ======================
|
||||
# Synapse (Matrix Homeserver)
|
||||
# ======================
|
||||
synapse:
|
||||
image: matrixdotorg/synapse:${SYNAPSE_IMAGE_TAG:-latest}
|
||||
environment:
|
||||
SYNAPSE_SERVER_NAME: ${MATRIX_DOMAIN}
|
||||
SYNAPSE_REPORT_STATS: ${SYNAPSE_REPORT_STATS:-no}
|
||||
SYNAPSE_CONFIG_DIR: /data
|
||||
SYNAPSE_DATA_DIR: /data
|
||||
SYNAPSE_LOG_LEVEL: ${SYNAPSE_LOG_LEVEL:-WARNING}
|
||||
# Database connection (external PostgreSQL or bundled)
|
||||
POSTGRES_HOST: synapse-postgres
|
||||
POSTGRES_PORT: 5432
|
||||
POSTGRES_DB: ${SYNAPSE_POSTGRES_DB:-synapse}
|
||||
POSTGRES_USER: ${SYNAPSE_POSTGRES_USER:-synapse}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- synapse-data:/data
|
||||
- synapse-media:/data/media_store
|
||||
depends_on:
|
||||
synapse-postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 10s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.matrix.rule=Host(`${MATRIX_DOMAIN}`)"
|
||||
- "traefik.http.routers.matrix.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.matrix.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.routers.matrix.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
- "traefik.http.services.matrix.loadbalancer.server.port=8008"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
# Well-known delegation (optional — for .well-known/matrix/server)
|
||||
# - "traefik.http.routers.matrix-wellknown.rule=Host(`${MATRIX_DOMAIN}`) && PathPrefix(`/.well-known/matrix`)"
|
||||
|
||||
# ======================
|
||||
# Element Web (Matrix Client)
|
||||
# ======================
|
||||
element-web:
|
||||
image: vectorim/element-web:${ELEMENT_IMAGE_TAG:-latest}
|
||||
volumes:
|
||||
- element-config:/app/config
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.element.rule=Host(`${ELEMENT_DOMAIN}`)"
|
||||
- "traefik.http.routers.element.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.element.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.routers.element.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
- "traefik.http.services.element.loadbalancer.server.port=80"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
|
||||
# ======================
|
||||
# PostgreSQL (Synapse Database)
|
||||
# ======================
|
||||
# Separate from Mosaic's PostgreSQL — Synapse manages its own schema.
|
||||
# If you prefer a shared PostgreSQL instance, remove this service and
|
||||
# point POSTGRES_HOST to your existing PostgreSQL with a separate database.
|
||||
synapse-postgres:
|
||||
image: postgres:${POSTGRES_IMAGE_TAG:-16-alpine}
|
||||
environment:
|
||||
POSTGRES_DB: ${SYNAPSE_POSTGRES_DB:-synapse}
|
||||
POSTGRES_USER: ${SYNAPSE_POSTGRES_USER:-synapse}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
||||
volumes:
|
||||
- synapse-postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"pg_isready -U ${SYNAPSE_POSTGRES_USER:-synapse} -d ${SYNAPSE_POSTGRES_DB:-synapse}",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- internal
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
# ======================
|
||||
# coturn (TURN/STUN for VoIP) - Optional
|
||||
# ======================
|
||||
# Uncomment if you need voice/video calls through NAT.
|
||||
# Requires additional DNS and port configuration.
|
||||
#
|
||||
# coturn:
|
||||
# image: coturn/coturn:latest
|
||||
# environment:
|
||||
# TURN_REALM: ${MATRIX_DOMAIN}
|
||||
# TURN_SECRET: ${COTURN_SECRET}
|
||||
# ports:
|
||||
# - "3478:3478/tcp"
|
||||
# - "3478:3478/udp"
|
||||
# - "5349:5349/tcp"
|
||||
# - "5349:5349/udp"
|
||||
# - "49152-49200:49152-49200/udp"
|
||||
# networks:
|
||||
# - internal
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
|
||||
volumes:
|
||||
synapse-data:
|
||||
synapse-media:
|
||||
synapse-postgres-data:
|
||||
element-config:
|
||||
|
||||
networks:
|
||||
internal:
|
||||
driver: overlay
|
||||
traefik-public:
|
||||
external: true
|
||||
name: ${TRAEFIK_DOCKER_NETWORK:-traefik-public}
|
||||
@@ -1,164 +0,0 @@
|
||||
# ==============================================
|
||||
# Speech Services - Sample Swarm Deployment
|
||||
# ==============================================
|
||||
#
|
||||
# Standalone speech services deployment for use with Mosaic Stack.
|
||||
# This is SEPARATE infrastructure — not part of the Mosaic Stack itself.
|
||||
# Mosaic connects to it via SPEACHES_URL and TTS_URL environment variables.
|
||||
#
|
||||
# Provides:
|
||||
# - Speaches: Speech-to-Text (Whisper) + basic TTS fallback
|
||||
# - Kokoro TTS: Default high-quality text-to-speech
|
||||
# - Chatterbox TTS: Premium TTS with voice cloning (optional, requires GPU)
|
||||
#
|
||||
# Usage (Docker Swarm via Portainer):
|
||||
# 1. Create a new stack in Portainer
|
||||
# 2. Paste this file or point to the repo
|
||||
# 3. Set environment variables in Portainer's env var section
|
||||
# 4. Deploy the stack
|
||||
#
|
||||
# Usage (Docker Swarm CLI):
|
||||
# 1. Create .env file with variables below
|
||||
# 2. docker stack deploy -c docker-compose.sample.speech.yml speech
|
||||
#
|
||||
# Required Environment Variables:
|
||||
# STT_DOMAIN=stt.example.com # Domain for Speaches (STT + basic TTS)
|
||||
# TTS_DOMAIN=tts.example.com # Domain for Kokoro TTS (default TTS)
|
||||
#
|
||||
# Optional Environment Variables:
|
||||
# WHISPER_MODEL=Systran/faster-whisper-large-v3-turbo # Whisper model for STT
|
||||
# CHATTERBOX_TTS_DOMAIN=tts-premium.example.com # Domain for Chatterbox (premium TTS)
|
||||
# TRAEFIK_ENTRYPOINT=websecure # Traefik entrypoint name
|
||||
# TRAEFIK_CERTRESOLVER=letsencrypt # Traefik cert resolver
|
||||
# TRAEFIK_DOCKER_NETWORK=traefik-public # Traefik network name
|
||||
# TRAEFIK_TLS_ENABLED=true # Enable TLS on Traefik routers
|
||||
#
|
||||
# Connecting to Mosaic Stack:
|
||||
# Add to your Mosaic Stack .env:
|
||||
# SPEACHES_URL=http://speaches:8000 (if same Docker network)
|
||||
# SPEACHES_URL=https://stt.example.com (if external)
|
||||
# TTS_URL=http://kokoro-tts:8880 (if same Docker network)
|
||||
# TTS_URL=https://tts.example.com (if external)
|
||||
#
|
||||
# GPU Requirements (Chatterbox only):
|
||||
# - NVIDIA GPU with CUDA support
|
||||
# - nvidia-container-toolkit installed on Docker host
|
||||
# - Docker runtime configured for GPU access
|
||||
# - Note: Docker Swarm requires "generic resources" for GPU scheduling.
|
||||
# See: https://docs.docker.com/engine/daemon/nvidia-gpu/#configure-gpus-for-docker-swarm
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
services:
|
||||
# ======================
|
||||
# Speaches (STT + basic TTS)
|
||||
# ======================
|
||||
# Primary speech-to-text service using Whisper.
|
||||
# Also provides basic TTS as a fallback.
|
||||
speaches:
|
||||
image: ghcr.io/speaches-ai/speaches:latest
|
||||
environment:
|
||||
WHISPER__MODEL: ${WHISPER_MODEL:-Systran/faster-whisper-large-v3-turbo}
|
||||
volumes:
|
||||
- speaches-models:/root/.cache/huggingface
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 120s
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 10s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.speech-stt.rule=Host(`${STT_DOMAIN}`)"
|
||||
- "traefik.http.routers.speech-stt.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.speech-stt.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.routers.speech-stt.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
- "traefik.http.services.speech-stt.loadbalancer.server.port=8000"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
|
||||
# ======================
|
||||
# Kokoro TTS (Default TTS)
|
||||
# ======================
|
||||
# High-quality text-to-speech engine. Always deployed alongside Speaches.
|
||||
kokoro-tts:
|
||||
image: ghcr.io/remsky/kokoro-fastapi:latest-cpu
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8880/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 120s
|
||||
networks:
|
||||
- internal
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 10s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.speech-tts.rule=Host(`${TTS_DOMAIN}`)"
|
||||
- "traefik.http.routers.speech-tts.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
- "traefik.http.routers.speech-tts.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
- "traefik.http.routers.speech-tts.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
- "traefik.http.services.speech-tts.loadbalancer.server.port=8880"
|
||||
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
|
||||
# ======================
|
||||
# Chatterbox TTS (Premium TTS - Optional)
|
||||
# ======================
|
||||
# Premium TTS with voice cloning capabilities. Requires NVIDIA GPU.
|
||||
#
|
||||
# To enable: Uncomment this service and set CHATTERBOX_TTS_DOMAIN.
|
||||
#
|
||||
# For Docker Swarm GPU scheduling, configure generic resources on the node:
|
||||
# /etc/docker/daemon.json:
|
||||
# { "runtimes": { "nvidia": { ... } },
|
||||
# "node-generic-resources": ["NVIDIA-GPU=0"] }
|
||||
#
|
||||
# chatterbox-tts:
|
||||
# image: devnen/chatterbox-tts-server:latest
|
||||
# healthcheck:
|
||||
# test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
|
||||
# interval: 30s
|
||||
# timeout: 10s
|
||||
# retries: 5
|
||||
# start_period: 180s
|
||||
# networks:
|
||||
# - internal
|
||||
# - traefik-public
|
||||
# deploy:
|
||||
# restart_policy:
|
||||
# condition: on-failure
|
||||
# delay: 10s
|
||||
# resources:
|
||||
# reservations:
|
||||
# generic_resources:
|
||||
# - discrete_resource_spec:
|
||||
# kind: "NVIDIA-GPU"
|
||||
# value: 1
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.http.routers.speech-tts-premium.rule=Host(`${CHATTERBOX_TTS_DOMAIN}`)"
|
||||
# - "traefik.http.routers.speech-tts-premium.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||
# - "traefik.http.routers.speech-tts-premium.tls=${TRAEFIK_TLS_ENABLED:-true}"
|
||||
# - "traefik.http.routers.speech-tts-premium.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
|
||||
# - "traefik.http.services.speech-tts-premium.loadbalancer.server.port=8000"
|
||||
# - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
|
||||
|
||||
volumes:
|
||||
speaches-models:
|
||||
|
||||
networks:
|
||||
internal:
|
||||
driver: overlay
|
||||
traefik-public:
|
||||
external: true
|
||||
name: ${TRAEFIK_DOCKER_NETWORK:-traefik-public}
|
||||
@@ -1,30 +1,34 @@
|
||||
{
|
||||
"default_server_config": {
|
||||
"m.homeserver": {
|
||||
"base_url": "http://localhost:8008",
|
||||
"server_name": "localhost"
|
||||
"base_url": "https://matrix.mosaicstack.dev",
|
||||
"server_name": "matrix.mosaicstack.dev"
|
||||
},
|
||||
"m.identity_server": {
|
||||
"base_url": "https://vector.im"
|
||||
}
|
||||
},
|
||||
"brand": "Mosaic Stack Dev",
|
||||
"brand": "Mosaic Chat",
|
||||
"integrations_ui_url": "https://scalar.vector.im/",
|
||||
"integrations_rest_url": "https://scalar.vector.im/api",
|
||||
"integrations_widgets_urls": [
|
||||
"https://scalar.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar.vector.im/api",
|
||||
"https://scalar-staging.vector.im/_matrix/integrations/v1",
|
||||
"https://scalar-staging.vector.im/api",
|
||||
"https://scalar-staging.riot.im/scalar/api"
|
||||
],
|
||||
"disable_custom_urls": false,
|
||||
"disable_guests": true,
|
||||
"disable_login_language_selector": false,
|
||||
"disable_3pid_login": false,
|
||||
"default_country_code": "US",
|
||||
"show_labs_settings": false,
|
||||
"default_theme": "dark",
|
||||
"room_directory": {
|
||||
"servers": ["localhost"]
|
||||
"servers": ["matrix.mosaicstack.dev"]
|
||||
},
|
||||
"features": {
|
||||
"feature_video_rooms": false,
|
||||
"feature_group_calls": false
|
||||
},
|
||||
"show_labs_settings": true,
|
||||
"piwik": false,
|
||||
"posthog": {
|
||||
"enabled": false
|
||||
},
|
||||
"privacy_policy_url": null,
|
||||
"terms_and_conditions_links": [],
|
||||
"setting_defaults": {
|
||||
"breadcrumbs": true,
|
||||
"custom_themes": []
|
||||
},
|
||||
"disable_guests": true,
|
||||
"disable_3pid_login": true
|
||||
"breadcrumbs": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
# ==============================================
|
||||
# Synapse Homeserver Configuration — Development Only
|
||||
# Synapse Homeserver Configuration — Production
|
||||
# ==============================================
|
||||
#
|
||||
# This config is for LOCAL DEVELOPMENT with the Mosaic Stack docker-compose overlay.
|
||||
# Do NOT use this in production. See docker-compose.sample.matrix.yml for production.
|
||||
# Deploy to /opt/mosaic/synapse/homeserver.yaml on the Docker host.
|
||||
#
|
||||
# Server name is set to 'localhost' — this is permanent and cannot be changed
|
||||
# after the database has been initialized.
|
||||
# IMPORTANT: server_name is PERMANENT. It becomes part of every user ID
|
||||
# (@user:server_name) and room alias. It cannot be changed after the
|
||||
# database has been initialized without losing all data.
|
||||
#
|
||||
# Before first deploy, replace ALL placeholders marked REPLACE_*.
|
||||
#
|
||||
# ==============================================
|
||||
|
||||
server_name: "localhost"
|
||||
# REPLACE with your Matrix domain (e.g. matrix.mosaicstack.dev)
|
||||
# This is permanent — cannot be changed after first startup.
|
||||
server_name: "REPLACE_MATRIX_DOMAIN"
|
||||
pid_file: /data/homeserver.pid
|
||||
public_baseurl: "http://localhost:8008/"
|
||||
public_baseurl: "https://REPLACE_MATRIX_DOMAIN/"
|
||||
|
||||
# ======================
|
||||
# Network Listeners
|
||||
# ======================
|
||||
listeners:
|
||||
# Client API (used by Element Web, Mosaic bridge, etc.)
|
||||
- port: 8008
|
||||
tls: false
|
||||
type: http
|
||||
# Traefik terminates TLS and forwards via X-Forwarded-For
|
||||
x_forwarded: true
|
||||
bind_addresses: ["0.0.0.0"]
|
||||
resources:
|
||||
@@ -35,9 +39,11 @@ database:
|
||||
name: psycopg2
|
||||
txn_limit: 10000
|
||||
args:
|
||||
user: "synapse"
|
||||
password: "synapse_dev_password"
|
||||
database: "synapse"
|
||||
# Must match SYNAPSE_POSTGRES_USER / SYNAPSE_POSTGRES_PASSWORD
|
||||
# from your Portainer environment variables
|
||||
user: "REPLACE_SYNAPSE_DB_USER"
|
||||
password: "REPLACE_SYNAPSE_DB_PASSWORD"
|
||||
database: "REPLACE_SYNAPSE_DB_NAME"
|
||||
host: "postgres"
|
||||
port: 5432
|
||||
cp_min: 5
|
||||
@@ -66,20 +72,25 @@ url_preview_ip_range_blacklist:
|
||||
- "fec0::/10"
|
||||
|
||||
# ======================
|
||||
# Registration (Dev Only)
|
||||
# Registration
|
||||
# ======================
|
||||
enable_registration: true
|
||||
enable_registration_without_verification: true
|
||||
# Public registration disabled. Create accounts via the admin API or CLI:
|
||||
# docker exec -it <container> register_new_matrix_user \
|
||||
# -u username -c /data/homeserver.yaml http://localhost:8008
|
||||
enable_registration: false
|
||||
|
||||
# ======================
|
||||
# Signing Keys
|
||||
# ======================
|
||||
# Auto-generated on first startup and persisted in the signing_key volume
|
||||
signing_key_path: "/data/keys/localhost.signing.key"
|
||||
# Auto-generated on first startup and persisted in /opt/mosaic/synapse/keys/
|
||||
signing_key_path: "/data/keys/signing.key"
|
||||
|
||||
# Suppress warning about trusted key servers in dev
|
||||
suppress_key_server_warning: true
|
||||
trusted_key_servers: []
|
||||
# ======================
|
||||
# Trusted Key Servers
|
||||
# ======================
|
||||
# matrix.org is the default. Set to [] to disable federation key trust.
|
||||
trusted_key_servers:
|
||||
- server_name: "matrix.org"
|
||||
|
||||
# ======================
|
||||
# Room Configuration
|
||||
@@ -88,44 +99,46 @@ enable_room_list_search: true
|
||||
allow_public_rooms_over_federation: false
|
||||
|
||||
# ======================
|
||||
# Rate Limiting (Relaxed for Dev)
|
||||
# Rate Limiting
|
||||
# ======================
|
||||
rc_message:
|
||||
per_second: 100
|
||||
burst_count: 200
|
||||
|
||||
rc_registration:
|
||||
per_second: 10
|
||||
burst_count: 50
|
||||
|
||||
rc_registration:
|
||||
per_second: 1
|
||||
burst_count: 5
|
||||
|
||||
rc_login:
|
||||
address:
|
||||
per_second: 10
|
||||
burst_count: 50
|
||||
per_second: 3
|
||||
burst_count: 10
|
||||
account:
|
||||
per_second: 10
|
||||
burst_count: 50
|
||||
per_second: 3
|
||||
burst_count: 10
|
||||
|
||||
# ======================
|
||||
# Logging
|
||||
# ======================
|
||||
log_config: "/data/localhost.log.config"
|
||||
|
||||
# Inline log config — write to stdout for docker logs
|
||||
# Synapse falls back to a basic console logger if the log_config file is missing,
|
||||
# so we leave log_config pointing to a non-existent file intentionally.
|
||||
# Override: mount a custom log config file at /data/localhost.log.config
|
||||
# Synapse falls back to a basic console logger (stdout) when this file
|
||||
# does not exist, which is ideal for Docker log collection.
|
||||
log_config: "/data/log.config"
|
||||
|
||||
# ======================
|
||||
# Miscellaneous
|
||||
# Secrets
|
||||
# ======================
|
||||
# Generate with: python3 -c 'import secrets; print(secrets.token_hex(32))'
|
||||
report_stats: false
|
||||
macaroon_secret_key: "dev-macaroon-secret-change-in-production"
|
||||
form_secret: "dev-form-secret-change-in-production"
|
||||
macaroon_secret_key: "REPLACE_MACAROON_SECRET"
|
||||
form_secret: "REPLACE_FORM_SECRET"
|
||||
|
||||
# Enable presence for dev
|
||||
# ======================
|
||||
# Presence & Retention
|
||||
# ======================
|
||||
use_presence: true
|
||||
|
||||
# Retention policy (optional, keep messages for 180 days in dev)
|
||||
retention:
|
||||
enabled: false
|
||||
enabled: true
|
||||
default_policy:
|
||||
min_lifetime: 1d
|
||||
max_lifetime: 365d
|
||||
|
||||
@@ -11,9 +11,6 @@ listener "tcp" {
|
||||
tls_disable = 1
|
||||
}
|
||||
|
||||
# Disable memory locking for Docker compatibility
|
||||
disable_mlock = true
|
||||
|
||||
# API address for cluster communication
|
||||
api_addr = "http://0.0.0.0:8200"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user