Skip to main content

Production Stack Architecture

A complete view of the minicloud production platform — 5-node on-premises k3s cluster running Phase 0–90 workloads across infrastructure, security, observability, AI/ML, automation, and collaboration layers.

:::tip Interactive diagram Open the full diagram in your browser for PNG/PDF export and zoom-level control:

View full architecture diagram → :::


Platform at a Glance

DimensionValue
Nodes4 ThinkPads + MacBook Pro 2012 (Ubuntu 22.04)
k3s versionv1.36.3+k3s1 (all 5 nodes, upgraded 2026-08-13)
ArgoCD apps78 live applications
PrometheusRule objects53 (monitoring ns + podinfo)
Grafana dashboards43+
GitOps repos11 (all → Harbor via Tailscale)
Phases complete0–90

Architecture Layers

Access Layer

All traffic enters through a single MetalLB IP (10.0.0.200). Two paths:

  • Public (devandre.sbs): Cloudflare CDN → Cloudflare Tunnel (k8s Deployment, 2 replicas, cloudflare-tunnel ns, anti-affinity spread across workers)
  • Internal/VPN: Tailscale → NGINX Ingress directly via *.10.0.0.200.nip.io

Every application is protected by Authentik SSO (OIDC + TOTP) via NGINX forward-auth ingress annotations. cert-manager handles TLS for both Let's Encrypt (public) and the minicloud root CA (internal).

:::note Cloudflare Tunnel — HA since 2026-08-13 Cloudflare Tunnel moved from a controller systemd service to a 2-replica Kubernetes Deployment (PRs #678+#679). Tunnel remains active during controller reboots. SSH backup path: controller.devandre.sbs → ssh://10.0.0.1:22 (PR #680). :::

GitOps & Automation

Developer push → GitHub Actions (CI)
├── Build → Trivy scan → Cosign sign → syft SBOM
├── Push → Harbor (via Tailscale tag:ci)
└── bump-gitops (GPG-signed PR) → ArgoCD auto-sync

├── canary-load (parallel) — k6 5 min sustained
└── rollout-gate (parallel) — polls Rollout phase

└── promote-staging (needs both pass)

ArgoCD manages 78 apps via the app-of-apps pattern from minicloud-gitops. All Helm values live in helm-values/, never in minicloud-ansible/.

Argo Rollouts handles progressive delivery:

  • platform-demo: Canary 50% → AnalysisRun (Prometheus gates) → 100%
  • minicloud-plane: BlueGreen prePromotion (health) + postPromotion (Prometheus)

Security

ComponentRole
Vault (HashiCorp CE)PKI, secrets, AWS KMS auto-unseal, fast-heron pinned
ESOExternalSecrets from Vault KV to k8s Secrets
Gatekeeper OPA18 constraints: require-non-root, no-privilege-escalation, no-hostpath, block-capabilities, require-seccomp, and more
FalcoRuntime syscall monitoring, DaemonSet
cert-managerLet's Encrypt (public) + minicloud CA (internal)
VaultwardenTimshel fork 1.34.1-6, SSO via Authentik
NetworkPoliciesdefault-deny-ingress on all namespaces
kube-benchk3s-CIS-1.7: 16/16 PASS on all 4 workers
Chaos MeshFault injection for Game Day reliability testing
cosign + SBOMSupply-chain: every staging/prod image is signed + has SBOM attached

AI / ML Platform

User-facing AI interfaces:
Open WebUI (chat.devandre.sbs) — conversational chat, RAG, model switching
Flowise 2.2.7 (flowise.devandre.sbs) — visual LLM flow builder, no-code pipelines

└→ LiteLLM Proxy (2 replicas, KEDA auto-scale)
├→ vLLM v0.6.6 (Phi-3-mini, on-cluster)
├→ Groq API (llama-3.1-8b-instant, primary for phi3-financial)
├→ OpenAI text-embedding-3-small (embeddings, 1536-dim)
├→ DeepSeek, Mistral (cloud, via LiteLLM routes)
└→ minicloud-agent / minicloud-crew-agent (OpenAI-compat endpoints)

RAG pipeline:
Document → markitdown-proxy (PDF/Office/images → Markdown via Docling)
→ rag-ingest (chunk → embed → store)
→ LiteLLM → OpenAI text-embedding-3-small (1536-dim)
→ Qdrant (policy-docs, incident-reports, regulatory collections)

AI agents:
minicloud-agent 1.0.1 — LangChain single-agent, MCP tool layer
minicloud-crew-agent 1.0.1 — CrewAI multi-agent, insurance workflow automation

Observability:
Langfuse: TTFT, tokens/s, cost/request, prompt eval scores, 90-day trace retention
LiteLLM Prometheus: litellm_proxy_total_requests_metric_total, input tokens
vLLM dashboard: vllm-inference-v1 (7 panels, ServiceMonitor in ai ns)

Specialised AI services (all in ai namespace, managed under litellm ArgoCD app):

ServiceVersionPurpose
vLLMv0.6.6GPU-optimised LLM inference (CPU on-cluster)
MLflowv2.16.0-psycopg2Experiment tracking, model registry
Flowise2.2.7Visual LLM flow builder
minicloud-agent1.0.1LangChain agent + MCP
minicloud-crew-agent1.0.1CrewAI multi-agent
PresidiolatestPII detection + anonymisation
Langfuse3.xLLM observability (ClickHouse backend)
Open WebUIcustomChat interface, French BM25 RAG
Qdrant1.xVector store (RAG collections)

phi3-financial PromptOps pipeline: routes through groq/llama-3.1-8b-instant (primary, weight 10) with vLLM phi3-mini as fallback. 25/25 CI eval cases pass.

Automation & Workflows

ServiceNamespaceURLNotes
n8n 2.32.7automationn8n.devandre.sbsPhase 83, 5Gi Longhorn RWO
Temporal v1.31.2temporaltemporal.devandre.sbsPhase 84, 4 historyShards (PERMANENT), PostgreSQL backend

Observability

The observability stack uses five complementary tools:

ToolPurposeKey metric
Prometheusmetrics collection + alerting53 PrometheusRule objects
Grafanadashboards + explore43+ dashboards, TraceQL, LogQL
Lokilog aggregationOTLP native, 30-day retention, LogQL ruler
Tempo 2.9.0distributed tracingTraceQL, tracesToLogsV2, 14-day retention
OTel Collectorlog + trace shippingDaemonSet, OTTL transforms, replaces Promtail
Alertmanageralert routing→ Stalwart:587 → SES → kanmegnea@devandre.sbs + healthchecks.io watchdog

Log pipeline (Gaps 1–7 closed):

  • Promtail replaced by OTel Collector DaemonSet (otelcol-contrib 0.156.0)
  • OTTL transformprocessor: Kopia job cardinality collapsed, level label extracted and normalised
  • Controller logs via Promtail Docker container (--net=host for MetalLB routing)

SLO & Regression Detection:

  • 7 SLO objectives, 11 recording rules, burn-rate 2-tier alerts
  • 5 regression alerts comparing current vs offset 1h baseline (latency, error rate, throughput, CPU, memory)
  • RolloutAutoRollback alert on argo_rollout_info{phase="Degraded"} == 1 for 1m

Operational resilience:

  • healthchecks.io: 3 checks — Alertmanager watchdog (cc3f232b), controller heartbeat (b2ac9ab5), recovery failures (6a37bfa6)
  • minicloud-ops: Python recovery check, 15 health probes, systemd-managed on controller

Collaboration & Business Apps

ServiceURLNotes
Stalwart Mailmail.devandre.sbsv0.16.13, JMAP API, RocksDB
AWS SESeu-west-1outbound relay + inbound pipeline (production approved)
Matrix + Elementelement.devandre.sbsSynapse v1.156.0, dedicated postgresql-synapse in chat ns
ERPNexterp.devandre.sbsv16.28.0, French insurance config (PCG 2025, TSCA, LOBs, Factur-X)
Jitsi Meetmeet.devandre.sbsv2.21.0, JVB pinned star-kitten, TURN = Lightsail 54.171.137.209
Nextcloudfiles.devandre.sbsOnlyOffice DocumentServer, SSO
Plane CEplane.devandre.sbsproject management
Vaultwardenvault-pw.devandre.sbsTimshel fork (SSO button), human credential store
Docusealsign.devandre.sbse-Signature (eIDAS Simple), 4 insurance templates
Flowiseflowise.devandre.sbsvisual LLM flow builder (ai ns)
MLflowmlflow.devandre.sbsML experiment tracking + model registry (ai ns)
n8nn8n.devandre.sbsworkflow automation
Temporaltemporal.devandre.sbsdurable workflow engine v1.31.2

Email flows (bidirectional, 2026-07-26):

  • Outbound: Stalwart SMTP:587 → ses-relay → SES eu-west-1 → recipient
  • Inbound: External → MX inbound-smtp.eu-west-1.amazonaws.com → SES Receipt → S3 → SNS → SQS → ses-ingest pod → Stalwart → mailbox

Storage

StoreLocationUsed by
LonghornDistributed (swift-mac preferred)All stateful workloads with RWO/RWX PVCs
MinIOMAAS Controller (Docker)Velero backup bucket, kine SQLite backups, S3-compat
Qdrantai ns (Longhorn PVC)RAG vector collections (1536-dim text-embedding-3-small via OpenAI)
PostgreSQL-aiai nsMLflow, LiteLLM cache, RAG metadata
postgresql-synapsechat ns, set-hog pinnedMatrix-synapse dedicated DB (migrated 2026-08-07)
ClickHouselangfuse nsLLM trace storage
MariaDBerp nsERPNext data

Backup layers:

  • Velero (daily): Longhorn snapshots → MinIO velero/ bucket (local)
  • Velero off-site (weekly): Cloudflare R2 minicloud-velero-offsite bucket (72h TTL)
  • kine SQLite (daily): sqlite3 .backup → MinIO k3s-backup/ + controller timer → db-backups/kine/

multipathd gotcha (all 5 nodes): IET VIRTUAL-DISK devices blacklisted in /etc/multipath.conf to prevent Longhorn iSCSI volumes from being claimed as mpatha.


Hardware

NodeIPRoleNotes
MAAS Controller (ThinkPad X390)100.88.123.8NAT, MAAS, Tailscale, MinIO, cloudflared systemd (SSH backup path)98G NVMe
set-hog10.0.0.2k3s control-planeKine/SQLite, socat proxy for scheduler+ctrl-mgr metrics
fast-skunk10.0.0.4k3s workerGeneral workloads, NVMe-first boot (BIOS)
fast-heron10.0.0.7k3s workerVault pinned (nodeSelector)
star-kitten10.0.0.8k3s worker (ai,worker)Jitsi JVB pinned (hostNetwork), vLLM inference
swift-mac10.0.0.10k3s worker (storage,worker)MacBook Pro 13" 2012, Ubuntu 22.04, Longhorn preferred

Boot order: Controller (30s) → cluster nodes (2 min) → Tailscale on Mac.

NVMe boot fix (2026-08-13): All 4 ThinkPads boot NVMe-first. fast-skunk via BIOS; fast-heron/star-kitten/set-hog via efibootmgr --create.

Power failure recovery: NAT is automated (restore-cluster-nat.service on controller — no manual step needed). MinIO requires a manual restart after disk-full events (caches error state in memory):

ssh controller "docker restart minio"

Key Design Decisions

Why on-premises? Portfolio-grade work demonstrating infrastructure ownership, cost awareness, and operational discipline beyond managed cloud. All compute, networking, and storage decisions are explicit.

Why app-of-apps? Single ArgoCD application (minicloud-gitops) owns everything. New services are added by dropping an apps/<name>.yaml file — no manual ArgoCD config.

Why Harbor vs ghcr.io? All custom images stay on-premises via Tailscale. Air-gap-capable registry with Cosign signature verification, SBOM storage, and CVE scanning in a single tool.

Why OTel Collector instead of Promtail? Stack-agnostic — only the exporter block changes when swapping backends. OTTL transforms handle cardinality normalization and label extraction without coupling to Loki's scrape config format.

Why Stalwart + SES instead of a managed email provider? Full control over deliverability, DKIM rotation, and JMAP automation. SES production access (50k msg/day) eliminates sandbox restrictions at near-zero cost.

Why Qdrant over pgvector? Dedicated vector DB with named collections, HNSW indexing, and snapshot API. pgvector is still running in postgresql-ai for legacy RAG metadata; new collections use Qdrant with OpenAI text-embedding-3-small (1536-dim, routed via LiteLLM).

Why Cloudflare Tunnel in k8s (not systemd)? 2-replica Deployment with pod anti-affinity eliminates the ~90s devandre.sbs outage that occurred on every controller reboot. Cloudflare load-balances across connectors automatically.