MicProtocol / docs /deployment.md
Benaah's picture
Upload folder using huggingface_hub
b17baae verified
|
Raw History Blame Contribute Delete
7.67 kB

MicProtocol – Deployment & Scaling Strategy

Deployment Architecture

graph LR
    subgraph "Vercel Edge"
        WEB[Next.js Web App]
        PORTAL[Guest Portal]
    end
    
    subgraph "AWS ECS/Fargate"
        API[NestJS API]
        WORKER[Background Workers]
        CLIP_SCORER[CLIP Scorer<br/>Python + ML]
    end
    
    subgraph "Managed Services"
        DB[RDS PostgreSQL]
        CACHE[ElastiCache Redis]
        SEARCH[Meilisearch Cloud]
        OBJ[Cloudflare R2]
    end
    
    subgraph "CI/CD"
        GHA[GitHub Actions]
        TURBO[Turborepo Cache]
    end
    
    GHA -->|Docker Build| API
    GHA -->|Deploy| WEB
    WEB -->|tRPC| API
    API --> DB
    WORKER --> DB
    WORKER --> CACHE
    WORKER -->|subprocess| CLIP_SCORER
    CLIP_SCORER --> DB

Production Stacks (Coolify / Render)

Beyond the ECS layout above, the repo ships two ready-to-deploy production blueprints that include the Erlang/OTP real-time engine (mic-realtime).

Coolify (docker-compose.coolify.yml)

Full self-hosted stack: Postgres, Redis, API, worker, mic-realtime, web, admin, and the self-hosted integration fleet. Caddy terminates TLS (wildcard cert via DNS-01) and routes:

Host Upstream
<root> / *. (workspace subdomains) web :3000
api.<root> api :4000
admin.<root> admin :3000
realtime.<root> mic-realtime :4001 (WebSocket)

Render (render.yaml)

Backend-only blueprint (web + admin deploy on Vercel):

  • micprotocol-api — NestJS API
  • micprotocol-redis — Redis add-on
  • micprotocol-realtime — Erlang/OTP engine (env: docker, rootDir: services/mic-realtime, health at /healthz)

Real-time engine & required secrets

The mic-realtime service is a first-class citizen of both stacks and ships fail-closed: both it and the API (RealtimeClient in apps/api/src/chat/realtime.client.ts) refuse to boot in production when the secrets are missing or equal to the known defaults.

Env var Required Notes
MIC_REALTIME_INTERNAL_TOKEN ✅ Bearer used by the API to publish events via /internal/dispatch
MIC_REALTIME_SHARED_SECRET ✅ HMAC secret for WebSocket auth tokens
MIC_REALTIME_PUBLIC_URL ✅ Browser-facing wss://realtime.<root> URL; forwarded to web/admin as NEXT_PUBLIC_MIC_REALTIME_URL
MIC_REALTIME_URL API only API→realtime base URL (Coolify default http://mic-realtime:4001)

Generate both secrets with openssl rand -hex 32 and set MIC_REALTIME_PUBLIC_URL to a wss:// URL before first boot — the services will not start with missing or default values.

Environment Strategy

Environment Web Frontend API Backend Database
Development localhost:3000 localhost:4000 Local PostgreSQL
Preview Vercel Preview ECS Staging RDS Staging
Production Vercel Edge ECS Production RDS Multi-AZ

Scaling Triggers

< 1,000 Podcasts (Startup Phase)

  • Single ECS task for API + Workers
  • Shared Redis instance (ElastiCache t3.micro)
  • Cloudflare R2 for all media storage
  • Single PostgreSQL instance (RDS db.t3.medium)

> 1,000 Podcasts (Growth Phase)

  • Split Workers into separate ECS service
  • Dedicated Redis for BullMQ vs. caching
  • Read replicas for PostgreSQL
  • CDN for static assets
  • Migrate complex workflows to Temporal.io

> 10,000 Podcasts (Scale Phase)

  • Extract Outreach Domain into standalone microservice
  • Outreach gets own MongoDB cluster (high write volume on email events)
  • Event bus (AWS SNS/SQS) for cross-service communication
  • Meilisearch cluster for search scaling
  • Multi-region deployment for global guest portal latency

CI/CD Pipeline

flowchart TD
    PUSH[Git Push] --> LINT[Lint & Type Check]
    LINT --> TEST[Unit Tests]
    TEST --> BUILD[Turbo Build]
    
    BUILD --> |Main Branch| DEPLOY_STAGING[Deploy to Staging]
    BUILD --> |Release Tag| DEPLOY_PROD[Deploy to Production]
    BUILD --> |PR| PREVIEW[Vercel Preview]
    
    DEPLOY_STAGING --> INTEGRATION[Integration Tests]
    INTEGRATION --> |Pass| PROMOTE[Promote to Production]
    
    DEPLOY_PROD --> MIGRATE[Run DB Migrations]
    MIGRATE --> HEALTH[Health Checks]
    HEALTH --> |Healthy| COMPLETE([Deployment Complete])
    HEALTH --> |Unhealthy| ROLLBACK[Automatic Rollback]

Infrastructure Configuration

Docker (API)

FROM node:20-alpine AS base
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate

FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/api/package.json apps/api/
COPY packages/shared-types/package.json packages/shared-types/
RUN pnpm install --frozen-lockfile

FROM base AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm --filter @micprotocol/api prisma generate
RUN pnpm --filter @micprotocol/api build

FROM base AS runtime
WORKDIR /app
COPY --from=build /app/apps/api/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/apps/api/prisma ./prisma
RUN apk add --no-cache ffmpeg
EXPOSE 4000
CMD ["node", "dist/main.js"]

Docker (CLIP Scorer)

Canonical source of truth is services/clip-scorer/Dockerfile. It builds a one-shot CLI image:

FROM python:3.11-slim AS base
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
RUN uv venv /opt/venv \
    && VIRTUAL_ENV=/opt/venv uv pip install --index-url https://download.pytorch.org/whl/cpu "torch>=2.0.0" \
    && VIRTUAL_ENV=/opt/venv uv pip install "open-clip-torch>=2.20.0" "transformers>=4.30.0" "Pillow>=10.0.0" "soundfile>=0.12.0" "pyloudnorm>=0.1.0" "numpy>=1.24.0"
ENV PATH="/opt/venv/bin:$PATH"
RUN python3 -c "import open_clip; open_clip.create_model_and_transforms('ViT-B-32', pretrained='laion2b_s34b_b79k')"
COPY scoring/ scoring/
COPY pipeline.py .
ENTRYPOINT ["python3", "pipeline.py"]

In production the same Python stack is baked into the worker image (apps/api/Dockerfile), where the BullMQ clip-scoring processor executes the pipeline via execFile.

Docker Compose Configuration

The worker service requires ipc: host for /dev/shm access (needed by ffmpeg shared memory). CLIP scoring runs inside the worker as a subprocess of the processor — there is no separate clip-scorer container or shared volume:

worker:
  container_name: micprotocol-worker
  command: node /app/apps/api/dist/main.js
  ipc: host
  deploy:
    resources:
      limits:
        cpus: "2"
        memory: 2G

Scaling Considerations for CLIP Scorer

Metric Value Notes
CPU per clip ~0.5 cores ~30–120s for CLIP inference (CPU)
Memory per clip ~1.5GB CLIP model + frame buffers
Concurrent clips 2 per worker (set in processor) Bump cautiously — Python ML is memory-heavy
Horizontal scaling Add worker containers Each container handles 2 concurrent clips
GPU acceleration Optional CLIP runs on CPU by default; GPU cuts time by 5-10x

Security

  • GDPR/CCPA: Right-to-erasure cascading through all related records
  • Email Compliance: CAN-SPAM footer injection, automatic unsubscribe
  • Data Isolation: Row-level scoping through workspace → podcast hierarchy
  • Upload Security: Presigned URLs (15-min expiry), MIME verification, virus scanning
  • Auth: Clerk JWT verification with workspace-scoped claims