Multi-Tenancy
One nanoinfra process serves one tenant. To serve several, run several processes — one config file, one workspace and one port each. There is no tenant dimension inside a running instance, and this page is mostly about why that is a requirement rather than a style preference.
If you are separating a test bot from a production bot, or a Telegram bot from a Slack bot, that is the same mechanism. Multiple Instances is the page you want. Come back here if the thing you are separating is customers.
Why one instance cannot hold two tenants
Four things inside an instance are deployment-wide, and each one leaks across a tenant boundary if you share a process.
| What | Why it crosses the boundary |
|---|---|
chat_id | It is a bearer capability. Anyone holding a valid WebSocket credential and a chat_id can attach to that conversation and read its output. nanoinfra does not namespace chat_ids per user, so two tenants on one instance are one conversation space with obscure names. See Security boundary. |
| The workspace | Memory, sessions, skills and cron jobs live there. A shared workspace is shared long-term memory, which is the one thing a tenant will never accept. |
gates.approvers | The list of people whose approval releases a suspended action is per deployment, not per conversation. An approver for tenant A can approve tenant B's remote command. |
| Secrets and servers | The credential store and the server inventory belong to the config directory. Any turn on the instance can reach any credential the instance holds. |
The first row is the one that makes this structural. The other three are policy you could in
principle scope. A chat_id that authorizes on possession cannot be, without a per-tenant auth
gate that does not exist today.
The shape that works
One instance per tenant, with its own config, its own workspace and its own port:
nanoinfra gateway --config ~/.nanoinfra-acme/config.json --workspace /srv/nanoinfra/acme
nanoinfra gateway --config ~/.nanoinfra-globex/config.json --workspace /srv/nanoinfra/globex
Config, state and network separate the same way they do for any other multi-instance run. Multiple Instances has the full flag reference, the path-resolution rules and the systemd and Docker Compose patterns for running them side by side.
That page does not cover the credential store. nanoinfra derives secrets/ from the config
directory, so a separate config directory is a separate store.
Who reaches which instance
Per-tenant isolation of state does not by itself decide who can open a session. That is the proxy's job. It is worth reading Identity and SSO before you expose any of these instances, because the failure mode is specific. With a public identity provider, any account that completes the flow with your client id is verified — and verified is not invited.
The rule that matters here is that the two lists do different jobs:
- the proxy's allowlist and
allowedIdentitiesdecide who reaches this tenant's instance at all gates.approversdecides whose approval counts once they are in
A deployment that scopes the second and leaves the first open has an open agent per tenant. See Two Lists, Two Jobs.
What this costs
Being straightforward about it, because the alternative is discovering it at the tenth tenant:
- One process per tenant means one model configuration, one set of channel credentials and one memory store per tenant. That is the isolation, and also the per-tenant overhead.
- Nothing is shared between instances, including the prompt cache. Two tenants on the same model warm two separate caches.
- There is no cross-tenant view. Aggregate token cost across tenants means reading each instance's own Metrics.
If your requirement is many small tenants on shared infrastructure rather than a few isolated
ones, nanoinfra is not shaped for that today. The chat_id row above is the reason.