# dd0c/alert — Dual-Mode Deployment Addendum **Template:** Based on dd0c/route dual-mode pattern --- ## Cloud → Self-Hosted Service Mapping | Cloud Service | Self-Hosted Replacement | Notes | |--------------|----------------------|-------| | API Gateway + Lambda | Fastify/Express container | Webhook ingestion endpoint | | SQS | PostgreSQL pgmq | Between ingestion and correlation | | ECS Fargate (Correlation) | Docker container | Same Node.js code | | DynamoDB | PostgreSQL (JSONB) | Incidents stored as JSONB with GIN indexes | | TimescaleDB on RDS | TimescaleDB container | Analytics unchanged | | Cognito | Local JWT (HS256) | AuthProvider pattern | | S3 (raw payload archive) | Local FS or MinIO | ObjectStore trait | | SES | SMTP relay | Email notifications | | EventBridge | Cron container | Scheduled tasks | ## Self-Hosted Compose Services ```yaml services: ingestion: # Webhook endpoint (replaces API Gateway + Lambda) image: ghcr.io/dd0c/alert-ingestion:latest correlation: # Correlation engine (replaces ECS Fargate) image: ghcr.io/dd0c/alert-correlation:latest api: # Dashboard API image: ghcr.io/dd0c/alert-api:latest dashboard: # React SPA image: ghcr.io/dd0c/alert-dashboard:latest postgres: # Incidents (JSONB) + config image: postgres:16-alpine timescaledb: # Analytics image: timescale/timescaledb:latest-pg16 redis: # Sliding windows for correlation, circuit breakers image: redis:7-alpine caddy: # Reverse proxy + auto-TLS image: caddy:2-alpine ``` ## Key Difference: DynamoDB → PostgreSQL JSONB DynamoDB Single-Table design maps to PostgreSQL JSONB with partition-like indexes: ```sql CREATE TABLE incidents ( id UUID PRIMARY KEY, tenant_id TEXT NOT NULL, data JSONB NOT NULL, severity TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'open', created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_incidents_tenant ON incidents(tenant_id); CREATE INDEX idx_incidents_data ON incidents USING GIN(data); -- RLS policy ALTER TABLE incidents ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON incidents USING (tenant_id = current_setting('app.tenant_id')); ``` ## Epic Impact | Epic | Change | Effort | |------|--------|--------| | 1 (Webhook Ingestion) | Lambda → Fastify container | 3 pts | | 2 (Normalization) | No change — pure logic | 0 | | 3 (Correlation) | pgmq instead of SQS, same Redis | 2 pts | | 4 (Notifications) | SMTP fallback | 1 pt | | 5 (Slack Bot) | No change | 0 | | 6 (Dashboard API) | LocalAuthProvider, DynamoDB→PG | 3 pts | | 7 (Dashboard UI) | Local login form | 2 pts | | 8 (Infrastructure) | docker-compose.yml + install.sh | 5 pts | | 9 (Onboarding) | Local signup, remove Stripe req | 3 pts | | 10 (TF Tenets) | No change | 0 | | **Total** | | **19 pts** |