Self-Hosted Deployment
This guide is for teams that run both Mubeen's data plane and control plane, every container, not just the parts that touch data, inside their own cloud or on their own servers, instead of calling Mubeen's hosted API (see Architecture Overview for the hosted model). You own the compute, networking, and data at rest; Mubeen ships the container images.
Three exceptions to full self-containment, each covered in detail further down this page: the Dashboard's calls to an external LLM provider and SMTP relay, outbound license validation, and Mubeen's standing support VPN into the Data Layer (see Mubeen Support Access).
What You'll Deploy
| Component | Role | You provide |
|---|---|---|
| WAF | Filters traffic before it reaches a load balancer, on both the public and the internal edge | Your cloud's WAF (AWS WAF, Cloudflare, ModSecurity, etc.) |
| Public Load Balancer | TLS termination for the one public endpoint: fingerprint traffic only | Your cloud's LB (ALB, NLB, nginx, etc.) |
| Private Load Balancer | Routes internal traffic to Rule Engine and Dashboard; never faces the public internet | Your cloud's internal LB |
| Fingerprint Collector | Receives device fingerprints, issues device_id | Container host + config; outbound internet for real-time device/IP reputation lookups against Mubeen's threat-intelligence feed |
| Rule Engine | Scores risk-context payloads, returns decisions; calls ML Scoring for model-based scoring | Container host + config |
| ML Scoring | Runs the ML model inference behind Rule Engine's scoring calls. Nothing else reaches it: no LB, no other caller | Container host + config; no internet needed, models ship inside the image |
| Dashboard | Web UI for reviewing decisions and managing rules; calls an LLM for AI-assisted analysis; sends OTP login codes, reports, and notification emails | Container host + config; an OpenAI-compatible LLM API key; SMTP/email provider credentials |
| DB Schema Manager | Applies Postgres schema migrations. Talks only to Postgres, with elevated/DDL access; not part of the request path, not reachable by anything else | Container host + config; a superuser or DDL-capable Postgres role (distinct from the app containers' least-privilege roles) |
| Postgres | Primary datastore for decisions, rules, and dashboard state | Managed or self-run Postgres instance |
| Valkey | Cache / queue for the app containers | Managed or self-run Valkey instance |
| Grafana Alloy | Collects metrics and logs from all app containers | Container host |
| Grafana | Dashboards and alerting on top of Alloy's telemetry; reached over your internal network, not via either LB | Container host; outbound internet is optional, only needed for the plugin catalog, update checks, or alerting to an external service (Slack, PagerDuty, email) |
| Metabase | Ad-hoc analytics / BI on top of Postgres; reached over your internal network, not via either LB | Container host; outbound internet is optional, only needed for map-tile visualizations, version checks, or (unless disabled) anonymous usage stats |
| Dev Machine | ML training / model development against real data | A host inside your cloud, reachable only via VPN (not a public port), with SSH access and read-only DB access to Postgres; outbound internet scoped to package/dependency installs (e.g. via an egress proxy or allowlist to package registries) |
| Support Bastion / VPN Gateway | Standing entry point for Mubeen support to reach Postgres and Valkey. Container log/exec access is a separate mechanism (e.g. docker exec/kubectl exec over the same bastion host), not a network path | A VPN gateway or bastion host inside your VPC, Mubeen-held credentials |
Two Ingress Paths, Kept Apart
- Public path: your users → WAF → Public Load Balancer → Fingerprint Collector only. This is the one path that faces the open internet. In practice your WAF is usually integrated with the load balancer or CDN itself (e.g. AWS WAF attached to an ALB, Cloudflare) and inspects the decrypted HTTP request. It isn't a separate network hop that only sees raw TLS ciphertext. The diagram shows it as a distinct box for clarity, not to imply a literal packet-order requirement.
- Private path: your app servers → WAF → Private Load Balancer → Rule Engine, and your employees → WAF → Private Load Balancer → Dashboard. This path should never be reachable from the public internet in the first place; the internal WAF is defence in depth on top of that, not the thing keeping it private. It catches the same request-level attacks (injection, malformed payloads, credential stuffing against the Dashboard login) from a compromised app server or employee workstation. As on the public edge, it is normally a policy attached to the internal load balancer rather than a separate network hop. The Dashboard also calls the Rule Engine directly (to manage rules), which does not pass through the Private LB or its WAF.
Grafana and Metabase aren't on either path. They're reached directly over your own internal network/VPN, not routed through a load balancer. Treat them as additional private endpoints that need their own access control, not as an implicit extension of the Private LB.
Topology
Solid arrows are the request path, dashed arrows are telemetry, state, migrations, or read access, and thick purple arrows are privileged access (SSH/DB/VPN). Blue boxes are components developed in-house by Mubeen; the slate data stores are part of your infrastructure. Outbound-internet requirements are noted in the node labels rather than drawn as separate arrows, to keep the figure readable. Traffic on the private path also passes a WAF on the internal edge (defence in depth before the Private LB); the Dashboard's direct calls to the Rule Engine do not pass through the Private LB or its WAF.
Container Reference
| Container | Depends on |
|---|---|
fingerprint-svc | Postgres, Valkey |
rule-engine | Postgres, Valkey, ml-scoring |
ml-scoring | — (no datastore access; called by rule-engine only) |
dashboard-ui | Postgres, Valkey, rule-engine |
db-schema-manager | Postgres (not network-reachable; runs migrations then exits) |
grafana-alloy | fingerprint-svc, rule-engine, ml-scoring, dashboard-ui |
grafana | Alloy |
metabase | Postgres |
Network Access Requirements
Firewall/security-group rules to plan for. Rule Engine and Dashboard are never reachable from the public internet: only the public WAF + Public LB + Fingerprint Collector path is.
| Container | Inbound | Outbound to internet | Notes |
|---|---|---|---|
| WAF (public edge) | Internet (public) | — | Public entry point: public path only |
| Public Load Balancer | WAF (public edge) | — | Routes to Fingerprint Collector only |
| WAF (internal edge) | Your app servers, your employees (private network) | — | No public inbound; filters internal traffic before the Private LB |
| Private Load Balancer | WAF (internal edge) | — | No public internet exposure; routes to Rule Engine + Dashboard |
| Fingerprint Collector | Public LB only | Required | Real-time device/IP reputation lookups against Mubeen's threat-intelligence feed |
| Rule Engine | Private LB only (your app servers); also called directly by Dashboard | Not required | Only talks to Postgres, Valkey, and ML Scoring: no outbound internet needed; no public inbound |
| ML Scoring | Rule Engine only: no LB, no other caller, no public inbound | Not required | Models ship inside the image; do not expose this container to the Private LB or any other caller |
| Dashboard | Private LB only (your employees) | Required | Outbound includes your LLM provider's API endpoint (OpenAI-compatible: OpenAI, Azure OpenAI, or a self-hosted/proxied endpoint) and your SMTP relay / email provider (OTP codes, reports, notifications) |
| DB Schema Manager | Not reachable by anything: runs migrations, then exits | — | No internet needed; migrations ship inside the image |
| Postgres | Fingerprint Collector, Rule Engine, Dashboard, Metabase, Dev Machine, Support Bastion, DB Schema Manager | — | No public internet exposure in either direction. Support Bastion reaches it over a private VPN link; Dev Machine is inside the same cloud. Every connection uses sslmode=require |
| Valkey | Fingerprint Collector, Rule Engine, Dashboard, Support Bastion | — | No public internet exposure in either direction |
| Grafana Alloy | Fingerprint Collector, Rule Engine, ML Scoring, Dashboard (telemetry) | — | |
| Grafana | Alloy, internal users over your network | Optional | Not routed through either load balancer. Only needed for the plugin catalog, update checks, or alerting to an external service |
| Metabase | Internal users over your network | Optional | Not routed through either load balancer. Only needed for map-tile visualizations, version checks, or (unless disabled) anonymous usage stats |
| Dev Machine | SSH via VPN | Required, scoped | Outbound to package/dependency registries only (via an egress proxy or allowlist, not open internet), plus read-only DB access to Postgres, for ML training |
| Support Bastion / VPN Gateway | Standing VPN from Mubeen support | — | Mubeen-held credentials; scoped support access to Postgres and Valkey only, non-superuser (see Mubeen Support Access). Container log/exec access is separate (not a network path). This is a standing, always-on connection, not a per-incident grant |
Mubeen Support Access
Mubeen reaches your self-hosted deployment through a standing VPN connection to a bastion host inside your VPC, using Mubeen-held credentials, not a per-incident grant. This is how Mubeen debugs issues and applies updates without needing inbound internet access to your environment. The bastion's network reach is Postgres and Valkey only. It is not a general path into the app containers; log/exec access to those is a separate mechanism layered on top of the same host.
- No shared credentials: each Mubeen support engineer connects with their own named identity; never a single shared VPN/DB credential used by the whole support team
- MFA / hardware-key auth: required at the VPN, not just a password
- IP allowlist: restrict the VPN endpoint to Mubeen's published support egress ranges; request the current list from your Mubeen account contact before provisioning
- Time-boxed credentials: short-lived SSH certs or DB tokens, even though the VPN connection itself is standing
- Least-privilege access: the bastion's Postgres access should be a non-superuser role scoped
to what support needs, not full admin (no
DROP/ALTER, no access to unrelated schemas); its Valkey access should be a similarly scoped ACL user, not unrestrictedCONFIG/FLUSHALL
If your organization can't accept a standing connection under these terms, ask Mubeen about a per-incident, customer-approved access model instead (bastion opened on a support ticket, closed after). This has slower time-to-support but a much smaller blast radius.
Rough Sizing Guide
Starting points only, not benchmarked by Mubeen. Load-test against your own traffic pattern before committing to a tier.
All Mubeen images are built for linux/amd64 (x86-64). Don't provision ARM-based instances (e.g. AWS Graviton) for any Mubeen container unless you've confirmed an ARM image is available.
| Component | 1M requests/day | 5M requests/day |
|---|---|---|
| Fingerprint Collector | 2 vCPU / 4 GB | 8 vCPU / 16 GB |
| Rule Engine | 2 vCPU / 4 GB | 8 vCPU / 16 GB |
| ML Scoring | 2 vCPU / 4 GB | 8 vCPU / 16 GB |
| Dashboard | 2 vCPU / 4 GB | 4 vCPU / 16 GB |
| Postgres | 2 vCPU / 16 GB | 8 vCPU / 64 GB |
| Valkey | 2 vCPU / 8 GB | 4 vCPU / 32 GB |
DB Schema Manager isn't in this table. It's a one-shot job, not a long-running service, so sizing it isn't traffic-sensitive the way the rest of this list is. Grafana Alloy, Grafana, Metabase, the Support Bastion, and the Dev Machine aren't covered either; size those against your own retention and usage needs rather than request volume.
Dashboard is capped at 4 vCPU rather than scaling 1:1 with the others. It's an internal UI, not on the hot request path, so it doesn't need to grow with fingerprint/scoring traffic.
The sizing above is per node. Recommended topology, not a single instance of each:
- Postgres: 1 writer + 1 reader, each sized per the table above
- Valkey: clustered with replication (mirroring), not a single node
Before You Provision
Infrastructure Basics
- Instance architecture: linux/amd64 (x86-64) only. Don't provision ARM instances (e.g. AWS Graviton) for Mubeen containers
- Time zone: set every host and container to UTC
- Resource sizing: see Rough Sizing Guide above for starting points
Network & Edge
- WAF rules: baseline rule set for the Public LB's fingerprint endpoint and for the Private LB's internal endpoint, confirmed with Mubeen
- TLS certificates: for both the Public LB's public endpoint and the Private LB's internal endpoint
- DNS: public hostname for fingerprint traffic, private/internal hostnames for the dashboard and Rule Engine
- Outbound connectivity: see Network Access Requirements; confirm exact destination hosts to allowlist with Mubeen (license validation, image pulls); route this traffic through a NAT gateway or egress proxy so the allowlist is actually enforced, not just documented
- Network segmentation: three subnets, not two. Separate the App Layer (Fingerprint Collector, Rule Engine, ML Scoring, Dashboard, DB Schema Manager), the Data Layer (Postgres, Valkey), and a Management Layer (Support Bastion, Dev Machine, Grafana Alloy, Grafana, Metabase), with security groups that block direct east-west traffic beyond what's actually needed (the internet-facing Fingerprint Collector shouldn't be able to reach the Rule Engine directly, and the Management Layer's egress to the Data Layer should be scoped to Postgres/Valkey only, not the App Layer)
- Rule Engine + Dashboard isolation: confirm both sit only behind the Private LB, with no path from the public internet or the WAF-fronted listener
- ML Scoring isolation: confirm it isn't reachable from either load balancer or anything other than Rule Engine
Data & Backups
- Backup strategy: for Postgres (decisions/rules data is not disposable)
- Schema migration process: decide how
db-schema-managerruns in your environment (manual step, CI/CD job, Helm hook) and who holds its elevated DB role - Per-service database roles: least-privilege Postgres roles per container, not one shared
superuser credential;
db-schema-manager's DDL role is the one deliberate exception - Encryption at rest (required, not optional): the Postgres and Valkey volumes must both be encrypted (cloud-disk encryption or equivalent); this applies to backups too
- Encryption in transit (every hop, not just the public edge):
- Public LB and Private LB → app containers (backend/target-group TLS, not just the client-facing certificate)
- App containers → Postgres (
sslmode=require) - App containers → Valkey (
rediss://) - Dashboard → Rule Engine, and Rule Engine → ML Scoring (
https://on both, not plainhttp://) - Support Bastion → Postgres and Valkey (
sslmode=verify-fullwith a pinned CA, not justrequire;requireencrypts but doesn't validate the server certificate, and a standing vendor VPN connection is exactly the hop that shouldn't rely on "inside the VPC" trust alone) - Dev Machine → Postgres (
sslmode=verify-fullwith a pinned CA, same reasoning as the bastion) - Grafana Alloy → Grafana, and app containers push telemetry to Alloy's ingest endpoint
Access Control & Hardening
- Grafana/Metabase hardening: change Grafana's default admin password before first boot; keep both reachable only over your internal network, never a public port
- Dev machine access: SSH gated behind your VPN (not exposed directly to the internet), a scoped read-only DB role for ML training instead of full production credentials, and outbound internet restricted to package/dependency registries (egress proxy or allowlist), not open access
- Support bastion: provision the VPN gateway Mubeen support connects through; see Mubeen Support Access for the required scoping and MFA controls
Access & Credentials
- Container registry access: credentials to pull Mubeen's images
- LLM API key: an OpenAI-compatible API key/endpoint for the Dashboard's AI-assisted analysis (OpenAI, Azure OpenAI, or a self-hosted/proxied OpenAI-compatible endpoint)
- Email/SMTP credentials: for the Dashboard to send OTP login codes, reports, and notifications (your own SMTP server, or a provider like SES/SendGrid/Postmark)
- Secrets management: DB credentials, API keys, the LLM key, and SMTP credentials
Where to Go Next
- Architecture Overview: the hosted-API model, if self-hosting isn't required
- Server-Side Integration: the request/response contract your backend uses to talk to the Rule Engine container (same schema as the hosted API)