Per-instance object storage: a store an instance owns, not just an app Feature Request open
store.json is per app. Grant mints one bucket and one IAM user, both named from (account, app) (internal/objstore/walk.go:62,70), and writeEnv delivers CARLOS_STORE_* through appconfig.Apply (walk.go:689) — the default bundle. So every instance of an app shares one bucket and one credential, and there is no way to give one instance its own.
Why this matters now
A managed, multi-tenant product wants one app with one instance per customer. Working through that shape for a managed Jelly (one instance per team at <team>.letsjelly.com), everything else already lines up:
- one
carlos shipupdates every team, because instances follow the channel their route names; - idle teams hibernate, so a long tail of small teams costs nearly nothing;
- each team gets its own database, admin token and socket, kernel-enforced under isolated exec;
- one delegated wildcard parent covers every team hostname, so no per-host certificate issuance;
- an
instance-role service credential lets the product mint instances at signup.
The one thing that cannot be per-team is the bucket. Today that leaves two options, both bad:
- One bucket, per-team key prefixes. Isolation becomes app logic rather than a credential boundary. One path-construction bug crosses tenants. For a product holding customers' mail attachments, that is not a boundary anyone should accept.
- The app brings its own S3. It re-implements what
storedoes, and the platform stops being the thing that provisions storage.
The ask
A store an instance owns: a per-instance store record, delivering CARLOS_STORE_* into the config that instance actually reads.
A smaller first step that may be most of the value. The config half already shipped: instances create --environment and set-environment bind one instance to a named bundle (#115). But the store walk delivers through appconfig.Apply, not ApplyEnvironment, so even an instance with its own bundle cannot receive its own store. Letting the store walk target the bundle an instance reads would unblock the shape without a new record: an operator could give one instance its own bucket today, and a signup path could do it with one extra call.
Design questions this needs answers to
- Naming.
MintedBucketisprefix + acct + "-" + appandUserNameis"carlos-store-" + acct + "-" + app. A per-instance store needs a third component, the host label, which lengthens both. Bucket naming rules and IAM name limits should be checked against realistic host labels before a shape is chosen. - Approval. The account entitlement should stay the decision (self-serve store spec §3's ruling: approve once per account, unlimited stores, metered). A per-instance store inside an approved account must need no new approval, or signup-time provisioning blocks on an operator.
- Teardown.
Teardownis per app today, andinstances deleteknows nothing about buckets. Deleting an instance has to tear its store down, and the trash/purge guard (release.PurgeAppparses thegrant/provisionedblocks) has to keep working. - Metering.
sizeProvisionedStorewalks one store per app. Per-instance stores need per-instance sizing, which is also what #140's per-instance usage ask wants.
Related
- #115 — per-instance environment binding (shipped; this is its storage twin)
- #140 — per-instance usage an owner can read
- #134 — a store read verb
- #133 — point-in-time restore of an instance's database
All four treat the instance as the unit an owner reasons about. Storage is the one that is still app-wide.