# MicProtocol – Full Deployment Guide (All Services) This guide covers deploying the complete MicProtocol stack — core platform, real-time engine, and all self-hosted integrations — on a single server using Docker Compose. ## Prerequisites | Requirement | Minimum | Recommended | |---|---|---| | OS | Ubuntu 22.04 / Debian 12 | Ubuntu 24.04 LTS | | CPU | 8 cores | 16+ cores | | RAM | 16 GB | 32+ GB | | Disk | 80 GB SSD | 200+ GB NVMe | | Docker | 24.0+ | 27+ | | Docker Compose | v2.20+ | v2.29+ | | Domain | 1 root domain | Wildcard DNS (`*.yourdomain.com`) | ## Architecture Overview ``` Caddy (TLS + Reverse Proxy) ┌──────────────────────────────┐ │ *.yourdomain.com → :3000 │ │ api.* → :4000 │ │ realtime.* → :4001 (WS) │ │ admin.* → :3006 │ └──────────────────────────────┘ │ ┌─────────────────┼─────────────────────┐ │ │ │ ┌────▼────┐ ┌─────▼─────┐ ┌──────▼──────┐ │ Web │ │ API │ │ Admin │ │ :3000 │────▶│ :4000 │◀───────│ :3006 │ └─────────┘ └─────┬─────┘ └─────────────┘ │ ┌──────────────┼──────────────────┐ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌─────▼─────┐ │ Postgres │ │ Redis │ │ Meilisearch│ │ :5432 │ │ :6379 │ │ :7700 │ └──────────┘ └─────────┘ └────────────┘ │ ┌─────────┼──────────────────────────────────┐ │ │ │ │ │ │ ┌───▼───┐ ┌──▼──┐ ┌────▼───┐ ┌──▼──┐ ┌──▼──┐ ┌──▼──┐ │Cal.com│ │Chat-│ │Form- │ │Docu-│ │List-│ │Umami│ │ :3002 │ │woot │ │bricks │ │menso│ │monk │ │:3003│ └───────┘ │:3005│ │ :3001 │ │:3004│ │:9000│ └─────┘ └─────┘ └────────┘ └─────┘ └─────┘ ``` ## Step 1: Generate Secrets Run the secrets generator to produce strong random values for every service: ```bash cd /path/to/MicProtocol bash scripts/generate-secrets.sh > .env.secrets ``` Review the output, then merge it into your `.env` file. **Never commit `.env` to git.** ## Step 2: Configure Environment Copy the example and fill in your values: ```bash cp .env.example .env ``` ### Required Variables At minimum, set these before the first boot: | Variable | Where to get it | |---|---| | `POSTGRES_PASSWORD` | Generated by `generate-secrets.sh` | | `REDIS_PASSWORD` | Generated by `generate-secrets.sh` | | `COOKIE_SECRET` | Generated by `generate-secrets.sh` | | `CLERK_SECRET_KEY` | Clerk Dashboard → API Keys | | `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | Clerk Dashboard → API Keys | | `WEB_URL` | Your public web app URL (e.g. `https://yourdomain.com`) | | `API_URL` | Your public API URL (e.g. `https://api.yourdomain.com`) | | `RESEND_API_KEY` | Resend Dashboard (or your SMTP relay) | | `R2_ACCESS_KEY_ID` | Cloudflare R2 dashboard | | `R2_SECRET_ACCESS_KEY` | Cloudflare R2 dashboard | | `R2_ENDPOINT` | Cloudflare R2 endpoint | | `R2_BUCKET` | R2 bucket name | ### Self-Hosted Integration Variables Each integration needs its own secrets (all generated by `generate-secrets.sh`): | Integration | Key Variables | |---|---| | **Cal.com** | `CALCOM_NEXTAUTH_SECRET`, `CALCOM_ENCRYPTION_KEY`, `CALCOM_WEBHOOK_SECRET`, `CALCOM_API_KEY` | | **Chatwoot** | `CHATWOOT_SECRET_KEY_BASE`, `CHATWOOT_WEBHOOK_SECRET`, `CHATWOOT_API_KEY` | | **Formbricks** | `FORMBRICKS_NEXTAUTH_SECRET`, `FORMBRICKS_ENCRYPTION_KEY`, `FORMBRICKS_CRON_SECRET`, `FORMBRICKS_HUB_API_KEY`, `FORMBRICKS_CUBE_API_SECRET`, `FORMBRICKS_WEBHOOK_SECRET` | | **Documenso** | `DOCUMENSO_NEXTAUTH_SECRET`, `DOCUMENSO_ENCRYPTION_KEY`, `DOCUMENSO_ENCRYPTION_SECONDARY_KEY`, `DOCUMENSO_SIGNING_PASSPHRASE` | | **Listmonk** | `LISTMONK_ADMIN_PASSWORD`, `LISTMONK_API_TOKEN`, `LISTMONK_WEBHOOK_SECRET` | | **Umami** | `UMAMI_APP_SECRET` | | **LiveKit** | `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET` | | **mic-realtime** | `MIC_REALTIME_INTERNAL_TOKEN`, `MIC_REALTIME_SHARED_SECRET`, `MIC_REALTIME_PUBLIC_URL` | ### Production URLs Replace all `http://localhost:*` URLs with your actual domain: ```bash # Public-facing WEB_URL=https://yourdomain.com API_URL=https://api.yourdomain.com NEXT_PUBLIC_API_URL=https://api.yourdomain.com # Self-hosted integration public URLs CALCOM_URL=https://cal.yourdomain.com CHATWOOT_URL=https://support.yourdomain.com DOCUMENSO_URL=https://sign.yourdomain.com FORMBRICKS_URL=https://surveys.yourdomain.com LISTMONK_URL=https://mail.yourdomain.com NEXT_PUBLIC_UMAMI_URL=https://analytics.yourdomain.com # Real-time (MUST be wss:// in production) MIC_REALTIME_PUBLIC_URL=wss://realtime.yourdomain.com NEXT_PUBLIC_MIC_REALTIME_URL=wss://realtime.yourdomain.com ``` ## Step 3: Configure DNS For wildcard TLS to work, you need a DNS-01 challenge. Add these DNS records: | Record | Type | Value | |---|---|---| | `@` | A | Your server IP | | `*` | A | Your server IP | Or use CNAME records if your DNS provider supports wildcard CNAMEs. ## Step 4: Generate Documenso Signing Certificate Documenso requires a PKCS#12 signing certificate: ```bash mkdir -p scripts/documenso-cert # Generate self-signed cert (replace with your details) openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 3650 -nodes \ -subj "/CN=Documenso Signing/O=MicProtocol" # Convert to PKCS#12 openssl pkcs12 -export -out cert.p12 -inkey key.pem -in cert.pem \ -passout pass:${DOCUMENSO_SIGNING_PASSPHRASE} # Move to the expected location mv cert.p12 scripts/documenso-cert/ rm -f key.pem cert.pem ``` ## Step 5: Start Core Infrastructure Start the shared infrastructure first — these must be healthy before anything else: ```bash # Start Postgres, Redis, Meilisearch, MinIO docker compose up -d postgres redis meilisearch minio minio-createbuckets # Wait for health checks to pass docker compose ps # All should show "healthy" ``` ### Verify Postgres is ready: ```bash docker exec -it micprotocol-postgres pg_isready -U micprotocol ``` The `init-databases.sql` script runs automatically on first boot, creating databases for: `chatwoot`, `calcom`, `documenso`, `formbricks`, `listmonk`, `umami`. ## Step 6: Start the API and Worker ```bash docker compose up -d api worker ``` The API will: 1. Run Prisma migrations against the `micprotocol` database 2. Start the NestJS server on `:4000` 3. Expose tRPC, REST, and webhook endpoints Verify: ```bash curl -s http://localhost:4000/health ``` ## Step 7: Start the Real-Time Engine ```bash docker compose up -d mic-realtime ``` Verify: ```bash curl -s http://localhost:4001/healthz ``` **Important:** `MIC_REALTIME_INTERNAL_TOKEN` and `MIC_REALTIME_SHARED_SECRET` must be strong random strings. The service fails closed at startup if they are missing or set to defaults. ## Step 8: Start Web and Admin ```bash docker compose up -d web admin ``` Verify: ```bash curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 # Web curl -s -o /dev/null -w "%{http_code}" http://localhost:3006 # Admin ``` ## Step 9: Start Self-Hosted Integrations Start all integrations (or selectively enable ones you need): ```bash # Start everything at once docker compose up -d # OR start individual services: docker compose up -d calcom # Scheduling docker compose up -d chatwoot-web chatwoot-worker # Live chat docker compose up -d formbricks-migrate formbricks-hub-migrate formbricks formbricks-hub formbricks-cube # Surveys docker compose up -d documenso # E-signatures docker compose up -d listmonk # Email marketing docker compose up -d umami # Analytics docker compose up -d livekit # WebRTC recording ``` ### Integration Startup Order Some services have dependencies that Docker Compose handles automatically: ``` Postgres (healthy) ├── formbricks-migrate (runs once) │ └── formbricks-hub-migrate (runs once) │ └── formbricks-cube (healthy) │ └── formbricks-hub (healthy) │ └── formbricks (healthy) ├── calcom (depends on Postgres + Redis) ├── chatwoot-web + chatwoot-worker (depends on Postgres + Redis) ├── documenso (depends on Postgres) ├── listmonk (depends on Postgres) └── umami (depends on Postgres) ``` ## Step 10: Start Dev SMTP (Optional) For local development, Mailhog captures all outbound email: ```bash docker compose up -d mailhog ``` - SMTP server: `localhost:1025` - Web UI: `http://localhost:8025` In production, replace with your transactional SMTP relay (Resend, SES, Postmark, etc.) by setting `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`. ## Step 11: Start CLIP Scorer (Optional) The CLIP scoring engine is CPU/GPU intensive. Start it only if you need AI-powered clip scoring: ```bash docker compose up -d clip-scorer ``` Resource requirements: - CPU: 2 cores minimum - Memory: 2 GB minimum - Optional GPU: Cuts inference time by 5-10x ## Step 12: Configure Caddy (TLS + Reverse Proxy) The `Caddyfile` handles TLS termination and routing. Set the `ROOT_DOMAIN` and `CLOUDFLARE_API_TOKEN` environment variables: ```bash export ROOT_DOMAIN=yourdomain.com export CLOUDFLARE_API_TOKEN=your_cloudflare_api_token ``` Start Caddy: ```bash docker compose up -d caddy ``` Or install Caddy on the host and run it with the provided `Caddyfile`: ```bash # Install Caddy sudo apt install -y caddy # Copy the Caddyfile sudo cp Caddyfile /etc/caddy/Caddyfile # Set environment export ROOT_DOMAIN=yourdomain.com export CLOUDFLARE_API_TOKEN=your_token # Start sudo systemctl restart caddy ``` ### Caddy Routing Table | Host | Upstream | Protocol | |---|---|---| | `yourdomain.com` / `*.yourdomain.com` | web:3000 | HTTPS | | `api.yourdomain.com` | api:4000 | HTTPS | | `realtime.yourdomain.com` | mic-realtime:4001 | WSS | | `admin.yourdomain.com` | admin:3006 | HTTPS | ## Step 13: Run Database Migrations If this is a fresh deployment, run Prisma migrations: ```bash docker exec -it micprotocol-api npx prisma migrate deploy ``` Or generate the Prisma client and push the schema: ```bash docker exec -it micprotocol-api npx prisma generate docker exec -it micprotocol-api npx prisma db push ``` ## Step 14: Post-Deployment Setup ### Cal.com 1. Open `https://cal.yourdomain.com` 2. Create admin account 3. Go to Settings → API Keys → generate a key 4. Set `CALCOM_API_KEY` in `.env` 5. Restart the API: `docker compose restart api` ### Chatwoot 1. Open `https://support.yourdomain.com` 2. Complete the setup wizard 3. Go to Settings → Agents → create an agent 4. Go to Settings → Inboxes → create a website inbox 5. Copy the inbox ID to `CHATWOOT_INBOX_ID` 6. Copy the website token to `NEXT_PUBLIC_CHATWOOT_WEBSITE_TOKEN` 7. Restart the API ### Formbricks 1. Open `https://surveys.yourdomain.com` 2. Create admin account 3. Go to Settings → Environments → copy the environment ID 4. Set `NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID` 5. Go to Settings → API Keys → create a key 6. Set `FORMBRICKS_API_KEY` 7. Restart the API ### Documenso 1. Open `https://sign.yourdomain.com` 2. Create admin account 3. Go to Settings → API → create a key 4. Set `DOCUMENSO_API_KEY` 5. Restart the API ### Listmonk 1. Open `https://mail.yourdomain.com` 2. Log in with `LISTMONK_ADMIN_USER` / `LISTMONK_ADMIN_PASSWORD` 3. Go to Settings → API → generate a token 4. Set `LISTMONK_API_TOKEN` 5. Create a mailing list and copy its UUID to `NEXT_PUBLIC_LISTMONK_LIST_UUID` 6. Configure webhooks: POST to `http://api:4000/integrations/webhooks/listmonk` with header `x-listmonk-secret: ` 7. Restart the API ### Umami 1. Open `https://analytics.yourdomain.com` 2. Log in with default credentials (admin/umami) 3. Change the admin password 4. Add a website and copy the website ID to `NEXT_PUBLIC_UMAMI_WEBSITE_ID` 5. Restart the web app ### LiveKit 1. The recording studio connects to LiveKit on port 7880 2. For internet-facing deployments, ensure UDP port 7882 is open 3. If behind a firewall, configure a TURN server or set `rtc.use_external_ip: true` ## Health Checks Verify all services are running: ```bash # Core curl -s http://localhost:4000/health # API curl -s http://localhost:4001/healthz # mic-realtime curl -s http://localhost:3000 # Web curl -s http://localhost:3006 # Admin # Integrations curl -s http://localhost:3002/api/health # Cal.com curl -s http://localhost:3005/health # Chatwoot curl -s http://localhost:3001/api/health # Formbricks curl -s http://localhost:3004/api/health # Documenso curl -s http://localhost:9000/health # Listmonk curl -s http://localhost:3003/api/health # Umami curl -s http://localhost:7880/health # LiveKit # Consolidated check curl -s http://localhost:4000/integrations/health | jq ``` ## Port Reference | Port | Service | Protocol | |---|---|---| | 3000 | Web (Next.js) | HTTP | | 3001 | Formbricks | HTTP | | 3002 | Cal.com | HTTP | | 3003 | Umami | HTTP | | 3004 | Documenso | HTTP | | 3005 | Chatwoot | HTTP | | 3006 | Admin | HTTP | | 4000 | API (NestJS) | HTTP | | 4001 | mic-realtime | HTTP/WS | | 5432 | PostgreSQL | TCP | | 6379 | Redis | TCP | | 7700 | Meilisearch | HTTP | | 7880 | LiveKit | HTTP | | 7881 | LiveKit | TCP | | 7882 | LiveKit | UDP | | 8025 | Mailhog UI | HTTP | | 9000 | Listmonk | HTTP | | 9001 | MinIO API | HTTP | | 9090 | MinIO Console | HTTP | | 1025 | Mailhog SMTP | TCP | ## Resource Limits Default limits from `docker-compose.yml`: | Service | CPUs | Memory | |---|---|---| | API | 2 | 2 GB | | Worker | 1 | 1 GB | | CLIP Scorer | 2 | 2 GB | | mic-realtime | 1.5 | 1 GB | Other services have no explicit limits. Tune based on your hardware. ## Common Operations ### Stop everything ```bash docker compose down # Keep data docker compose down -v # Wipe all data (destructive) ``` ### Update a single service ```bash docker compose pull docker compose up -d --no-deps ``` ### Update Formbricks (requires migration re-run) ```bash docker compose up formbricks-migrate docker compose up formbricks-hub-migrate docker compose up -d --no-deps formbricks formbricks-hub formbricks-cube ``` ### View logs ```bash docker compose logs -f api # API logs docker compose logs -f mic-realtime # Real-time logs docker compose logs -f chatwoot-web # Chatwoot logs ``` ### Restart a service ```bash docker compose restart api ``` ### Enter a container ```bash docker exec -it micprotocol-api sh docker exec -it micprotocol-postgres psql -U micprotocol ``` ## Troubleshooting ### Service won't start 1. Check logs: `docker compose logs ` 2. Verify env vars are set in `.env` 3. Check that dependent services are healthy: `docker compose ps` ### Database connection refused 1. Ensure Postgres is healthy: `docker exec micprotocol-postgres pg_isready` 2. Verify `DATABASE_URL` uses the container hostname (`postgres`), not `localhost` ### Real-time WebSocket not connecting 1. Ensure `MIC_REALTIME_PUBLIC_URL` is a `wss://` URL in production 2. Check that `MIC_REALTIME_INTERNAL_TOKEN` and `MIC_REALTIME_SHARED_SECRET` are set 3. Verify Caddy is routing `realtime.*` correctly ### Formbricks won't start 1. Ensure all 5 Formbricks containers start in order 2. Check that `SKIP_STARTUP_MIGRATION=true` is set on the web container 3. Verify Cube.js is healthy before Formbricks web starts ### Outbound email not working 1. Check `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD` in `.env` 2. In dev, ensure Mailhog is running: `docker compose up -d mailhog` 3. Check Mailhog UI at `http://localhost:8025` ## Disabling Services Any integration can be removed by deleting its block from `docker-compose.yml` and omitting its env vars from `.env`. The API degrades gracefully — missing integrations return `false` from health checks and log warnings rather than crashing. ## Security Checklist - [ ] All default passwords changed - [ ] `.env` not committed to git - [ ] Firewall configured (only ports 80, 443, and SSH open) - [ ] TLS working via Caddy - [ ] `MIC_REALTIME_INTERNAL_TOKEN` and `MIC_REALTIME_SHARED_SECRET` are strong random values - [ ] Encryption keys (`*_ENCRYPTION_KEY`) generated and backed up (never rotate after first use) - [ ] Webhook secrets configured on both sides - [ ] SMTP relay configured (not Mailhog) in production - [ ] `DEV_AUTH_BYPASS_ENABLED=false` in production