Architecture
How Savannah Cloud components fit together.
System Overview
┌──────────────────────────────────────────────────────┐
│ Client / Deployed App │
│ (API Key · JWT · MCP tool call · Dashboard UI) │
└────────────────────────┬─────────────────────────────┘
│
┌─────────▼──────────┐
│ Go API Server │ :8080
│ auth · jobs │
│ deploy · domains │
│ email · sms · mcp │
└──┬──────────────┬──┘
│ │
┌─────────▼──┐ ┌───────▼────────┐
│ Redis │ │ PostgreSQL │
│ Job Queue │ │ orgs · users │
│ Sessions │ │ jobs · domains│
└────────────┘ │ deployments │
│ api_keys │
└───────────────-┘
│
┌─────────▼──────────┐ ┌─────────────────┐
│ Worker Pool (x4) │ │ PowerDNS │
│ send_email │ │ (custom domains │
│ send_sms │ │ ns1/ns2 │
│ webhook · deploy │ │ savannahcloud) │
└────────────────────┘ └─────────────────┘
│ WebSocket
┌─────────▼──────────────────────┐
│ React Dashboard :80 │
│ Deployments · Domains · Email │
│ SMS · Pipelines · Team · Billing│
└────────────────────────────────┘Components
Go API Server (:8080)
The central hub. Handles:
- Authentication — JWT issuance, bcrypt, API key validation, Redis session cookies
- Job ingestion — validates, stores in Postgres, enqueues into Redis
- Deployments — framework detection, Docker build/run, port assignment (9100–9999)
- Custom domains — PowerDNS zone management, background DNS verifier goroutine
- Email —
send_emailjob handler via Resend API - SMS —
send_smsjob handler +POST /sms/sendvia Africa's Talking - MCP server — JSON-RPC 2.0 over
POST /mcp, SSE stream atGET /mcp/sse - Metrics — Prometheus counters at
/metrics
Redis
Dual purpose:
- Job queue —
LPUSH/BLPOP(FIFO), 4 concurrent worker goroutines - Sessions — Redis-backed HttpOnly cookie sessions with sliding 30-min TTL
PostgreSQL
| Table | Purpose |
|---|---|
jobs | Job records with status, payload, retry count |
job_logs | Per-attempt execution log lines |
orgs | Organizations (tenants) |
users | User accounts with bcrypt-hashed passwords |
api_keys | Hashed sc_... keys scoped to an org |
invites | Pending team invitation tokens (72h, single-use) |
domains | Custom domain records with DNS verification state |
deployments | App deployment records, status, env vars |
Worker Pool (4 goroutines)
Each goroutine runs a BLPOP loop with 5s timeout:
BLPOPfrom Redis — blocks until job arrives- Marks job
processingin Postgres - Executes handler for
job.Type - On success → marks
completed, writesjob_logs - On failure → increments retry counter; re-enqueues or marks
failed
PowerDNS Auth 4.9
Manages DNS for custom domains delegated to Savannah's nameservers:
ns1.savannahcloud.com/ns2.savannahcloud.com- REST API at
:8081(internal) - Background goroutine resolves domains via Google DNS (
8.8.8.8) every 5 min - Sets
verified=truewhen A record matches VPS IP178.105.152.34
Cloudflare R2
Stores deployment build artefacts. Configured via R2_ENDPOINT, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET.
React Dashboard (:80 production)
| Route | Feature |
|---|---|
/dashboard | Live job feed, WebSocket updates, quick stats |
/deployments | App deployments — create, logs, env vars, redeploy |
/domains | Custom domain management + DNS verification status |
/email | 3-column inbox UI — compose, sent folder |
/sms | SMS compose + delivery history |
/team | Members, invite by email, role management |
/billing | Plan + usage + Paystack integration |
/api-keys | Create and revoke API keys |
Data Flow: Job Lifecycle
POST /jobs
│
├─ Auth middleware (JWT / API Key / Session) ──→ 401/403 if invalid
│
├─ Insert job row (status=pending) into Postgres
│
├─ LPUSH job ID onto Redis queue
│
└─ Return 202 Accepted
│
▼
Worker BLPOP loop picks up job
│
├─ UPDATE status=processing
│
├─ Execute handler (send_email, send_sms, webhook, deploy, ...)
│
├─ Success → UPDATE status=completed, INSERT job_logs
│
└─ Failure → increment retries
if retries >= MaxRetries → UPDATE status=failed
else re-enqueue with backoffDocker Compose Services
| Container | Role | Port |
|---|---|---|
riftcloud-app-1 | Go API + Worker pool | 8080 |
riftcloud-dashboard-1 | React UI (Nginx) | 80 |
riftcloud-postgres-1 | PostgreSQL 16 | 5432 |
riftcloud-redis-1 | Redis 7 | 6379 |
riftcloud-pdns-1 | PowerDNS Auth 4.9 | 53, 8081 |
Security
- Passwords hashed with bcrypt (cost 12)
- JWTs signed HS256, expire 30 days
- API keys stored as SHA-256 hashes; raw key shown once at creation
- Invite tokens single-use, expire 72 hours
- Sessions: Redis-backed HttpOnly cookie, sliding 30-min TTL
- Postgres:
trustfor Docker172.x.x.xnetwork,scram-sha-256for external connections