Deployment topologies#
MAQPNA is one set of binaries with several deployment shapes. The shape is chosen by how you start it (maqpna dev up or the Helm chart) and by the chart profile: values.yaml (default), values-dev.yaml, values-production.yaml or values-sovereign-eu.yaml (maqpna install --profile dev|prod|sovereign-eu).
| Topology | Started with | Kubernetes | State backend | Replicas | Sandboxes | Typical use |
|---|---|---|---|---|---|---|
| Local dev | maqpna dev up |
None | Files in ./.maqpna |
1 process each | None: your agent runs as a local process | Building and testing an agent |
| Single cluster | maqpna install (default values) |
1.29+ with agent-sandbox | file |
1 each | tier-0, tier-1 (and tier-2 if enabled) | Pilots, non-production |
| HA | --profile prod |
Same | postgres |
gateway 3, identity 2, attest 2, operator 2 | Same | Production |
| Multi-tenant / service provider | Any cluster profile plus Tenant objects |
Same | postgres recommended |
As HA | Same, per tenant namespace | Hosting agents for customers |
| Air-gapped | Air-gap bundle + maqpna airgap push + install |
Same, no internet | Either | Either | Same | Disconnected and regulated sites |
| Confidential (attested) | --profile sovereign-eu |
Same, plus confidential-VM nodes | postgres |
As HA | tier-2 confidential VMs | Sovereign and high-assurance workloads |
The profiles combine: values-sovereign-eu.yaml is HA, air-gapped and confidential at once.
Local dev#
maqpna dev up starts a local MAQPNA without Kubernetes, Docker or kind: plain processes bound to 127.0.0.1.
flowchart LR
subgraph Laptop["Your laptop (127.0.0.1)"]
A["Your agent<br/>maqpna dev run -- CMD"]
GW["maqpna-gateway :8080"]
IB["maqpna-identity :8081"]
ECHO["mcp-echo :8090"]
STUB["stub-llm :8000<br/>(--stub-llm)"]
L[(".maqpna/audit.jsonl")]
end
A -- "MCP and model calls<br/>Bearer session token" --> GW
GW -- JWKS --> IB
GW --> ECHO
GW --> STUB
GW --> L
CLI["maqpna dev run"] -- "POST /v1/token" --> IB
maqpna dev upwritespolicies.jsonandgateway.jsoninto the state directory (./.maqpna), with a file ledger and a 2-second policy reload.- It starts the test MCP server (
mcp-echo, built into the CLI), the stub model server with--stub-llm, the identity broker with a generated key, and the gateway, waiting for each to be healthy. maqpna dev run -- CMDmints a session token for namespacedev, agentcoder, useryou@localhost, and runs your command withMAQPNA_GATEWAY_URL,MAQPNA_TOKENandMAQPNA_TOOL_<NAME>_URLset.
There is no sandbox, trust tier, operator or NetworkPolicy here; the gateway pipeline, policy, DLP, approvals and ledger are the real ones. See developer loop.
Single cluster#
The default chart installs one replica of each control-plane component into maqpna-system, with file-backed state.
flowchart TB
subgraph CP["Namespace maqpna-system"]
OP["maqpna-operator (1)"]
GW["maqpna-gateway (1)<br/>file ledger + journals"]
IB["maqpna-identity (1)<br/>:8081, bootstrap :8083"]
EX["maqpna-mcp-echo (example)"]
end
subgraph AG["Namespace maqpna-agents<br/>default-deny NetworkPolicy"]
S1["Sandbox tier-0<br/>gVisor"]
S2["Sandbox tier-1<br/>Kata + Firecracker"]
end
K[("Kubernetes API<br/>CRDs maqpna.com")]
OP <--> K
OP -- "mint token" --> IB
S1 & S2 -- "only egress" --> GW
GW --> EX
- Workloads:
maqpna-operator,maqpna-gateway,maqpna-identity(Deployments),maqpna-mcp-echo(example, on by default),maqpna-attestonly withattestation.enabled. - RuntimeClasses
gvisor,kata-fcandkata-qemu-snpand trust tierstier-0,tier-1,tier-2are created by the chart. - Prerequisite not installed by the chart: kubernetes-sigs/agent-sandbox v1.0.4.
- With
state.backend: file,gateway.replicas > 1refuses to render unlessguards.allowIndependentGatewayReplicasis set, because each replica would have its own approvals and ledger. - The admin API uses the static admin token (
adminAuth.mode: token) by default.
HA#
values-production.yaml runs every stateful component on shared PostgreSQL state.
flowchart TB
LB["Service maqpna-gateway"] --> G1 & G2 & G3
subgraph GWs["maqpna-gateway x3, PDB minAvailable 2"]
G1["replica A<br/>leader: siem/siem"]
G2["replica B<br/>leader: worm/audit"]
G3["replica C<br/>standby"]
end
subgraph Others["Other components"]
I1["maqpna-identity x2<br/>key from existingSecret"]
A1["maqpna-attest x2"]
O1["maqpna-operator x2<br/>Lease maqpna-operator.maqpna.com"]
end
PG[("PostgreSQL<br/>schema maqpna:<br/>state_events, audit_chain,<br/>state_blobs, counters")]
G1 & G2 & G3 <--> PG
A1 <--> PG
Setting in values-production.yaml |
Value |
|---|---|
state.backend |
postgres (DSN from Secret maqpna-state) |
gateway.replicas, gateway.pdb.minAvailable |
3, 2 |
gateway.auditFailurePolicy |
closed: an intent record is written before every allowed call |
gateway.auditStorage.persistent |
true (one PVC per replica, StatefulSet) |
gateway.config.adminListen |
:9090: the admin API, metrics and pprof move off the data listener |
identity.replicas, identity.key.mode |
2, existingSecret |
attestation.replicas, attestation.verifier |
2, trustee, with signed responses |
operator.replicas |
2 (the chart adds --leader-elect when replicas > 1) |
adminAuth.mode |
oidc, break-glass token disabled |
audit.sink, audit.checkpointSigning.enabled |
worm, true |
Gateway replicas share approvals, revocations, taint, pins, the notification outbox, FinOps state and one audit hash chain. Background jobs (SIEM and WORM shipping, tenant ledger feed, retention sweeps) run on one replica each, elected with PostgreSQL advisory locks. See state backend and HA and failure handling.
Multi-tenant / service provider#
A service provider (a neocloud, colocation provider, telco or ISV) runs one installation for several customers. Each customer is a Tenant (cluster-scoped) that owns namespaces.
flowchart LR
subgraph Inst["One installation"]
GW["Gateway<br/>key binding per tenant"]
IB["Identity broker<br/>platform key + tenant keys"]
subgraph TA["Tenant acme"]
NA["ns acme-prod<br/>label maqpna.com/tenant=acme"]
end
subgraph TB["Tenant globex"]
NB["ns globex-dev"]
end
end
NA -- "tokens spiffe://acme.maqpna.local/..." --> GW
NB -- "tokens spiffe://globex.maqpna.local/..." --> GW
GW --> LA[("ledger tenant/acme<br/>WORM prefix tenants/acme/audit")]
GW --> LB2[("ledger tenant/globex")]
GW --> LP[("platform ledger")]
What each tenant gets:
| Isolation | Mechanism |
|---|---|
| Namespaces | spec.namespaces, labelled maqpna.com/tenant=<name>; the oldest Tenant wins a contested namespace |
| Trust domain and signing key | <tenant>.<--tenant-trust-domain-suffix> (default suffix maqpna.local); Ed25519 key in Secret maqpna-tenant-keys, generated or copied |
| Token binding | The gateway refuses a tenant namespace token not signed with that tenant's kid and trust domain (tenant_mismatch), and a tenant key for any other namespace |
| Ledger | An independent hash chain per tenant (tenant/<name>), verifiable on its own |
| WORM | Its own key prefix (auditWormPrefix, default tenants/<name>/audit) |
| Admin login | admin.oidcIssuer / oidcAudience; mapped roles become namespace roles in the tenant's namespaces only |
| Quotas | quotas render the BudgetPolicy tenant-<name> |
| MCP servers | Shared across tenants only with the owner's allowSharingWith (cross_tenant_server otherwise) |
For billing, the operator counts billable nodes (nodes running at least one pod labelled maqpna.com/session) every minute into ConfigMap maqpna-usage-nodes, and reports each session's sandbox time to the gateway, which records it in the ledger as usage. maqpna usage buckets shows hourly usage per tenant and maqpna usage report builds a signed usage report. See tenants and usage metering.
Air-gapped#
flowchart LR
subgraph Online["Connected build host"]
B["maqpna airgap bundle<br/>images, chart, SBOMs,<br/>SHA256SUMS + cosign signature"]
end
B -- "removable media" --> V
subgraph Site["Disconnected site"]
V["maqpna airgap verify DIR<br/>--require-signature"] --> P["maqpna airgap push DIR<br/>--registry registry.eu.internal:5000"]
P --> R[("Private registry")]
R --> I["maqpna install<br/>airgap.enabled, airgap.imageRegistry"]
LIC["Licence file<br/>verified offline"] --> I
end
- On a connected host,
maqpna airgap bundlebuilds the offline bundle: the 11 images, chart, upstream manifests, SBOMs,SHA256SUMSand a cosign signature (key-based or keyless). maqpna airgap verify DIRchecks every checksum, the signature (--trusted-rootfor keyless verification offline) and that all 11 images are present; it exits 3 on any failure.maqpna airgap push DIR --registry REGcopies the images into your registry (with skopeo, crane or docker).- Install with
airgap.enabled: trueandairgap.imageRegistrypointing at the mirror.
Nothing at runtime needs the internet: no telemetry, no update checks, and the licence is a signed file verified against a key embedded in the binary.
Confidential (attested)#
tier-2 sandboxes run as confidential VMs (Kata Containers with AMD SEV-SNP or Intel TDX). Their session token is released only after remote attestation.
flowchart LR
subgraph Node["Confidential-VM node<br/>maqpna.com/confidential=true"]
subgraph CVM["tier-2 sandbox (Kata, kata-qemu-snp)"]
INIT["maqpna-attest init container"]
REN["maqpna-attest-renewer sidecar"]
AG["agent container<br/>token on tmpfs"]
end
end
OP["Operator"] -- "POST /v1/releases" --> AT["maqpna-attest x2"]
INIT -- "TEE report via configfs-tsm" --> AT
AT -- "verify evidence" --> TR["Trustee attestation service<br/>(customer-run)"]
AT -- "appraise vs reference values" --> RV[("reference values<br/>measurements, HOST_DATA,<br/>MRCONFIGID, RTMR")]
AT -- "mint" --> IB["Identity broker"]
AT -- "token sealed to the VM key" --> INIT
AG -- "only egress" --> GW["Gateway"]
values-sovereign-eu.yaml configures this shape: attestation.enabled, two replicas on Postgres, verifier: trustee with a customer Trustee URL and JWKS, reference values that are empty by default (empty rejects every VM), requireAttestationFor: [confidential], tier-2 with attestationRequired: true and EU node affinity, audit.sink: worm with 3650-day retention, an EU DLP profile, OIDC admin auth and principalBinding.mode: requester.
See attestation-gated secrets and attestation service.