Architecture
flex.plane is composed of four main components, each deployed independently.
Explore the components
Orchestrator
The central API gateway. It receives GraphQL requests from the portal (or any API client), authenticates them via OIDC, and fulfills them by reaching out to neighboring systems. It has no database of its own. See Stateless Design below.
Deployed as a Kubernetes pod via the flextenant Helm chart.
Portal
The self-service web dashboard. End-users create and manage VMs, networks, Kubernetes clusters, and VDCs through the portal without needing sysadmin access to Proxmox. Platform admins use the same portal to configure compute profiles, storage profiles, manage hosts, and control access. Everything available in the portal is also accessible via the GraphQL API for automation.
Deployed alongside the orchestrator in the same Helm chart.
Agent
A lightweight daemon running on every Proxmox node. It communicates with the orchestrator via gRPC over the WireGuard mesh network and handles:
- Image management: downloading and caching VM images on the node
- Routing and edge gateways: managing network routing and gateway services
- Authenticating proxy: forwarding Proxmox API requests with injected credentials
- Node bootstrap: initial setup when a new node joins the platform
- Self-updates: pulling and applying new agent versions via knockknock
The agent is the only flex.plane component installed on your Proxmox hosts.
Identity (Zitadel)
Zitadel provides OIDC authentication and user management. It stores user identities, roles, and VDC memberships. The orchestrator validates tokens against Zitadel and extracts role claims to enforce authorization.
Deployed via the identity Helm chart with its own PostgreSQL database.
Understand the stateless design
The orchestrator has no database. This is not an oversight. It is a deliberate design choice. All state is derived from neighboring systems at request time:
| State | Source |
|---|---|
| VMs, networks, storage, node status | Proxmox API (via agent) |
| Kubernetes clusters | Cluster API CRDs in Kubernetes |
| Compute profiles, storage profiles, VDC mappings | Kubernetes ConfigMaps |
| Hosts, zones, service discovery | WireGuard mesh overlay network |
| User identities, roles, VDC memberships | Zitadel OIDC token claims |
When the orchestrator receives a request, it fans out to the relevant systems, aggregates the results, and returns them. No caching layer, no eventual consistency, no stale reads.
This stateless design makes flex.plane non-intrusive. If the orchestrator goes down, all running VMs continue to run. All existing networks keep working. Kubernetes clusters stay healthy. If the mesh network has issues between the orchestrator and a node, VMs on that node are unaffected — you simply cannot manage them until connectivity is restored. flex.plane can be upgraded, restarted, or even temporarily offline without impacting your workloads. Your VMs do not depend on flex.plane being up. flex.plane depends on Proxmox being up, and Proxmox is the thing that is actually running your VMs.
For a deeper dive into the trade-offs and design rationale, see Stateless Orchestrator.
The management cluster
All flex.plane control plane components (orchestrator, portal, identity, mesh control server) run on a Kubernetes cluster called the management cluster. This cluster is separate from any Kubernetes clusters you provision for your workloads through flex.plane.
The management cluster can be lightweight — a single-node k3s setup works fine for smaller deployments. It only hosts the platform itself, not your VM workloads.
You have two options for running the management cluster: self-host it on your own infrastructure, or use the flex.plane managed SaaS offering where the management plane is hosted for you. See Self-Hosted vs Managed for details.
Follow a request through the system
Here is what happens when a user creates a VM through the portal:
- Portal sends a
createVMGraphQL mutation to the orchestrator, including an OIDC bearer token and aFlexPlane-VDC-IDheader. - Orchestrator validates the token against Zitadel, checks that the user has the
USERrole, and resolves the VDC context. - Orchestrator looks up the requested compute profile, storage profile, and image from Kubernetes ConfigMaps.
- Orchestrator selects a target host in the requested zone (or auto-places based on available resources).
- Orchestrator sends a VM creation request over the WireGuard mesh to the agent running on the target Proxmox node.
- Agent injects the Proxmox authentication headers and forwards the request to the local Proxmox API.
- Proxmox creates the VM. The agent returns the result to the orchestrator.
- Orchestrator returns the new VM details to the portal as a GraphQL response.
The entire flow is synchronous and stateless. If the orchestrator restarts mid-request, the client simply retries. The VM either exists in Proxmox or it does not.