MicProtocol / docker-compose.yml
Benaah's picture
Upload folder using huggingface_hub
b17baae verified
Raw History Blame Contribute Delete
32.4 kB
# ═══════════════════════════════════════════════════════════
# MicProtocol - Full Stack Docker Compose
# Production-ready orchestration
# ═══════════════════════════════════════════════════════════
version: "3.8"
services:
# ─── PostgreSQL Database (pgvector β€” required by Chatwoot + Formbricks)
postgres:
image: pgvector/pgvector:pg16
container_name: micprotocol-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-micprotocol}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-micprotocol_secret}
POSTGRES_DB: ${POSTGRES_DB:-micprotocol}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-databases.sql:/docker-entrypoint-initdb.d/init-databases.sql
ports:
- "5432:5432"
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-micprotocol}" ]
interval: 10s
timeout: 5s
retries: 5
networks:
- micprotocol-network
# ─── Redis (Caching & Job Queue) ─────────────────────────
redis:
image: redis:7-alpine
container_name: micprotocol-redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD:-redis_secret}
volumes:
- redis_data:/data
ports:
- "6379:6379"
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 3s
retries: 5
networks:
- micprotocol-network
# ─── Meilisearch (Search Engine) ─────────────────────────
meilisearch:
image: getmeili/meilisearch:v1.6
container_name: micprotocol-meilisearch
restart: unless-stopped
environment:
MEILI_MASTER_KEY: ${MEILISEARCH_API_KEY:-masterKey}
MEILI_NO_ANALYTICS: "true"
volumes:
- meilisearch_data:/meili_data
ports:
- "7700:7700"
networks:
- micprotocol-network
# ─── API Server ──────────────────────────────────────────
api:
build:
context: .
dockerfile: apps/api/Dockerfile
container_name: micprotocol-api
restart: unless-stopped
environment:
NODE_ENV: production
API_PORT: 4000
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/${POSTGRES_DB:-micprotocol}?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis_secret}
MEILISEARCH_HOST: http://meilisearch:7700
CLERK_SECRET_KEY: ${CLERK_SECRET_KEY}
CLERK_PUBLISHABLE_KEY: ${CLERK_PUBLISHABLE_KEY}
WEB_URL: ${WEB_URL:-http://localhost:3000}
API_URL: ${API_URL:-http://localhost:4000}
MIC_REALTIME_URL: http://mic-realtime:4001
MIC_REALTIME_INTERNAL_TOKEN: ${MIC_REALTIME_INTERNAL_TOKEN}
INTERNAL_API_TOKEN: ${INTERNAL_API_TOKEN:-${MIC_REALTIME_INTERNAL_TOKEN}}
MIC_REALTIME_SHARED_SECRET: ${MIC_REALTIME_SHARED_SECRET}
COOKIE_SECRET: ${COOKIE_SECRET:-cookie_secret_change_in_production}
# Email
RESEND_API_KEY: ${RESEND_API_KEY}
# Storage
R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID}
R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY}
R2_ENDPOINT: ${R2_ENDPOINT}
R2_BUCKET: ${R2_BUCKET}
# AI
NVIDIA_NIM_API_KEY: ${NVIDIA_NIM_API_KEY}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY}
MOONSHOT_API_KEY: ${MOONSHOT_API_KEY}
# Self-hosted integration URLs (Docker network)
CALCOM_URL: http://calcom:3000
CHATWOOT_URL: http://chatwoot-web:3000
DOCUMENSO_URL: http://documenso:3000
FORMBRICKS_URL: http://formbricks:3000
LISTMONK_URL: http://listmonk:9000
UMAMI_URL: http://umami:3000
LIVEKIT_HOST: http://livekit:7880
LIVEKIT_URL: http://livekit:7880
PRETIX_URL: http://pretix:8000
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
ports:
- "4000:4000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: [ "CMD", "wget", "-q", "--spider", "http://localhost:4000/health" ]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- micprotocol-network
deploy:
resources:
limits:
cpus: "2"
memory: 2G
reservations:
cpus: "0.5"
memory: 512M
# ─── Background Workers ──────────────────────────────────
# Same image/entrypoint as `api`, published without ports: a plain
# second instance of the app whose role is queue processing. main.js
# registers the BullMQ WorkerHosts, including the clip-scoring
# processor which spawns the Python pipeline via execFile β€” see
# apps/api/src/queue/clip-scoring.processor.ts. The Python ML stack
# baked into this image is what actually serves CLIP scoring. The
# previous `npx bullmq` command never worked (the package ships no
# CLI).
worker:
build:
context: .
dockerfile: apps/api/Dockerfile
container_name: micprotocol-worker
restart: unless-stopped
command: node /app/apps/api/dist/main.js
ipc: host
environment:
NODE_ENV: production
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/${POSTGRES_DB:-micprotocol}?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis_secret}
MEILISEARCH_HOST: http://meilisearch:7700
MEILISEARCH_URL: http://meilisearch:7700
NVIDIA_NIM_API_KEY: ${NVIDIA_NIM_API_KEY}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY}
MOONSHOT_API_KEY: ${MOONSHOT_API_KEY}
RESEND_API_KEY: ${RESEND_API_KEY}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- micprotocol-network
deploy:
resources:
limits:
# Python CLIP/BART inference runs inside this container; the
# 1G limit starved torch and got OOM-killed mid-scoring.
cpus: "2"
memory: 2G
# ─── Erlang/OTP Real-time Collaboration Engine ─────────
# Owns chat fanout, presence, live domain events. The NestJS API
# publishes into it via /internal/dispatch; the web client opens
# a WebSocket at /ws?token=... to subscribe to conversations.
mic-realtime:
build:
context: ./services/mic-realtime
dockerfile: Dockerfile
container_name: micprotocol-mic-realtime
restart: unless-stopped
environment:
MIC_REALTIME_PORT: 4001
MIC_REALTIME_HOST: 0.0.0.0
MICPROTOCOL_API_URL: ${API_URL:-http://api:4000}
# No insecure compose fallbacks here on purpose: mic_config.erl
# fails closed on the known-insecure default strings, so a
# deployment without strong .env values crashes at boot with an
# explicit error instead of silently running with default creds.
MIC_REALTIME_INTERNAL_TOKEN: ${MIC_REALTIME_INTERNAL_TOKEN}
MIC_REALTIME_SHARED_SECRET: ${MIC_REALTIME_SHARED_SECRET}
MIC_REALTIME_PUBLIC_URL: ${MIC_REALTIME_PUBLIC_URL:-ws://localhost:4001}
MIC_REALTIME_MAX_MSG: 65536
MIC_REALTIME_RATE_LIMIT: 120
MIC_REALTIME_PRESENCE_TTL: 90
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis_secret}
ports:
- "4001:4001"
depends_on:
api:
condition: service_started
redis:
condition: service_healthy
networks:
- micprotocol-network
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:4001/healthz"]
interval: 15s
timeout: 5s
retries: 5
deploy:
resources:
limits:
memory: 1G
cpus: "1.5"
# ─── Next.js Web App ─────────────────────────────────────
web:
build:
context: .
dockerfile: apps/web/Dockerfile
container_name: micprotocol-web
restart: unless-stopped
environment:
NODE_ENV: production
NEXT_PUBLIC_API_URL: ${API_URL:-http://localhost:4000}
NEXT_PUBLIC_MIC_REALTIME_URL: ${MIC_REALTIME_PUBLIC_URL:-ws://localhost:4001}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${CLERK_PUBLISHABLE_KEY:-pk_test_cGVuZGluZy1tYWNhdy05OC5jbGVyay5hY2NvdW50cy5kZXYk}
CLERK_SECRET_KEY: ${CLERK_SECRET_KEY:-sk_test_cGVuZGluZy1tYWNhdy05OC5jbGVyay5hY2NvdW50cy5kZXYk}
DEV_AUTH_BYPASS_ENABLED: ${DEV_AUTH_BYPASS_ENABLED:-true}
ports:
- "3000:3000"
depends_on:
- api
- mic-realtime
networks:
- micprotocol-network
# ─── Admin Web App ───────────────────────────────────────
admin:
build:
context: .
dockerfile: apps/admin/Dockerfile
container_name: micprotocol-admin
restart: unless-stopped
environment:
NODE_ENV: production
API_URL: ${API_URL:-http://api:4000}
NEXT_PUBLIC_API_URL: ${API_URL:-http://api:4000}
NEXT_PUBLIC_APP_URL: ${WEB_URL:-http://localhost:3000}
ports:
- "3006:3006"
depends_on:
- api
networks:
- micprotocol-network
# ═══════════════════════════════════════════════════════════
# Self-Hosted Integrations (Open-Source SaaS Replacements)
# ═══════════════════════════════════════════════════════════
# Each service shares the central Postgres + Redis infra above
# (separate DB/schema per service) to keep ops simple.
# Port map: 3001=formbricks 3002=calcom 3003=umami 3004=documenso
# 3005=chatwoot 3006=admin 9000=listmonk
# 9001=minio-s3 9090=minio-console
# ─── Cal.com (Scheduling) ────────────────────────────────
calcom:
image: calcom/cal.com:latest
container_name: micprotocol-calcom
restart: unless-stopped
ports:
- "3002:3000"
environment:
# Bind to all interfaces so other containers can reach Cal.com
HOSTNAME: "0.0.0.0"
# Cal.com requires its own database (Prisma migrations need a clean schema).
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/calcom?schema=public
DATABASE_DIRECT_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/calcom?schema=public
NEXTAUTH_SECRET: ${CALCOM_NEXTAUTH_SECRET}
CALENDSO_ENCRYPTION_KEY: ${CALCOM_ENCRYPTION_KEY}
NEXTAUTH_URL: ${CALCOM_URL:-http://localhost:3002}
NEXT_PUBLIC_WEBAPP_URL: ${CALCOM_URL:-http://localhost:3002}
NEXT_PUBLIC_API_V2_URL: ${CALCOM_API_URL:-http://localhost:3002/api/v2}
# Telemetry off for self-hosted
CALCOM_TELEMETRY_DISABLED: "1"
# Email
EMAIL_FROM: ${EMAIL_FROM:-noreply@micprotocol.local}
EMAIL_SERVER_HOST: ${SMTP_HOST:-mailhog}
EMAIL_SERVER_PORT: ${SMTP_PORT:-1025}
EMAIL_SERVER_USER: ${SMTP_USER:-mailhog_user}
EMAIL_SERVER_PASSWORD: ${SMTP_PASSWORD:-mailhog_password}
# S3 storage (MinIO) for Cal.com uploads
S3_ACCESS_KEY: ${MINIO_ROOT_USER:-micprotocol_admin}
S3_SECRET_KEY: ${MINIO_ROOT_PASSWORD:-micprotocol_secret}
S3_REGION: us-east-1
S3_BUCKET: calcom-uploads
S3_ENDPOINT: http://minio:9000
S3_FORCE_PATH_STYLE: "true"
# Redis for Cal.com v2 API/background jobs
REDIS_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
# Disable paid org features
NEXT_PUBLIC_SINGLE_ORG_SLUG: ${CALCOM_SINGLE_ORG_SLUG:-micprotocol}
ORGANIZATIONS_ENABLED: "0"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 40s
networks:
- micprotocol-network
# ─── Listmonk (Newsletter / Email Marketing) ─────────────
listmonk:
image: listmonk/listmonk:latest
container_name: micprotocol-listmonk
restart: unless-stopped
ports:
- "9000:9000"
# Run install + upgrade + serve on first boot. Idempotent.
command: >
sh -c "./listmonk --install --idempotent --yes --config '' &&
./listmonk --upgrade --yes --config '' &&
./listmonk --config ''"
environment:
LISTMONK_app__address: "0.0.0.0:9000"
LISTMONK_db__host: postgres
LISTMONK_db__port: 5432
LISTMONK_db__user: ${POSTGRES_USER:-micprotocol}
LISTMONK_db__password: ${POSTGRES_PASSWORD:-micprotocol_secret}
LISTMONK_db__database: listmonk
LISTMONK_db__ssl_mode: disable
LISTMONK_db__max_open: 25
LISTMONK_db__max_idle: 25
LISTMONK_db__max_lifetime: 300s
# Optional: bootstrap super-admin on first run
LISTMONK_ADMIN_USER: ${LISTMONK_ADMIN_USER:-admin}
LISTMONK_ADMIN_PASSWORD: ${LISTMONK_ADMIN_PASSWORD:-admin_secret}
TZ: Etc/UTC
volumes:
- listmonk_uploads:/listmonk/uploads:rw
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9000/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
networks:
- micprotocol-network
# ─── Formbricks (Surveys / Customer Feedback) ────────────
# v4 architecture: web app + Hub API + Cube (analytics) + migrate job
formbricks-migrate:
image: ghcr.io/formbricks/formbricks:latest
container_name: micprotocol-formbricks-migrate
restart: "no"
entrypoint: ["sh", "-c"]
command:
[
'node packages/database/dist/scripts/apply-migrations.js',
]
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/formbricks?schema=public
NEXTAUTH_SECRET: ${FORMBRICKS_NEXTAUTH_SECRET}
ENCRYPTION_KEY: ${FORMBRICKS_ENCRYPTION_KEY}
depends_on:
postgres:
condition: service_healthy
networks:
- micprotocol-network
formbricks:
image: ghcr.io/formbricks/formbricks:latest
container_name: micprotocol-formbricks
restart: unless-stopped
ports:
- "3001:3000"
environment:
# Bind to all interfaces so other containers can reach Formbricks
HOSTNAME: "0.0.0.0"
WEBAPP_URL: ${FORMBRICKS_URL:-http://localhost:3001}
NEXTAUTH_URL: ${FORMBRICKS_URL:-http://localhost:3001}
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/formbricks?schema=public
NEXTAUTH_SECRET: ${FORMBRICKS_NEXTAUTH_SECRET}
ENCRYPTION_KEY: ${FORMBRICKS_ENCRYPTION_KEY}
CRON_SECRET: ${FORMBRICKS_CRON_SECRET}
REDIS_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
# Skip in-container migrate (handled by formbricks-migrate above)
SKIP_STARTUP_MIGRATION: "true"
# Hub/Cube wiring
HUB_API_URL: http://formbricks-hub:8080
HUB_API_KEY: ${FORMBRICKS_HUB_API_KEY}
CUBEJS_API_URL: http://formbricks-cube:4000
CUBEJS_API_SECRET: ${FORMBRICKS_CUBE_API_SECRET}
CUBEJS_JWT_ISSUER: formbricks-web
CUBEJS_JWT_AUDIENCE: formbricks-cube
# Auth defaults (signup disabled without invite)
EMAIL_VERIFICATION_DISABLED: 1
PASSWORD_RESET_DISABLED: 1
# Email
MAIL_FROM: ${EMAIL_FROM:-noreply@micprotocol.local}
SMTP_HOST: ${SMTP_HOST:-mailhog}
SMTP_PORT: ${SMTP_PORT:-1025}
SMTP_USER: ${SMTP_USER:-mailhog_user}
SMTP_PASSWORD: ${SMTP_PASSWORD:-mailhog_password}
# Telemetry
TELEMETRY_DISABLED: 1
RATE_LIMITING_DISABLED: 0
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
formbricks-migrate:
condition: service_completed_successfully
formbricks-cube:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 40s
networks:
- micprotocol-network
formbricks-hub-migrate:
image: ghcr.io/formbricks/hub:latest
container_name: micprotocol-formbricks-hub-migrate
restart: "no"
entrypoint: ["sh", "-c"]
command:
[
'/usr/local/bin/goose -dir /app/migrations postgres "$$DATABASE_URL" up && /usr/local/bin/river migrate-up --database-url "$$DATABASE_URL"',
]
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/formbricks?schema=public
depends_on:
formbricks-migrate:
condition: service_completed_successfully
networks:
- micprotocol-network
formbricks-hub:
image: ghcr.io/formbricks/hub:latest
container_name: micprotocol-formbricks-hub
restart: unless-stopped
environment:
API_KEY: ${FORMBRICKS_HUB_API_KEY}
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/formbricks?schema=public
depends_on:
formbricks-hub-migrate:
condition: service_completed_successfully
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
networks:
- micprotocol-network
formbricks-cube:
image: cubejs/cube:v1.6.6
container_name: micprotocol-formbricks-cube
restart: unless-stopped
command: ["sh", "-c", "exec cubejs server"]
environment:
CUBEJS_DB_TYPE: postgres
CUBEJS_DB_HOST: postgres
CUBEJS_DB_NAME: formbricks
CUBEJS_DB_USER: ${POSTGRES_USER:-micprotocol}
CUBEJS_DB_PASS: ${POSTGRES_PASSWORD:-micprotocol_secret}
CUBEJS_DB_PORT: 5432
CUBEJS_API_SECRET: ${FORMBRICKS_CUBE_API_SECRET}
CUBEJS_JWT_ISSUER: formbricks-web
CUBEJS_JWT_AUDIENCE: formbricks-cube
CUBEJS_DEFAULT_API_SCOPES: meta,data
CUBEJS_CACHE_AND_QUEUE_DRIVER: memory
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:4000/readyz',(r)=>{r.resume();process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
networks:
- micprotocol-network
# ─── Documenso (Document e-signing) ──────────────────────
# Requires a signing cert. Generate via instructions in .env.example
documenso:
image: documenso/documenso:latest
container_name: micprotocol-documenso
restart: unless-stopped
ports:
- "3004:3000"
environment:
# Bind to all interfaces so other containers can reach Documenso
HOSTNAME: "0.0.0.0"
PORT: 3000
NEXTAUTH_SECRET: ${DOCUMENSO_NEXTAUTH_SECRET}
NEXT_PRIVATE_ENCRYPTION_KEY: ${DOCUMENSO_ENCRYPTION_KEY}
NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY: ${DOCUMENSO_ENCRYPTION_SECONDARY_KEY}
NEXT_PUBLIC_WEBAPP_URL: ${DOCUMENSO_URL:-http://localhost:3004}
NEXT_PRIVATE_INTERNAL_WEBAPP_URL: http://documenso:3000
NEXT_PRIVATE_DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/documenso?schema=public
NEXT_PRIVATE_DIRECT_DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/documenso?schema=public
# Uploads via MinIO (S3-compatible)
NEXT_PUBLIC_UPLOAD_TRANSPORT: s3
NEXT_PRIVATE_UPLOAD_ENDPOINT: http://minio:9000
NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE: "true"
NEXT_PRIVATE_UPLOAD_REGION: us-east-1
NEXT_PRIVATE_UPLOAD_BUCKET: documenso-uploads
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID: ${MINIO_ROOT_USER:-micprotocol_admin}
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:-micprotocol_secret}
# SMTP
NEXT_PRIVATE_SMTP_TRANSPORT: smtp
NEXT_PRIVATE_SMTP_HOST: ${SMTP_HOST:-mailhog}
NEXT_PRIVATE_SMTP_PORT: ${SMTP_PORT:-1025}
NEXT_PRIVATE_SMTP_SECURE: "false"
NEXT_PRIVATE_SMTP_FROM_NAME: MicProtocol
NEXT_PRIVATE_SMTP_FROM_ADDRESS: ${EMAIL_FROM:-noreply@micprotocol.local}
# Signing cert (mounted read-only below, generated by host per .env.example)
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH: /opt/documenso/cert.p12
NEXT_PRIVATE_SIGNING_PASSPHRASE: ${DOCUMENSO_SIGNING_PASSPHRASE}
# Disable paid signup gates for self-hosted
NEXT_PUBLIC_DISABLE_SIGNIN: "false"
NEXT_PUBLIC_DISABLE_SIGNUP: "false"
volumes:
- ./scripts/documenso-cert:/opt/documenso/cert:ro
depends_on:
postgres:
condition: service_healthy
networks:
- micprotocol-network
# ─── Chatwoot (Customer Support / Live Chat) ────────────
# Rails + Sidekiq workers, palette requested separately to keep
# things simple by default.
chatwoot-web:
image: chatwoot/chatwoot:latest
container_name: micprotocol-chatwoot-web
restart: unless-stopped
ports:
- "3005:3000"
entrypoint: docker/entrypoints/rails.sh
command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"]
environment:
NODE_ENV: production
RAILS_ENV: production
INSTALLATION_ENV: docker
SECRET_KEY_BASE: ${CHATWOOT_SECRET_KEY_BASE}
RAILS_LOG_TO_STDOUT: "true"
# Database β€” chatwoot needs pgvector; see init-databases.sql
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DATABASE: chatwoot
POSTGRES_USERNAME: ${POSTGRES_USER:-micprotocol}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-micprotocol_secret}
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/chatwoot?sslmode=disable
# Redis (shared instance, namespaced by Chatwoot internally)
REDIS_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis_secret}
REDIS_SIDEKIQ_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
# Frontend URL
FRONTEND_URL: ${CHATWOOT_URL:-http://localhost:3005}
# SMTP for outbound mail
SMTP_ADDRESS: ${SMTP_HOST:-mailhog}
SMTP_PORT: ${SMTP_PORT:-1025}
SMTP_USERNAME: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_DOMAIN: ${SMTP_DOMAIN:-micprotocol.local}
MAILER_SENDER_EMAIL: ${EMAIL_FROM:-Chatwoot <support@micprotocol.local>}
# Storage
ACTIVE_STORAGE_SERVICE: local
RAILS_STORAGE_LOCATION: /app/storage
# Disable dev-mode features
ENABLE_ACCOUNT_GAME: "false"
USE_INBOUND_EMAIL_SERVICE: "false"
volumes:
- chatwoot_storage:/app/storage
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- micprotocol-network
chatwoot-worker:
image: chatwoot/chatwoot:latest
container_name: micprotocol-chatwoot-worker
restart: unless-stopped
command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]
environment:
NODE_ENV: production
RAILS_ENV: production
INSTALLATION_ENV: docker
SECRET_KEY_BASE: ${CHATWOOT_SECRET_KEY_BASE}
RAILS_LOG_TO_STDOUT: "true"
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DATABASE: chatwoot
POSTGRES_USERNAME: ${POSTGRES_USER:-micprotocol}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-micprotocol_secret}
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/chatwoot?sslmode=disable
REDIS_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-redis_secret}
REDIS_SIDEKIQ_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
FRONTEND_URL: ${CHATWOOT_URL:-http://localhost:3005}
SMTP_ADDRESS: ${SMTP_HOST:-mailhog}
SMTP_PORT: ${SMTP_PORT:-1025}
SMTP_USERNAME: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_DOMAIN: ${SMTP_DOMAIN:-micprotocol.local}
MAILER_SENDER_EMAIL: ${EMAIL_FROM:-Chatwoot <support@micprotocol.local>}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "bundle", "exec", "sidekiq", "health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- micprotocol-network
# ─── Pretix (Self-Hosted Ticketing & Event Registration) ─────
# Open-source ticketing platform. Port 8000 inside Docker network.
# Pretix uses its own Postgres database + Redis + a separate pretixworker.
pretix:
image: pretix/pretix:latest
container_name: micprotocol-pretix
restart: unless-stopped
ports:
- "8000:8000"
environment:
PRETIX_DB_BACKEND: postgres
PRETIX_DB_NAME: pretix
PRETIX_DB_USER: ${POSTGRES_USER:-micprotocol}
PRETIX_DB_PASSWORD: ${POSTGRES_PASSWORD:-micprotocol_secret}
PRETIX_DB_HOST: postgres
PRETIX_DB_PORT: 5432
PRETIX_URL: ${PRETIX_URL:-http://localhost:8000}
PRETIX_ADMIN_EMAIL: ${PRETIX_ADMIN_EMAIL:-admin@micprotocol.local}
PRETIX_ADMIN_PASSWORD: ${PRETIX_ADMIN_PASSWORD:-admin_secret}
PRETIX_ADMIN_NAME: MicProtocol Admin
PRETIX_INSTANCE_NAME: MicProtocol Ticketing
PRETIX_INSTANCE_SLUG: micprotocol
PRETIX_SECRET: ${PRETIX_SECRET}
PRETIX_EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
# Pretix needs Redis for caching
PRETIX_REDIS_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- micprotocol-network
pretixworker:
image: pretix/pretix:latest
container_name: micprotocol-pretixworker
restart: unless-stopped
command: pretixworker
environment:
PRETIX_DB_BACKEND: postgres
PRETIX_DB_NAME: pretix
PRETIX_DB_USER: ${POSTGRES_USER:-micprotocol}
PRETIX_DB_PASSWORD: ${POSTGRES_PASSWORD:-micprotocol_secret}
PRETIX_DB_HOST: postgres
PRETIX_DB_PORT: 5432
PRETIX_URL: ${PRETIX_URL:-http://localhost:8000}
PRETIX_SECRET: ${PRETIX_SECRET}
PRETIX_REDIS_URL: redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
pretix:
condition: service_healthy
networks:
- micprotocol-network
# ─── MinIO (S3-compatible storage for all integrations) ──
minio:
image: minio/minio:latest
container_name: micprotocol-minio
restart: unless-stopped
ports:
- "9001:9000"
- "9090:9090"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-micprotocol_admin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-micprotocol_secret}
command: server /data --console-address ":9090"
volumes:
- minio_data:/data
networks:
- micprotocol-network
# One-shot bucket creator for all S3 consumers
minio-createbuckets:
image: minio/mc:latest
container_name: micprotocol-minio-mc
depends_on:
- minio
restart: "no"
entrypoint: >
/bin/sh -c "
/usr/bin/mc alias set myminio http://minio:9000 ${MINIO_ROOT_USER:-micprotocol_admin} ${MINIO_ROOT_PASSWORD:-micprotocol_secret};
/usr/bin/mc mb myminio/micprotocol-storage --ignore-existing;
/usr/bin/mc mb myminio/calcom-uploads --ignore-existing;
/usr/bin/mc mb myminio/documenso-uploads --ignore-existing;
/usr/bin/mc mb myminio/formbricks-uploads --ignore-existing;
/usr/bin/mc anonymous set public myminio/micprotocol-storage;
exit 0;
"
networks:
- micprotocol-network
# ─── Umami (Self-hosted web analytics) ───────────────────
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: micprotocol-umami
restart: unless-stopped
ports:
- "3003:3000"
environment:
# Bind to all interfaces so other containers can reach Umami
HOSTNAME: "0.0.0.0"
DATABASE_URL: postgresql://${POSTGRES_USER:-micprotocol}:${POSTGRES_PASSWORD:-micprotocol_secret}@postgres:5432/umami?schema=public
DATABASE_TYPE: postgresql
APP_SECRET: ${UMAMI_APP_SECRET}
depends_on:
postgres:
condition: service_healthy
networks:
- micprotocol-network
# ─── LiveKit (WebRTC SFU for recording studio) ──────────
# Provides real-time multi-participant audio rooms.
# Ports: 7880 (HTTP), 7881 (TCP), 7882 (UDP), 7883 (TURN/TLS)
livekit:
image: livekit/livekit-server:latest
container_name: micprotocol-livekit
restart: unless-stopped
ports:
- "7880:7880"
- "7881:7881"
- "7882:7882/udp"
command:
- --config
- /etc/livekit.yaml
volumes:
- ./services/livekit/livekit.yaml:/etc/livekit.yaml:ro
networks:
- micprotocol-network
# ─── coturn (TURN server for LiveKit NAT traversal) ───────
# Provides TURN/STUN relay for WebRTC participants behind symmetric NAT.
# LiveKit autodiscords TURN via the coturn REST API / livekit config.
coturn:
image: coturn/coturn:4.6.2
container_name: micprotocol-coturn
restart: unless-stopped
command: >
sh -c "
turnserver -v
--listening-port=3478
--tls-listening-port=5349
--min-port=49152 --max-port=65535
--use-auth-secret
--static-auth-secret=${COTURN_SECRET:-coturn_secret}
--realm=${ROOT_DOMAIN:-localhost}
--no-cli
--no-stdout-log
--syslog
"
environment:
COTURN_SECRET: ${COTURN_SECRET:-coturn_secret}
ROOT_DOMAIN: ${ROOT_DOMAIN:-localhost}
ports:
- "3478:3478/tcp"
- "3478:3478/udp"
- "5349:5349/tcp"
- "5349:5349/udp"
- "49152-65535:49152-65535/udp"
networks:
- micprotocol-network
# ─── Mailhog (Local SMTP catch-all for all services) ─────
# Replaces paid SMTP services in dev. Swagger UI at :8025.
# In production, point SMTP_HOST/PORT to your real relay.
mailhog:
image: mailhog/mailhog:latest
container_name: micprotocol-mailhog
restart: unless-stopped
ports:
- "1025:1025"
- "8025:8025"
networks:
- micprotocol-network
# ─── Volumes ───────────────────────────────────────────────
volumes:
postgres_data:
driver: local
redis_data:
driver: local
meilisearch_data:
driver: local
minio_data:
driver: local
listmonk_uploads:
driver: local
chatwoot_storage:
driver: local
# ─── Networks ──────────────────────────────────────────────
networks:
micprotocol-network:
driver: bridge