MDAPI Homelab¶
A self-hosted, production-grade Kubernetes homelab running on bare metal — built around GitOps, zero-trust secrets, and modern DevSecOps practices.
What is this?¶
This site documents the architecture of MDAPI (Martino Dell'Ambrogio's Personal Infrastructure), a personal homelab that runs a full-stack cloud-native environment at home. It is intended as a reference for architecture decisions, security practices, and operational patterns — not a tutorial, but a real-world example of how these technologies fit together.
For the longer story of how MDAPI started on an ISDN line in the 1990s and arrived at its current shape, see About MDAPI.
The stack is built to mirror enterprise-grade principles: declarative GitOps configuration, hardware-backed secret management, automated certificate lifecycle, container image CVE scanning, runtime threat detection, and a multi-cluster management plane.
Stack at a Glance¶
| Layer | Technology |
|---|---|
| Kubernetes distribution | Harvester HCI (RKE2 + KubeVirt) |
| Cluster management | Rancher |
| GitOps | Fleet (multi-cluster) |
| Ingress | rke2-ingress-nginx (RKE2-bundled) + ModSecurity WAF |
| TLS & PKI | cert-manager + Let's Encrypt, Evertrust Horizon/Stream CLM + internal PKI |
| Storage | Longhorn (block on FusionIO, 2-replica default / 3-replica for core infra) + Ceph (in-cluster via Rook, 3-replica — bulk RWX, block, and every S3 bucket through RGW) |
| Secrets | Akeyless with customer fragment on CipherTrust Manager (+ OpenBao failover plane) |
| Identity | Keycloak (OIDC/SSO) + OpenLDAP |
| Admission control | Pod Security Standards (baseline) + native ValidatingAdmissionPolicy in CEL — no admission webhook |
| Runtime detection | Falco — modern eBPF, no kernel module, least-privileged — details |
| Posture & compliance | CIS Kubernetes Benchmark weekly + continuous Pod Security, RBAC and segmentation probes — one Grafana dashboard per control — security overview |
| DNS | five planes — BIND · Technitium · unbound · OpenNIC · NextDNS |
| docker-mailserver — self-hosted platform | |
| Member services | portal & marketplace at start.mdapi.ch |
| Image updates | Keel (floating tags, digest-based polling) + Renovate (pinned tags, raised as merge requests) |
| CI/CD | GitLab (self-hosted EE) with Grype+Syft CVE scanning — and every running image rescanned four times a day (supply chain) |
| Observability | VictoriaMetrics stack (vmagent · vmsingle · vmalert · vmalertmanager) + standalone Grafana · Cribl + OpenObserve (indexed logs · traces · RUM, correlated by service) |
| Automation | Windmill (CI/CD, backups, and infrastructure health automation) |
| Task tracking | beads (Dolt-backed issue tracker, web UI + automation API) |
| Edge router | BPI-R4 running OpenWrt 25.12 (custom fork) |
| LAN fabric | 2× Cisco Catalyst C9300-24T (StackWise, 16× SFP+, STP root) + XikeStor 2.5G-PoE AP leaf |
| External access | Per-service DNAT across multiple public WAN IPs (each service its own IP:port) + haproxy (TLS termination for select TCP services) |
| IPv6 edge | NAT64 (Jool) into the v4-only cluster + native-v6 public DNS via dnsdist |
Architecture at a Glance¶
Five tiers, separated by responsibility. The edge terminates external traffic; the cluster runs every workload; storage and secrets are addressed as distinct planes rather than per-service concerns. Each subsequent page in this site drills into one slice of the diagram.
flowchart TB
subgraph ext["External"]
clients["Clients<br/>web · mobile"]
offsite["Off-site backup<br/>Backblaze B2 + SFTP"]
end
subgraph edge["Edge — BPI-R4 (custom OpenWrt fork)"]
dnat["Per-service DNAT + haproxy<br/>(TLS termination for select services)"]
nat64["Jool NAT64 + dnsdist<br/>IPv6 edge"]
end
subgraph k8s["Harvester HCI — RKE2 + KubeVirt — qui · quo · qua"]
plat["Platform<br/>Rancher · Fleet · cert-manager<br/>ESO · Keel · Reloader"]
ingress["Ingress + WAF<br/>rke2-ingress-nginx + ModSecurity"]
apps["Applications<br/>GitLab · Nextcloud · Paperless · Keycloak<br/>Home Assistant · Plex · Joplin · …"]
obs["Observability<br/>VictoriaMetrics + vmalert<br/>Grafana · Cribl + OpenObserve"]
end
subgraph storage["Storage"]
longhorn["Longhorn<br/>in-cluster block on FusionIO"]
ceph["Ceph (Rook)<br/>in-cluster bulk · RWX + S3"]
rgw["Ceph RGW<br/>S3 — live stores + backup tier"]
end
subgraph sec["Secret store"]
akeyless["Akeyless SaaS<br/>+ CipherTrust customer fragment<br/>(on-premise)"]
end
clients --> dnat --> ingress --> apps
clients -->|"IPv6"| nat64 --> ingress
plat -.->|"declarative"| apps
plat -.->|"declarative"| ingress
apps --> longhorn
apps --> ceph
apps --> rgw
longhorn -->|"backup"| rgw
rgw -->|"daily rclone"| offsite
apps -->|"VolSync restic<br/>client-side encrypted"| offsite
apps -.->|"ExternalSecret"| akeyless
obs -.->|"observe"| apps
Solid arrows are request and data flow; dashed arrows are the management and observability planes (declarative reconcile, secret fetch, metric and log scrape).
Cluster Contexts¶
Two Kubernetes clusters, both managed through Rancher and Fleet:
| Cluster | Location | Purpose |
|---|---|---|
mdapi-mgmt |
In-cluster VM trio (Rancher management, HA) | Fleet controller, cluster-scoped resources — the previous external Rancher instance is kept as a cold standby |
mdapi-prod |
Bare metal (homelab) | All production workloads |
Design Principles¶
Everything is declarative. All cluster state lives in the mdapi/fleet GitLab repo at https://gitlab.mdapi.ch/mdapi/fleet (public mirror). No kubectl apply is ever run directly — every change is a git commit.
Secrets never touch git. Secrets are stored in Akeyless SaaS with an on-premise customer fragment (the plaintext never leaves the local network). Kubernetes workloads fetch them via the External Secrets Operator at runtime.
TLS everywhere, automated. cert-manager issues certificates via Let's Encrypt — DNS-01 (RFC 2136 against a self-hosted BIND9 authoritative nameserver with TSIG) for the public domains, and HTTP-01 for names under the internal-only home.tillo.ch zone.
Security is layered. ModSecurity WAF on all ingresses, OIDC for user-facing apps, network-level protocol demultiplexing at the edge, Pod Security admission, CVE scanning both in every CI pipeline and against every running image, runtime detection with Falco, and the CIS Kubernetes Benchmark measured weekly with an alert on regression. Each layer is expected to hold on its own when another fails — see the Security Overview.
Measured, not assumed. A control counts only if something would notice it stop working. Coverage is derived from live state by probes rather than read from configuration, and recovery paths — an off-site restore, a secrets-store failover — are rehearsed on a schedule. A green deployment proves only that something was deployed.
Live surfaces¶
This site is the deep dive. The platform also exposes two front ends and a public status page:
- mdapi.ch — the public showcase: what the platform runs, the sites it hosts, and the open-source projects behind it.
- start.mdapi.ch — the members' portal: family & friends sign in via Keycloak SSO to a personalised set of apps, plus a self-service marketplace to request a mailbox, website, file space or subdomain.
- Service status — live status of the ten public-facing service groups, with incident history and subscribe-by-email/Slack/RSS.
Source¶
- Fleet GitOps repo:
https://gitlab.mdapi.ch/mdapi/fleet(public mirror) - This documentation: docs.mdapi.ch · source at
https://gitlab.mdapi.ch/mdapi/docs