Infrastructure¶
Overview¶
Three distinct infrastructure environments exist across the two product lines:
| System | Provider | Type | Access |
|---|---|---|---|
| KRO | DigitalOcean | Managed Droplet (VPS) | SSH to root@188.166.145.68 |
| GIV (KROGiving) | DigitalOcean | App Platform (PaaS) | DO Console / API |
| Pencom | On-Premises | Internal servers | VPN / internal network |
KRO — DigitalOcean Droplets¶
Servers¶
| Role | IP | Environment |
|---|---|---|
| Production / Pre-prod | 188.166.145.68 | Docker containers via kro-devops |
| Stage (inactive) | 64.226.94.111 | Commented out in CI pipeline |
Container Architecture¶
All services run as Docker containers managed via docker-compose in the kro-devops repository.
Production stack (kro-devops/production/docker-compose.yml):
nginx (stable-alpine) — reverse proxy, port 80/443
└── backend (kro-backend) — NestJS API, port 3000
└── frontend (kro-frontend) — React SPA
└── admin-frontend (kro-admin) — Admin React SPA
Local dev stack (kro-devops/local/docker-compose.yml): - frontend on port 8088 - backend-db (Postgres 11) on port 5432 - backend on port 3000 - admin-frontend on port 8089
Nginx¶
Domain routing via Nginx with Let's Encrypt TLS (/etc/letsencrypt/live/krotrust.com/):
| Domain | Target |
|---|---|
api.krotrust.com | http://backend:3000 (NestJS API) |
app.krotrust.com | http://frontend (user SPA) |
admin.krotrust.com | http://admin-frontend (admin SPA) |
app2.krotrust.com | http://frontend (alias) |
All HTTP traffic on port 80 is redirected to HTTPS (301).
WebSocket upgrades are enabled for api.krotrust.com (real-time notifications).
TLS Renewal¶
Certificates are managed by Certbot / Let's Encrypt. Stored at /etc/letsencrypt/ on the host and mounted into the Nginx container.
Databases (KRO)¶
| Database | Type | Provider | Notes |
|---|---|---|---|
| KRO main DB | PostgreSQL | DigitalOcean Managed DB | SSL required (CA cert mounted in container) |
| WhatsApp verifications | MongoDB schema (in PostgreSQL via TypeORM) | — | — |
The CA certificate file is mounted at /usr/src/app/kro-prod-db-cluster-ca-certificate.crt in the backend container.
CI/CD (KRO)¶
Repository: kro-devops/.github/workflows/main.yml
Trigger: push to main branch
Steps: 1. SSH into production droplet (188.166.145.68) using NEW_PREPROD_DROPLET_SSH_PRIVATE_KEY secret 2. cd /root/Kro/kro-devops && git pull origin main 3. docker system prune -f (clear disk space)
Note: The stage deployment (to
64.226.94.111) is commented out in the workflow. Currently only production is deployed automatically.
Application rebuild: After pulling updated devops config, you must manually rebuild and restart containers:
GIV (KROGiving) — DigitalOcean App Platform¶
Overview¶
KROGiving runs fully managed on DigitalOcean App Platform. There are no servers to SSH into; scaling, TLS, and routing are managed by DigitalOcean.
Components deployed to App Platform: - krogiving-backend — NestJS backend service - krogiving-frontend — React frontend static site - giv-admin-new — Admin dashboard static site
CI/CD (GIV)¶
Repository: krogiving-backend/.github/workflows/deploy.yml
Trigger: push any git tag (*)
Steps: 1. Calls DigitalOcean App Platform API via curl 2. Triggers re-deployment of the krogiving-backend component 3. Uses secrets: STAGING_APP_PLATFORM_ID, DIGITALOCEAN_ACCESS_TOKEN
Note: Only the backend is deployed via CI. The frontend apps may be deployed separately or via DO App Platform's built-in GitHub integration.
Databases (GIV)¶
| Database | Type | Provider |
|---|---|---|
| MongoDB | Document DB | DigitalOcean Managed MongoDB |
| PostgreSQL | Relational DB | DigitalOcean Managed PostgreSQL |
| Redis | Cache / queues | DigitalOcean Managed Redis |
File Storage (GIV)¶
DigitalOcean Spaces (S3-compatible) for campaign media: - SPACES_ACCESS_KEY_ID / SPACES_SECRET_ACCESS_KEY - SPACES_REGION, SPACES_BUCKET_NAME, SPACES_ENDPOINT
Cloudinary is used for video uploads (via useCloudinaryVideoUpload hook in frontend).
Pencom — On-Premises¶
Overview¶
The Pencom/eHub system runs entirely on-premises across four dedicated VPS nodes. All Docker containers are deployed via GitHub Actions to self-hosted runners on each VPS. There is no cloud provider dependency for eHub compute or databases.
VPS Topology¶
┌─────────────────────────────────────────────────────┐
│ VPS 1 — Internet-Facing (Ingress) │
│ Exposed to the public internet │
│ │
│ ├── Nginx (TLS termination, reverse proxy) │
│ ├── api-gateway :3000 │
│ └── external-gateway :3010 │
└────────────────────┬────────────────────────────────┘
│ Internal network only
┌────────────────────▼────────────────────────────────┐
│ VPS 2 — Core Services │
│ Not internet-accessible; internal network only │
│ │
│ ├── core :4000 │
│ ├── payments :5000 │
│ ├── compliance :6000 │
│ ├── notifications :7000 │
│ ├── external-integrations :8000 │
│ └── audit :9000 │
└────────────────────┬────────────────────────────────┘
│ Internal network only
┌────────────────────▼────────────────────────────────┐
│ VPS 3 — Databases │
│ Not internet-accessible; internal network only │
│ │
│ ├── PostgreSQL │
│ │ ├── pencom_core_db2 (core) │
│ │ ├── pencom_payments (payments) │
│ │ ├── pencom_compliance (compliance + │
│ │ │ external-gateway)│
│ │ ├── pencom_external_integrations │
│ │ └── pencom_audit (audit) │
│ ├── MongoDB │
│ │ ├── pencom_notifications (notifications) │
│ │ └── pencom_core_otp (core OTPs) │
│ └── Redis │
│ └── Bull queues + magic-link cache │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ VPS 4 — Object Storage │
│ │
│ └── MinIO (S3-compatible object store) │
│ ├── PCC certificates (PDFs) │
│ ├── Employer registration documents │
│ ├── PFC contribution CSVs │
│ └── Tutorial videos + thumbnails │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Existing PenCom On-Prem (managed by PenCom) │
│ │
│ └── Oracle ECRS DB │
│ Read-only access by core + compliance │
└─────────────────────────────────────────────────────┘
Service Ports¶
| Service | VPS | HTTP Port | Role |
|---|---|---|---|
| api-gateway | VPS 1 | 3000 | Public internet — employer & admin portal |
| external-gateway | VPS 1 | 3010 | Public internet — PFC partner API |
| core | VPS 2 | 4000 | Internal — employer/employee domain, Oracle bridge |
| payments | VPS 2 | 5000 | Internal — Remita payment state |
| compliance | VPS 2 | 6000 | Internal — contributions, penalties, PCC/GLI |
| notifications | VPS 2 | 7000 | Internal — email/SMS fan-out |
| external-integrations | VPS 2 | 8000 | Internal — CAC CRUD, PFC partner credentials |
| audit | VPS 2 | 9000 | Internal — append-only audit log |
Inter-service calls are HTTP with x-internal-api-key header. TCP microservice transport is declared on some services but only used internally within notifications for fire-and-forget email dispatch.
CI/CD (Pencom)¶
Deployment via GitHub Actions with self-hosted runners installed on the VPS nodes.
| Workflow | Trigger | Runner | What it does |
|---|---|---|---|
deploy-prod.yaml | Push to main, filter apps/** | prod-runner | Builds only changed apps via dorny/paths-filter, pushes Docker images, deploys |
deploy-staging.yaml | Push to staging, filter apps/** | staging-runner | Same shape, staging environment |
deploy-monitoring.yaml | Push to main or monitoring-deploy, filter monitoring/** | prod-runner | Runs Ansible playbook to provision Prometheus + Grafana + node-exporter |
Each app has its own Dockerfile at apps/<app>/Dockerfile.
Databases (Pencom)¶
| Database | Type | Host | Service |
|---|---|---|---|
pencom_core_db2 | PostgreSQL | VPS 3 | core |
pencom_payments | PostgreSQL | VPS 3 | payments |
pencom_compliance | PostgreSQL | VPS 3 | compliance + external-gateway |
pencom_external_integrations | PostgreSQL | VPS 3 | external-integrations |
pencom_audit | PostgreSQL | VPS 3 | audit |
pencom_notifications | MongoDB | VPS 3 | notifications |
pencom_core_otp | MongoDB | VPS 3 | core (OTP storage) |
| Redis | Redis | VPS 3 | compliance (Bull queues) + core (magic-link cache) |
| Oracle ECRS | Oracle | PenCom on-prem | core + compliance (read-only) |
File Storage (Pencom)¶
MinIO on VPS 4, accessed via the @aws-sdk/client-s3 SDK (MinIO is S3-compatible). Configure with SPACES_* env vars pointing at the MinIO endpoint.
| Bucket path | Contents |
|---|---|
CERTIFICATES/ | PCC PDF certificates |
EMPLOYER_CODE_REQUEST_DOCUMENT/ | Employer registration documents |
TUTORIAL_VIDEOS/ | Admin/employer tutorial videos |
TUTORIAL_THUMBNAILS/ | Video thumbnails |
| PFC upload namespace | Monthly contribution CSVs from PFC partners |
External Integrations (Pencom)¶
| Service | Purpose | Notes |
|---|---|---|
| Remita | Pension certificate fee payment processing | Nigerian payment gateway |
| PENCOM Oracle ECRS | Official employer/employee/PFA data source | Read-only; PenCom-managed |
| SendGrid | Email notifications (OTPs, certificates, alerts) | Via notifications service |
| Termii | SMS notifications | ⚠ Currently unused — no producer |
| Highlight.io | Error tracking + APM | Every service's main.ts |
| PostHog | Product analytics | api-gateway only, dev disabled |
Monitoring¶
Prometheus + Grafana stack provisioned via Ansible (monitoring/ansible/), deployed via deploy-monitoring.yaml.
- Node-exporter collects host-level metrics from the VPS nodes.
- ⚠ No application-level Grafana dashboards exist — only host metrics via node-exporter.
Network Security Notes¶
- VPS 1 is the only internet-facing node. VPS 2, 3, and 4 communicate only over the internal network.
- Nginx on VPS 1 handles TLS termination. All external traffic is HTTPS.
- All inter-service HTTP calls carry an
INTERNAL_API_KEYheader. Backend services on VPS 2 (core,notifications,audit) verify it viaApiKeyAuthGuard. Other services rely on network isolation. - JWT is used for end-user authentication (employer/admin). Separate JWT secret for external PFC partner tokens.
- Database connections do not use SSL (internal network only). Oracle connections are read-only.
- KRO database connections use SSL with CA certificate verification (DigitalOcean managed database).