
The Projects page — each project with its namespace, pod CIDR, egress addresses, LoadBalancer range, and isolation state.
What a project actually is
Celum stores no project state of its own. A project is Kubernetes objects, and Kubernetes is the only source of truth:
Description and creator are kept as namespace annotations (
nuvira.dev/description, nuvira.dev/created-by). Delete the namespace outside Celum and the project is gone — there is no database row left behind to reconcile.
Project names must be DNS-1123 labels: lowercase alphanumerics and hyphens, starting and ending with an alphanumeric, 2–42 characters. The namespace is always the name prefixed with
proj-.Supervisor pool configuration
Before projects can be allocated automatically, the supervisor needs to know which address space it is carving up. That configuration lives in a singletonNuviraClusterConfig CRD, plus the pod CIDR that Cilium already owns:
The config endpoint always answers, with a
seeded flag telling you which case you are in: true when the NuviraClusterConfig CRD exists, false when you are looking at derived defaults that nobody has confirmed.
Allocation
When you create a project you may name its pod CIDR, LB CIDR, egress IP and VM-identity range. Only the LB range is allocated for you when you leave it empty: Celum takes the next free slice of the LB band. An empty pod CIDR or egress IP creates nothing, and an empty VM-identity range is carved when the first VM asks for an identity address. Create a project has the table. Allocation works by listing the live Cilium CRDs on each create and taking the next free slice — there is no stored “taken” set that can drift from reality.Because allocation reads live state, a project created outside Celum that uses one of these ranges is still counted. Conversely, hand-editing a pool CRD changes what the allocator considers free.
Network isolation
New projects are isolated unless you opt out. Isolation is oneCiliumNetworkPolicy in the project namespace that flips it to default-deny and then opens a specific set of holes:
What the default isolation policy allows
What the default isolation policy allows
- Traffic within the project namespace, both directions
- Egress DNS to
kube-dnsinkube-system(UDP + TCP 53) - Ingress from the outside world, the host and remote nodes, and Cilium’s ingress proxy — which is what keeps kubelet probes and Gateway-published services working
- Ingress from the Cluster API and KubeVirt control-plane namespaces, so a guest cluster provisioned inside an isolated project can bootstrap
- Egress to the world, host and remote nodes, and the Kubernetes API server
- Egress to the central observability stack, unless you turn that off
What it denies
What it denies
Ingress from every other namespace on the supervisor. That is the point of the policy — everything else in the list above exists to stop the deny from breaking things that must keep working.
Create-once, never overwritten
The isolation policy is created only when it is absent. Re-applying a project never overwrites it, so exceptions you add in Policy Studio survive. Turning the toggle off deletes the policy. Two changes are exceptions, because they change what the policy itself must allow: turning observability egress on or off, and turning Dedicated Envoy on or off, on an isolated project. Both rewrite the policy, and the edits made in Policy Studio are lost. This is why the UI tracks intent and reality separately: the namespace annotation records that the project should be isolated, while the badge reads whether the policy currently exists. A policy deleted by hand in Policy Studio shows the project as open even though its annotation still says isolated.Related switches
Dedicated Envoy
An isolated project gets a gateway of its own unless you opt out:Gateway <name>-envoy in the project namespace, served by its own Envoy (an EnvoyProxy that runs one Envoy per node), with a VIP from the project’s LB pool. The project’s routes move to it, and every later publish targets it.
It is on by default because of what it closes. With isolation on, the project is then reachable only from outside the cluster and from its own namespace. On the shared gateway, the isolation policy has to admit the shared gateway’s proxies, so another project could reach this one through its public hostname.
It needs the Envoy Gateway engine and an LB range on the project. GET /api/supervisors/{name}/projects/config says whether this supervisor can give a project one (dedicatedGateway.available, with a reason when it cannot). Where it cannot, a create that left the switch alone succeeds without one and says so in warnings; asking for one explicitly (dedicatedGateway: true) is refused with 409.
Turning it off moves the routes back to the shared gateway and removes the project’s own. Either way the work runs in the background, and the project’s dedicatedGatewayStatus reports it.
Egress
Each project can leave the cluster with its own source IP, which is what makes project-level firewall rules possible upstream.The project form also offers an anycast egress routing choice, and it is live. Every candidate node advertises the egress
/32 over BGP so the upstream sees an ECMP set, while non-owner candidates hold a kernel route steering returns to the owner. What does not change is the data-plane owner: the IP and the Cilium gateway label stay on one elected node, so SNAT conntrack lives in a single place and long-lived TCP survives in both modes.VM identity pool
A project may additionally be given a VM-identity CIDR (between/16 and /29). Each VM in the project then claims a routable address that follows it through live migration, with no LoadBalancer involved.
BGP advertisement
A project’s three address sources — pod, LoadBalancer, and egress — each carry an advertise choice of default, on, or off. The selection is stored on the namespace and fanned out to the BGP configuration, so what the fabric hears matches what the project declared.Changing a project
Edit on a project’s row changes its description and labels, and Save & re-apply re-applies its namespace and pools. The same form moves the pod CIDR (back to the cluster default, or to another range), the LB range (to another free slice, or to no LB pool, which deletes the project’s pool) and the VM-identity range (to another slice, or to none, after which a VM takes its address from a supervisor-wide pool or carves a new slice at its next request), and turns the four switches. A range is checked against the address plan only when it moves, so keeping the current one never fails. Over the API this isPUT /api/supervisors/{name}/projects/{project}. Every field is optional, and a field you leave out keeps its value.
Deleting a project
Deletion is an ordered teardown, not a namespace delete: Cluster API clusters in the project go first, the namespace last. While it runs, the project reports itself as deleting.Permissions
Projects have their ownproject:* actions, and three of them name the individual project — which is what lets a policy say “project A but not project B”.
Until 2026-08-17 these routes were gated by
cilium:GetState / cilium:Apply and no narrower grant existed, so managing projects meant handing over the supervisor’s whole Cilium configuration. That is no longer true — and a policy written against the old shape grants nothing here, because an action the code does not define is rejected outright.
What commonly goes wrong
Related
Supervisors & clusters
The layer a project sits on.
Permissions model
How an action and a project-qualified KRN are matched.
Create a cluster
Clusters are provisioned into a project’s namespace.
Glossary
Supercidr, EGP, KRN, and the rest of the vocabulary.