An agency runs twelve client sites. Twelve CMS instances mean twelve deployments, twelve upgrades, twelve backups. RustyCMS runs twelve spaces on one process instead: each with its own content, its own assets, its own history, its own languages — sharing the binary, the schemas and the maintenance.
Separate what has to be separate
A space is its own content room. Entries, images, version history, reference index — none of it sees the neighbour. On disk that is one directory per space:
content/
├── site-a/ assets/ .history/ page/ post/
└── site-b/ assets/ .history/ page/ post/
Each space decides which languages it carries: { locales: ["de", "en"] } in its settings, otherwise the global default from RUSTYCMS_LOCALES applies. A single-language club site and a trilingual shop run on the same instance without one inheriting the other’s languages.
Share what should be shared
Schemas do not have to be copied per space. A type pack is maintained once and subscribed to by every space; where a site differs, it adds an override that names only the difference. Everything else keeps coming from the pack — a change there reaches every subscribing site immediately.
One parameter on the request
Every content and asset endpoint accepts ?_environment=<name>. Without it, the first space in RUSTYCMS_ENVIRONMENTS answers — the default. A site’s frontend sets the parameter once in its API client and needs to know nothing else.
GET /api/environments
# → { "environments": ["site-a", "site-b"], "default": "site-a" }
GET /api/content/post?_environment=site-b
Create without a restart
Spaces are not a deployment concern. Create, duplicate, rename, delete — all at runtime, via the API or on the “Spaces” page in the admin UI. Duplicating copies content, assets and history in full, so a staging copy of production exists in seconds.
POST /api/environments/site-a/duplicate
{ "name": "site-a-staging" }
Names use a-z, 0-9, - and _. The last remaining space cannot be deleted, and every write requires write permission.
Access per space
An API key can be restricted to spaces: in RUSTYCMS_API_KEYS_JSON, each key carries a list of allowed environments, optionally also allowed collections. The key in site B’s frontend reads site B and nothing else. The same applies to admin UI users — allowed_environments decides which spaces someone sees at all. Admin roles bypass both lists.
What doesn’t happen
Deletion is final — there is no recycle bin for a whole space, but there is the automatic per-environment backup. In Postgres mode, where content lives in table rows rather than directories, create and list work; duplicate, rename and delete answer with 422 there, because they would move directories.