Namespaces
Flux's namespace primitive — what it isolates, what it doesn't, and how to use namespaces for team, environment, or tenant separation.
A namespace in Flux is a short label attached to a workflow definition. Set it on the decorator:
from flux import workflow, ExecutionContext
@workflow.with_options(namespace="billing")
async def invoice(ctx: ExecutionContext[dict]):
...
The decorator runs the value through validate_namespace (flux/_namespace.py:12), which lowercases-only, allows a-z0-9_-, caps the length at 64, and substitutes "default" when the argument is None or "". Every workflow ends up with a namespace; the only question is whether you picked one.
The namespace travels with the workflow through three tables. WorkflowModel keys uniqueness on (namespace, name, version) (flux/models.py:478). ExecutionContextModel.workflow_namespace (flux/models.py:527) is copied onto every execution, and ScheduleModel.workflow_namespace (flux/models.py:688) pins each schedule to the workflow it triggers. The qualified name on the wire is always namespace/name (flux/workflow.py:94).
Operational uses
Three patterns make sense in practice:
Per-team isolation. Pick team-a, team-b. Each team owns its namespace and the RBAC rules that grant access to it. Names can collide across namespaces (billing/invoice and analytics/invoice are different workflows), which is the main reason to separate teams this way.
Environment isolation. You can run prod and staging namespaces in one Flux deployment, but separate deployments are usually cleaner. Namespaces don’t isolate the database, worker fleet, or encryption key (see below), so a staging workflow on the same server still competes for the same workers and reads from the same secrets store as prod. If you need real blast-radius separation, run two servers.
Per-customer isolation. tenant-acme, tenant-xyz. Works for the catalog and the audit trail (every execution row records workflow_namespace), but it does not get you tenant-isolated secrets, storage, or workers. Treat it as a logical grouping for queries and permissions, not as a security boundary.
Listing namespaces
There is no top-level flux namespace command group in 0.56.0. Namespaces are implicit: they are created the moment a workflow registers with one and disappear when the last workflow in them is deleted. The discovery surface is on the workflow group (flux/cli.py:109-142):
flux workflow list-namespaces # all namespaces + workflow counts
flux workflow list --namespace billing # workflows in one namespace
The REST equivalents are GET /namespaces (flux/api/workflow_routes.py:112) and GET /workflows?namespace=billing (flux/api/workflow_routes.py:135). Both honor the caller’s workflow:{ns}:{name}:read permissions and elide entries the caller cannot see.
To create a namespace, register a workflow with that namespace value — that is the whole operation. To delete one, delete every workflow in it.
What namespaces isolate
- Catalog uniqueness.
(namespace, name, version)is the unique key. Samenamein two namespaces is two workflows. - Workflow references. CLI and API paths are
namespace/name(flux/catalogs.py:169-189). A barenameresolves todefault/name. - Permission scoping. Permission strings are
resource:scope:scope:verbwith*wildcards (flux/security/identity.py:38).workflow:billing:*:readgrants read on every workflow inbilling;workflow:billing:invoice:runis per-workflow. - Query filters.
flux execution list --namespace billingfilters server-side (flux/cli.py:691,flux/context_managers.py:560).flux workflow listand the schedule routes take the same filter.
What namespaces do NOT isolate
- Storage. One database backs every namespace. The
workflows,executions,execution_events, andschedulestables hold all rows side by side, distinguished only by thenamespace/workflow_namespacecolumn. - Workers. A worker that registers without labels claims work from any namespace it has permission for. To pin workers to a namespace, use
@workflow.with_options(affinity={"tenant": "acme"})and start the matching workers with--label tenant=acme. The match is exact-equality across all keys (flux/domain/resource_request.py:128). - Encryption and secrets.
SecretModelhas no namespace column. One encryption key (flux.security.encryption.encryption_key) protects every secret on the server, and any workflow that declaressecret_requests=["DB_PASSWORD"]reads the sameDB_PASSWORDregardless of namespace. - The event log.
execution_eventsrows carryworkflow_namespaceso you can filter by it, but the table itself is shared. Auditing per tenant is a query, not a separate store.
Cross-namespace references
Yes — they work. The call() task (flux/tasks/call.py) accepts either a workflow object or a string ref. A string "analytics/report" is parsed by resolve_workflow_ref (flux/catalogs.py:169) into (analytics, report) and POSTed to /workflows/analytics/report/run/{mode}. From a workflow in billing, you can call into analytics provided the caller’s identity holds workflow:analytics:report:run. Namespaces are an addressing convention, not a wall.
Failure modes
Worker label drift. A worker meant for tenant-acme starts without --label tenant=acme and begins claiming work for every tenant. Workflows that omit affinity={...} will land on whichever worker grabs them first. Fix: declare affinity on every multi-tenant workflow, and gate worker startup on the label being present.
Secret name collision. Two tenants both declare secret_requests=["DB_PASSWORD"]. There is one DB_PASSWORD in the secrets store — whichever tenant set it last wins, and the other reads the wrong value. Fix: prefix secret names by tenant (acme_db_password, xyz_db_password) and update each workflow’s secret_requests, or run a separate Flux deployment per tenant when the prefix discipline is not workable.
Audit “per tenant” assumed to be cheap. The event log is global, so any tenant report is a WHERE workflow_namespace = ? scan. Index that column if you find yourself filtering it under load — ix_workflow_namespace_name exists on the workflows table but not on execution_events.