Architecture Decisions

ADR-0008: Self-hosted Convex on k3s

Documents the decision to self-host Convex backends on the k3s cluster

ADR-0008: Self-hosted Convex on k3s

Status

Accepted

Date: 2026-03-05

Context

Multiple apps in the monorepo use Convex as their backend platform (CalNexus, Lexilink, Planex, Nexus LMS, Archus, BGS-Service, Elbe-Akustik). Convex provides real-time sync, serverless functions, and a document database that significantly accelerates development.

However, running 7 Convex deployments on Convex Cloud raised concerns:

  • Cost: Convex Cloud pricing scales per-deployment. With 7 apps (and growing), costs become unpredictable and significant
  • Data locality: EU data residency requirements for client projects (BGS-Service, Elbe-Akustik) are not guaranteed on Convex Cloud
  • Vendor lock-in: Infrastructure-level dependency on a single SaaS provider for all backend functionality
  • Control: Limited ability to tune performance, manage backups, or customize the deployment topology

Key constraints:

  • Must maintain the same developer experience (Convex CLI, dashboard, real-time sync)
  • Each app needs its own isolated Convex instance
  • Must integrate with existing 1Password secret management
  • Must work within the single-node k3s cluster

Decision

Self-host Convex on the k3s cluster with the following architecture:

Per-App Isolation

  • Each app gets its own Convex StatefulSet in the convex namespace
  • StatefulSets use OnDelete update strategy for safe, manual upgrades
  • Persistent volumes ensure data survives pod restarts

Networking

  • Each Convex backend is exposed via Traefik ingress at https://convex-{app}.hansenexus.dev
  • Lexilink production uses a custom domain: https://api.lexilink.app

Secret Management

  • Admin keys stored in 1Password (convex-{app}-{env} naming convention)
  • 1Password Operator syncs admin keys to Kubernetes Secrets
  • CI deploys Convex functions using admin keys from GitHub Actions secrets

Dashboard Access

  • Convex dashboards protected via Traefik BasicAuth middleware
  • Dashboard URLs follow the same convex-{app}.hansenexus.dev pattern
  • Production: convex namespace
  • Staging: hn-staging namespace (separate StatefulSet)
  • Preview: hn-preview namespace (scales to 0 on PR close, PVC preserved)

Consequences

Positive

  • Cost is predictable and fixed (server cost only, regardless of number of apps)
  • Full EU data residency on Hetzner servers in Germany
  • Complete control over upgrades, backups, and performance tuning
  • No per-deployment pricing allows adding new apps freely
  • Convex developer experience (CLI, real-time sync, dashboard) is preserved

Negative

  • Self-managed upgrades require manual intervention (OnDelete strategy means deleting pods to trigger updates)
  • Backup strategy must be implemented and maintained independently
  • Monitoring Convex health is an additional operational responsibility
  • StatefulSet management adds complexity compared to managed cloud
  • Single-node cluster means no HA for Convex instances

Neutral

  • Convex functions are deployed via the same bunx convex deploy command regardless of hosting
  • The CONVEX_SELF_HOSTED_URL and CONVEX_SELF_HOSTED_ADMIN_KEY environment variables replace Convex Cloud’s authentication
  • Dashboard provides the same functionality as Convex Cloud’s dashboard

Alternatives Considered

Alternative 1: Convex Cloud

  • Description: Use Convex’s managed cloud hosting for all deployments
  • Pros: Zero operational overhead, automatic scaling, managed backups, official support
  • Cons: Per-deployment pricing scales poorly with 7+ apps, no EU data residency guarantee, vendor lock-in on infrastructure
  • Why not chosen: Cost becomes significant at scale; data locality requirements for client projects

Alternative 2: PlanetScale + Supabase

  • Description: Replace Convex with a traditional database (PlanetScale for MySQL) plus Supabase for real-time
  • Pros: Mature, widely adopted, strong ecosystem
  • Cons: Completely different paradigm from Convex, loss of real-time sync out of the box, massive migration effort, two services to manage instead of one
  • Why not chosen: Would require rewriting all backend logic across 7 apps; Convex’s real-time document model is integral to the architecture

References

Notes

  • Convex version upgrades should be tested on a non-critical app (e.g., Planex) before rolling out to all apps
  • The OnDelete update strategy means pods must be manually deleted to pick up new images after a StatefulSet update
  • PVCs are preserved when preview environments scale to 0, allowing data to persist across PR lifecycles
  • Elbe-Akustik’s Convex instance runs on hn-hub rather than k3s due to client-specific requirements
HanseNexus 2026