Architecture
Lua inside Nginx, PostgreSQL underneath
OpsAPI is a Lapis application running inside OpenResty's Nginx workers, so there's no separate app server. Every request takes the same layered path, every business row carries its workspace, and a deployment loads only the modules its preset names.
The whole system
Clients reach one API through the edge. Inside the container a request passes six layers; outside it, PostgreSQL holds the data and a few services do the rest.
The stack
| Layer | What | Why |
|---|---|---|
| Runtime | OpenResty (Nginx + LuaJIT) | Lua runs in the worker processes: no proxy hop to an app server |
| Framework | Lapis | Routing, models and migrations for Lua |
| Database | PostgreSQL (with pgvector) | One database per deployment; workspaces separate tenants inside it |
| Dashboard | Next.js (App Router), React, Zustand | Menu and permissions come from the API |
| Cache and events | Redis | Chat and notifications across pods |
| Files | MinIO or S3 | Uploads, PDFs and media |
| Events | Kafka | Audit trail and outbox |
| AI | Ollama or a hosted model | Classification, extraction and chat agents |
| Monitoring | Prometheus, Grafana | /metrics on every pod |
Where things live in the repository
| Path | Contents |
|---|---|
lapis/ | The API: app.lua, routes/, queries/, models/, middleware/, migrations/, helper/, lib/ |
opsapi-dashboard/ | The Next.js admin dashboard |
projects/ | Pluggable project modules with their own routes and migrations |
sdk/typescript/ | The @opsapi/client TypeScript SDK |
devops/ | Helm charts, Kubernetes manifests, the demo stack |
start.sh | Local and server orchestration: compose, migrations, admin seed |
website/ | This site |
Deep dives
Each technote takes one part of the system and draws it out.
SAML single sign-on per workspace
Each workspace connects its own SAML 2.0 identity provider. How the sign-in round trip works, what OpsAPI checks in every response, and why an IdP can only vouch for its own workspace.
Keeping admin dashboards off the internet: VPN and zero-trust access
Customer apps are public; the dashboards that run the platform shouldn’t be. How to put OpsAPI admin, the edge admin, the vault UI, Grafana and the Kubernetes API behind a VPN or ZTNA broker, with OpsAPI sign-in and roles still on top.
Webhooks: sending workspace events to your own systems
How OpsAPI turns business changes into signed HTTP deliveries, how retries and ordering work, how to verify a request, and how the CMS “website update” webhook refreshes a static site.
Single sign-on for any app: the OpsAPI sign-in flow
How a Java app and a PHP app share one sign-in through OpsAPI: the hosted sign-in page, the nonce, the token hand-back and the one call that proves who the user is.
One request through OpsAPI: from nginx worker to PostgreSQL
What happens between a request arriving and the JSON going back: the global before_filter, the public allow-list, JWT and API-key checks, feature-gated routes, the middleware chain, the routes → queries → models layering and the two response envelopes.
Building a safe public demo login
Publishing a password for a real admin dashboard without leaking anyone's data or letting visitors break it: a separate workspace, a read-only role and a reset job.
PROJECT_CODE: one codebase, many products
How one environment variable decides which modules a deployment has: the presets, combining codes, why core is always on, and why every module must be gated in both app.lua and migrations.lua.
Extending OpsAPI: plugins and consumer products
How a folder of Lua files becomes a first-class OpsAPI module with routes, RBAC, dashboard pages, events, jobs and its own migrations, and how an external product ships its schema in its own image built FROM bwalia/opsapi.
Seeing inside OpsAPI: probes, metrics, errors and audit trails
The three health endpoints and what each one is for, the Prometheus metrics, the error envelope with its correlation id, sign-in and activity records, the Kafka outbox, and why the error catalog had to move into the core.
Workspaces and roles: how OpsAPI keeps tenants apart
How a request is tied to one workspace, what membership and module.action permissions decide, where platform admins and owners fit, and which of the three access layers is actually a hard boundary.
The modules map: what each PROJECT_CODE switches on
Every business module behind its feature flag, which deployment presets include which, how a preset decides what routes and tables exist, and how to call it all from TypeScript.
Running OpsAPI on Kubernetes: the Helm chart, piece by piece
What the OpsAPI chart creates, how secrets reach the pods, why migrations run as a pre-upgrade Job, how a rollout drains without dropping requests, and the start-up race we hit on a fast node.
Filing a UK tax return through HMRC Making Tax Digital
The six-step “File your tax” wizard, the HMRC endpoints and API versions behind it, the sandbox behaviour that is expensive to rediscover, and what production needs.
From DNS to pod: the edge in front of OpsAPI
How a request for an OpsAPI host travels: a DNS-only CNAME, an edge PoP that terminates TLS and picks a rule, the cluster ingress and the pod. Plus how opsapi.org itself is a GitHub Pages site behind the same edge.