How Obvelum is built

Obvelum is a privacy-first hiring platform: companies discover and approach candidates, and candidates stay anonymous until they choose otherwise. This is a high-level tour of the engineering behind it, kept at the level of how the system is shaped and why. All of it was designed, built, and is operated by one engineer.

System shape

The backend is a set of event-driven Go microservices, each owning its own data and talking to the others over an internal mesh. The split follows domain boundaries: profiles and candidate data, companies and job posts, the skills and matching engine, and transactional email each live in their own service with their own database credentials.

Three React single-page apps sit in front: the candidate portal, the employer portal, and an internal administration console. Everything runs on Azure Kubernetes Service, provisioned entirely as code, so any environment can be rebuilt from its Git history.

The skills and matching engine

The part that makes Obvelum more than a database of CVs is how it reasons about skills. Free-text skills are normalised and placed on a semantic map, so related capabilities fall into the same family and matching can work on meaning rather than raw strings.

A match is not a keyword count. Each required skill is compared at proficiency level and years of experience against what a role actually needs, split into must-have and optional coverage, and rolled up across skill families. The output a hiring manager sees is a gap-and-strength breakdown: where a candidate is strong, where they fall short, and which adjacent roles they map to.

Database access is generated from typed SQL, and the schema evolves only through versioned migrations, which keeps the data layer honest as the model grows. The reasoning behind this approach is laid out in Why recruitment needs a better first filter.

Identity and privacy by design

Authentication is delegated to a dedicated, standards-based OIDC provider (Zitadel) rather than built in-house, which is the safer choice for something as sensitive as identity. Users who prefer it can also sign in with Google. Sessions use short-lived access tokens that renew transparently in the background, which keeps the window of a stolen or revoked session small, and are explicitly invalidated on security-sensitive events such as a password reset or an MFA change.

Privacy is enforced by the data model itself. Candidate names are not stored; a past employer is shown to companies as a category, never a name, until the candidate accepts a conversation. The most sensitive fields are encrypted at the application layer before they ever reach the database, each person's and each company's under a key of their own (envelope encryption: the keys are held in Azure Key Vault, never in the database, and every read of one is logged). Deleting your account destroys your personal key, and data-subject rights (export, deletion, rectification) are self-service.

Product analytics are self-hosted and cookieless (Umami): no personal data is stored, and there are no third-party advertising or tracking scripts anywhere on the platform.

Security posture

Service-to-service traffic runs over a mesh enforcing strict mutual TLS, so every internal call is mutually authenticated and encrypted with short-lived, automatically rotated identities. There is no plaintext between services and no long-lived certificates to manage, and internal-only endpoints are reachable only by in-cluster callers.

Public traffic is proxied through a hardened edge (Cloudflare) with a managed WAF and DDoS protection, concealing the origin. Secrets live in a managed vault and are injected at runtime through workload identity, never baked into images. Access follows least privilege, with role-based authorization enforced centrally on every request.

Least privilege extends to the runtime itself. Every workload we run, including the supporting third-party components, runs as an unprivileged user on a read-only filesystem with every Linux capability dropped, so a process that was somehow compromised could not install tooling, alter the running image or escalate its own permissions. Each one is pinned to an exact, verified image version rather than a moving tag, so the image we tested is the image that runs.

Those preventive controls are backed by detection. Runtime behaviour inside the cluster is watched at the kernel level: system calls are inspected in real time on every node, so an unexpected shell, an attempt to read credentials from a running container, or a binary dropped into one and executed is surfaced as it happens rather than discovered in a later log review. This operates below the application, which means it sees activity that produces no application log at all.

AI-driven features (CV extraction, summaries, assessments) carry explicit prompt-injection defences that reject and log malicious instructions. The platform has been through external penetration testing and several hardening cycles; responsible-disclosure researchers are credited on the credits page.

Service levels and operations

The platform runs to a 99.5% availability SLO, with live and historical uptime published openly at status.obvelum.com.

Recovery targets are explicit. The platform aims for a recovery point objective under 15 minutes, backed by continuous write-ahead-log archiving plus daily base backups, and a recovery time objective under 8 hours for a full rebuild. Because the whole stack is defined as code, a complete environment can be reconstructed from Git and its backups, with copies replicated to a second geographical region.

Databases run under a Kubernetes-native Postgres operator with redundant clusters across availability zones, and pod disruption budgets keep services up during maintenance and zone outages.

The cluster is built to run lean. Workloads sit across an on-demand node pool for the critical baseline and cheaper spot capacity for everything that can tolerate it, with autoscaling that grows and shrinks the node count to match real demand. A watchdog reacts to spot reclaim notices and drains and reschedules affected pods ahead of eviction, so the cost saving does not cost availability.

Logs and metrics from every service and the ingress gateway are aggregated centrally (Grafana, Loki, Prometheus). Privileged actions are tied to a verified identity, IP and timestamp for a clean audit trail, and privileged or anomalous activity raises real-time alerts to an on-call channel. PII is kept out of logs by design.

How changes ship

Infrastructure is 100% Terraform; application changes go out through automated pipelines using passwordless workload-identity federation. Every change passes code review and automated gates: linting, tests, and dependency and container vulnerability scanning. Container images are verified by signature and checksum before they reach production.

Stack at a glance

Backend

GoMicroservicesEvent-drivensqlcFlywayPostgreSQL

Frontend

ReactTypeScriptViteTailwind CSSi18n

Platform

AzureAKS / KubernetesIstio (mTLS)CloudNativePGTerraformFalco (runtime detection)

Identity & edge

Zitadel (OIDC)Cloudflare WAFAzure Key VaultRBAC

Observability & analytics

GrafanaLokiPrometheusUmami (cookieless)Structured logging

Delivery

Azure PipelinesTrivygovulncheckcosign

Questions about any of this, or think something could be done better? .