Data and secrets
Your app gets a database of its own and the values only it should see. You ask for both in the manifest and with one command; you set up neither.
A database
Declare it:
data:
postgres: true
tables: [expenses, approvals]
uploads: false
Publish, and the app is given a Postgres database and a connection string:
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
That is the whole setup. No provisioning, no credentials to store, no connection string to paste anywhere.
tables is what you tell your coworkers, not what Spryloom creates. Your app
creates its own tables and runs its own migrations, the way it does on your
machine. What the list does is appear on the app's page, so somebody deciding
whether to open it can see what it keeps.
If Spryloom cannot give the app a database, the publish is refused. It will not tell you a database was created when none was: an app that believes it has one writes to nothing and finds out later.
It belongs to the app, and to nothing else
One database and one role per app. Another app cannot open it — not by guessing the name, not with its own credential, not at all. That is enforced by Postgres, not by code that has to remember.
Publishing again keeps the data and rotates the password, so the connection string an app runs with is always the one that publish produced. A copy taken from an earlier version stops working.
Archiving an app keeps its database. Archiving is not deleting, and a tool for a finished project is often wanted again.
Secrets
An API key, a signing key, a password for something else — the values that should not be in your repository.
spry secrets expense-notes set STRIPE_API_KEY=sk_live_...
Your app reads it from the environment like anything else:
const apiKey = process.env.STRIPE_API_KEY;
See what is set, and when:
spry secrets expense-notes
STRIPE_API_KEY
set by priya@yourcompany.com on 2026-09-07
Values are not shown. Nothing can read one back.
Remove one:
spry secrets expense-notes remove STRIPE_API_KEY
Two things to know before you meet them
Secrets reach the app when it is published. Set one, then publish again. Setting it does not change what is running.
An app cannot be given a secret before it exists. If your app cannot start without a key, the first publish will fail and the second will work. Publish, set the secret, publish again.
There is no way to read one back
Not a command, not a page, not an API. That is deliberate: a secret somebody can retrieve is a secret that leaks through whoever can retrieve it, and every reason to retrieve one is better served by setting it again.
What it costs is real. If you lose the only copy of a key, you have to get another from wherever it came from. Spryloom cannot give it to you.
What holds
- Encrypted before it is stored. A copy of the database, a backup, or a replica is not a set of usable credentials.
- Tied to the app it belongs to. A secret moved to another app or renamed does not decrypt. Somebody with access to the storage cannot give one app another's secret.
- In no image layer and no log line. It is given to the app when it starts and nowhere else.
- Not in the audit log. That records that a secret was set, and what it was called. The value is never passed to it.
What your app may reach
Almost nothing, and the exception is worth knowing exactly.
Blocked, with no way around it. The web, in general. Mail servers, so an app here cannot send spam. Every other app on Spryloom. Spryloom's own systems. None of that is a policy somebody checks after the fact: it is enforced outside your app by the host it runs on, so nothing your app does, and nothing a dependency does, can switch it off. Other apps and our own systems are blocked by something stronger still, because each app sits on its own private network and there is no route between them at all.
The exception. One port is open, the one databases listen on, because that is how your app reaches its own. The rule is by port rather than by address, so an app can in principle open that port to a machine somewhere else. What that means in practice: somebody who publishes an app could write it to send its own data to a database server they control. It does not let one app reach another's data, and it does not let a stranger reach yours.
So the honest version is this. Against a dependency that turns out to be hostile, or an app that gets more than you meant it to, the boundary holds. Against the person who published the app deliberately sending out data that app already shows them, it is a speed bump. We would rather write that down than let you find it later.
Closing it means putting the database behind a relay of our own, after which no port is open at all. That is planned and not built, and it is on the list before Spryloom is opened to more than invited teams.
If your app genuinely needs to call an external service, that needs an allow-list and it is not built yet. An app that tries will find the connection simply does not open.
Backups
The mechanism is exercised: a backup taken, the data destroyed, the backup restored, and the app used again afterwards. Point-in-time recovery in production belongs to the database provider.
What is not built: a command that takes one for you. Today both a backup and a restore are an operator doing it for you. If your app's data matters, say so when you ask for access.