Skip to content

Tech stack

Audience

PLANA staff, technical partners. The choices below are intentional and mostly long-standing; this page captures the why alongside the what.

PLANA's stack is FOSS-first and deliberately narrow. We pick one tool per job and stick with it across products. The rule is "fewer, deeper" rather than "one-tool-per-team".

Cloud and platform

LayerChoiceWhy
CloudExoscale, zone bg-sof-1 (Sofia, BG)EU data residency. Bulgarian provider. No US-cloud reliance.
ComputeExoscale instances + SKS managed KubernetesManaged K8s without the operational tax of running etcd ourselves
Object storageExoscale Simple Object Storage (S3-compatible)Same vendor, S3 tooling
Block storageExoscale block storage, retain reclaim policyPer-tool DBs survive PVC accidents
Load balancerExoscale NLBTCP-level; TLS terminates inside the cluster
DNSExoscale DNSAPI-controllable; cert-manager DNS-01 works
Email (outbound)Mailu on a separate Hetzner boxEmail reputation is non-trivial; cluster-cluster mail is bad practice

Containers and orchestration

LayerChoice
KubernetesExoscale SKS, currently v1.35.3
CNICalico, with NetworkPolicy enforcement on
IngressEnvoy Gateway v1.7.1 (Kubernetes Gateway API)
ProvisioningCrossplane composite resources (custom XRDs)
GitOpsFlux v2
Image registryForgejo registry at git.planapulse.com
Container runtimecontainerd (Exoscale default)
TLScert-manager + Let's Encrypt, DNS-01 challenge

Languages and frameworks

Backend — Node.js

ChoiceWhy
JavaScript ESM, not TypeScriptReduce build complexity; TS adds CI time, types we already test against runtime
Fastify v5 (not Koa, not Express)Best perf, schema-first, modern plugin model
RamdaFunctional utilities; consistent across services
BullMQRedis-backed queues; widely-deployed pattern

Backend — Python

ChoiceWhy
Python 3.12Most recent stable at the time the AI services started
FastAPIAsync, schema-first, OpenAPI generation built in
asyncpgNative async PostgreSQL driver, very fast
PyJWTStandard JWT library
Anthropic SDK (claude-opus-4-7 / claude-sonnet-4-6)Primary LLM provider
OpenRouterFallback router for non-Anthropic models

Backend — Odoo (Python, but separate world)

ChoiceWhy
Odoo Community 18.0Current default for new tenants
OCA modulesWhere the community already solved it, we use their solution
PLANA plana_* modulesThin layer over OCA + Bulgarian fiscal pack
PostgreSQLOdoo's only supported DB

Frontend

ChoiceWhy
Vue 3.5 + Nuxt 3The Vue ecosystem's "batteries included" framework, used for pulse-account, pulse-admin, pulse-portal
Vue 3 + vite-ssg (static)pulse-website — full static build, served by nginx, fast
Vue 3 + Vite SPA (no router)bos-portal — single-page app, in-memory view switching
VitePress 1.6This documentation portal
PiniaState management
JavaScript ESMSame rule as backend; no TypeScript
RamdaShared utility library

Mobile

ChoiceWhy
Ionic Vue + Capacitor 7Reuse the Vue frontend stack on iOS and Android
Plan, not yet builtFirst mobile artifacts targeted for 2026-Q3

Data and messaging

LayerChoice
Relational DBPostgreSQL (single VM at pg01)
Connection poolingpgbouncer, transaction mode
Cache + sessions + queueRedis (specifically Valkey 7), single instance
Event busRedis Streams (PLANA:events), CloudEvents 1.0
Search (in-app)PostgreSQL trigram + Odoo's own ORM search
Vector storepgvector inside pulse-data (768-dim sentence-transformer embeddings)
File storeNFS export at nfs01.planapulse.com
BackupsExoscale SOS (S3-compatible)
Future scaleKafka only if PLANA:events exceeds ~1M events/day

Auth and secrets

LayerChoice
Staff SSOAuthentik Community (self-hosted)
Tenant SSOplana_auth Odoo module against Authentik OIDC
Service-to-serviceX-API-Key header (per-service)
User-facing tokensJWT HS256, 15-min access + 30-day refresh, pa_token cookie
Secrets at restSOPS age encrypted YAML in infra/secrets/
Secrets mirrorVaultwarden (UI-friendly secondary copy)
2FAMandatory TOTP for staff, optional for tenant users

We deliberately do not use HashiCorp Vault. SOPS + Vaultwarden gives us the same security with one-tenth the operational complexity. The OpenBao POC explored a deeper secrets-management story and was parked.

Observability

LayerChoice
MetricsPrometheus + Grafana
LogsLoki (operator-grade), 90-day retention
Synthetic probesBlackbox Exporter
AlertsAlertmanager → Matrix room only (no email pages)
TracingNone currently; OTLP via Prometheus when needed
AuditIn-cluster ValidatingAdmissionWebhook → Loki

CI/CD

LayerChoice
Git hostForgejo at git.planapulse.com (not GitHub or GitLab)
CI runnersForgejo Actions runners in the cluster
Build imagesKaniko (rootless container builds)
Workflow templatesShared in ci-templates repo
DeployMostly Flux GitOps; a few legacy kubectl set image jobs

LLM and AI

LayerChoice
Primary LLMAnthropic Claude (claude-opus-4-7 for high quality; claude-sonnet-4-6 for routine workloads; claude-haiku-4-5 for low-cost tasks)
FallbackOpenRouter for non-Anthropic models when warranted
Embeddingssentence-transformers (768 dims) inside pulse-data
Agent frameworkIn-house (ai-agents repo, FastAPI + Anthropic SDK + tool registry)
StreamingSSE end-to-end, native to Anthropic SDK
Shadow ModeAll agents run with shadow_mode=True by default; writes simulated and logged for review

What we do NOT use

This list is short and intentional:

  • TypeScript — ESM + JSDoc is enough; the build cost isn't worth it
  • GraphQL — REST + Fastify schema gives us the same DX with less infra
  • Kafka — Redis Streams is plenty until we cross ~1M events/day
  • HashiCorp Vault — SOPS + Vaultwarden is simpler and adequate
  • Falco / runtime security agents — dropped in May 2026 in the simplification sweep; the audit-webhook + Loki gives us what we need
  • Bitnami / vendor-rebranded images — we use official upstream images or build our own (see FOSS first)
  • OpenTelemetry collector — Prometheus + Loki is enough; we will add OTLP later only if traceability gaps motivate it

Where to read more

  • Policies → FOSS first — the rule that drives several of the "no vendor X" choices
  • Services — per-service detail on which slice of this stack each one uses

© PLANA Digital Ltd.