Agent index (llms.txt) — fetch this first, then the page it names.
Run your own cluster
Reference. To join the network, start with Get started. This page is reference for running your own cluster.
A private Orama cluster is three or more Linux machines that you install with
orama node setup, plus a domain whose nameserver records you can publish.
(orama setup --cluster-only --domain <domain> does the same for every machine
you give it, prints the NS and glue records the domain needs and waits until
they resolve; orama setup without --cluster-only also joins the network's
chain. Its reference is Join with orama setup.)
The installer refuses a machine that fails the checks in
core/pkg/install/checks.go. This page is that sequence. It does not create
servers: orama maint sandbox create does, on Hetzner, and
Sandbox is that command.
What you need
Machines. Three VPS, so the index RQLite group has three voters. Each one
must be one of debian 12, 13; ubuntu 22.04, 24.04, 26.04, with at least 2 CPU
cores (runtime.NumCPU), 2GB of RAM and 10GB free disk. Those floors are
MinCPUCores, MinRAMBytes and MinFreeDiskBytes. The RAM and disk checks
count 1024³ bytes and the error text calls that GB. The archive you install
must match the machine: orama maint build --arch builds amd64 (the default) or arm64, and
nothing else.
A domain. Genesis records the environment gateway as https://<base-domain>.
That name has to reach the cluster, which means NS records and glue for its
nameservers. For a name you registered (example.com), the glue is stored at
the registrar. For a name under a zone you already host (stagenet.example.com),
the glue is an ordinary A record in the parent zone. --cloudflare-token-file
writes that parent zone when it is hosted at Cloudflare, and it refuses a
registry TLD (a parent with no dot, such as com). The record format is
Nameserver.
On the machine you type from. The RootWallet desktop app, open and
unlocked. orama node setup and orama auth login ask its agent for a key
and a signature. There is no SSH key on disk and no password flag: rw vault add <ip> stores the VPS login, and --password reads it. You need no source
checkout, Go or zig: setup installs a published release. It takes three things
you choose, none of which it fetches for you:
- +
--release <version>: the release to install, on thestablechannel unless you pass--channel. - +
--release-repo <url>: the https address of the release repository, which serves the signed metadata and the archives. - +
--release-root <root.json>: the TUF root of the release signers you decided to trust. It is the one thing that has to reach you by another road than the repository (the project publishes its SHA-256; check the file against it).
Setup fetches the release's metadata on your machine, verifies it against that
root (every role at its threshold, a timestamp that has not expired, a snapshot
no older than the newest this machine has accepted, the channel's own keys for
its own files), downloads the archive and checks its length and hashes. Only then
does your RootWallet sign it, as the build your cluster runs, with the root
inside: your cluster trusts your wallet for what it installs, and every node
adopts the root, so it can later update itself (below). A test cluster passes
--acme-ca letsencrypt-staging so certificate issuance does not use the
production Let's Encrypt quota.
Ports the installer opens. On every node: SSH 22/tcp, WireGuard
51820/udp, 80/tcp and 443/tcp. On a nameserver it also opens 53/tcp and
53/udp. A firewall in front of the VPS has to allow the same ports; ufw on
the machine cannot open it.
Install
Store each VPS login, then install the genesis nameserver. --genesis creates
the cluster and records the environment. It does not make that environment the
active one.
rw vault add 203.0.113.10 orama node setup --ip 203.0.113.10 --password --env mycluster --release 0.3.1 \ --release-repo https://releases.example.org/tuf --release-root ./root.json \ --base-domain cluster.example.com --role nameserver --genesis --acme-ca letsencrypt-staging
Publish the NS and glue records before joining anyone else. The invite a joiner uses pins the genesis node's certificate, and that certificate is not issued until the delegation resolves.
orama node dns delegation --env mycluster orama node dns delegation --env mycluster --cloudflare-token-file /path/to/token
The command without the token prints the records. The command with the token
writes them when the parent zone is at Cloudflare, then checks DNS. Wait until
dig NS cluster.example.com @8.8.8.8 lists the nameservers the command
printed and the genesis certificate has been issued.
Join the other two machines through the genesis node. --join-via mints the
invite over SSH, so the cluster name does not have to resolve on your machine
yet.
orama node setup --ip 203.0.113.11 --password --env mycluster --release 0.3.1 \ --release-repo https://releases.example.org/tuf --release-root ./root.json \ --base-domain cluster.example.com --role nameserver --join-via root@203.0.113.10 --acme-ca letsencrypt-staging orama node setup --ip 203.0.113.12 --password --env mycluster --release 0.3.1 \ --release-repo https://releases.example.org/tuf --release-root ./root.json \ --base-domain cluster.example.com --role nameserver --join-via root@203.0.113.10 --acme-ca letsencrypt-staging orama node dns delegation --env mycluster
Run the delegation command again after the new nameservers exist and update
the parent zone to match. A nameserver slot stays with its machine until
orama node remove.
Use it
orama network use mycluster orama auth login orama namespace create myapp orama auth login --namespace myapp orama deploy static ./site --name www
orama auth login signs in to the active environment's gateway. Creating a
namespace does not sign you into it; the second login does. orama deploy static publishes a directory (any directory with an index.html; ./site here)
as the deployment www.
Further nameserver detail, including installing by hand on the VPS with
orama maint node install, is Nameserver.
Check it
orama status --env mycluster orama app list
orama status reads every node's health from the cluster's operator telemetry
API, so it needs the sign-in above, with the RootWallet account you ran
orama node setup with: that account is the cluster's operator. A node is healthy when its gateway answers
and its RQLite is Leader or Follower; all three of yours should say so.
orama app list shows www.
Every command in Install, Use it and Check it is executed, in this order, by
make e2e-cluster against machines the repo's owner provides
(core/e2e/clusterguide, see Testing, "Cluster guide
e2e"). Change a command here and that test fails until its plan matches.
Keep it updated
A cluster does not update itself until you tell it where releases are. These are settings of the cluster, changed by an operator and written to the audit trail:
orama maint cluster settings set release-repo https://releases.example.org/tuf orama maint cluster settings set update-channel stable orama maint cluster settings set auto-update notify orama maint cluster settings show
With notify, the default, every node looks every 15 minutes, verifies what it
finds against the release root it adopted at install, and orama status shows
a newer release as information (a release that does not verify, or that a node
rolled back, as a warning). Nothing is installed. With orama maint cluster settings set auto-update auto the nodes install it themselves, one at a time, followers
first and the leader last, only while the cluster is healthy and the hour is
inside update-window, and a release that fails on one node is not tried on the
others. How it decides, and what it does when an install fails, is
Upgrades.
Building from source
A release is the way to run a cluster without a checkout. To run a build of your
own instead, build it in a checkout of this repository, which needs Go, zig and
the RootWallet agent, and pass the path to setup in place of the three release
flags (--archive and --release are alternatives):
orama maint build
orama maint build prints the archive's path. The build is reproducible, so a second
build of the same commit with SOURCE_DATE_EPOCH set to the commit's time gives
the same archive, byte for byte (Build and sign).
A sealed backup
A namespace backup is sealed to an X25519 keypair you keep. The cluster is only ever given the public key, so it can write backups it cannot read.
# On the cluster the namespace lives on, signed in as its owner: orama namespace backup --key <public key hex> --out myapp.orbk
The gateway puts three things in the file: the namespace's RQLite snapshot
(the same /db/backup that orama namespace rqlite export downloads), every
CID the namespace holds pinned (stored objects and each deployment's content
and build), and its stored secrets (function secrets, push tokens and
credentials, TURN secret, deployment environment). The secrets are decrypted
by the source cluster, because they are encrypted under its encryption root
and no other cluster can read them. If the source cluster dies before a backup
was taken, its secrets are gone.
To restore onto another cluster, create the namespace there, sign in to it as its owner, and run:
orama namespace restore-key # the destination's restore public key orama namespace restore --in myapp.orbk --key-file ./backup.key \ --namespace myapp --dest-key <restore public key hex>
restore opens the backup on your machine; the private key never leaves it.
It seals each secret to the destination gateway's restore key for that
namespace, which is derived from the cluster's encryption root and the
namespace name, so it changes when the root is rotated. Each sealed secret
also carries its namespace and row, and the gateway checks them, so secrets
cannot be replayed into another namespace or row.
Before writing anything the gateway refuses: a wrong key, a corrupt or
truncated file, a backup of a different namespace, a gateway whose RQLite
client cannot run atomic batches, and a restore that would put the namespace
over its storage quota on this cluster (max_storage_bytes times the
replication factor, as /v1/storage/pin counts it). Then it replaces the
namespace's RQLite with the snapshot, puts back this cluster's own storage
quota (a backup never brings its quota with it), writes the secrets under its
own encryption root, checks the quota again against the restored storage
table, and pins the CIDs, eight at a time, within ten minutes. A failure after
the database was replaced says so; running the same restore again is safe.
The live keys, grants and sessions are not restored: they are in the cluster's
registry, not in the namespace's database. A key revoked since the backup stays
revoked and one minted since still works; mint the keys the new cluster needs
with orama namespace keys create. The backup's database still carries stale
copies of those rows, which nothing reads.
A database image (a restore's, or an orama namespace rqlite import's) is
checked before RQLite loads it, because once loaded every gateway of the
namespace serves writes against it. The gateway writes it to a temporary file
and opens it read-only in SQLite. It refuses the image with 400, writing
nothing, unless it is an intact SQLite database with no trigger (tenant SQL
cannot create one), no view over a platform table, and no stored-object record
naming content that the registry records only against other namespaces (such a
record would let you read another tenant's content). Table names are matched as SQLite does, ignoring case, and an
ownership table missing its cid or namespace column is refused (the
platform's schema has had both since the first release of the table). Older backups may still
carry plaintext api_keys rows and stored-object records of other namespaces;
nothing reads them, so they are accepted. After the load the gateway removes
them, checks again for anything above as a backstop, and only then puts the
destination's storage quota back; both run detached from your connection, so hanging up
after the upload does not skip them. It cannot check the size_bytes of the
records it keeps: no gateway call reports a pinned object's size, so the storage
quota counts them as the image says. Functions, their triggers and the other
rows the namespace's gateway reads from its own database come back as the image
has them.
Each gateway runs one whole-database transfer at a time (backup, restore,
rqlite export, rqlite import) and answers 429 while one is running. A backup
and its restore request are held in memory whole (one nacl box), so they are
capped: a namespace database over 256 MiB, more than 50,000 pinned CIDs, or
more than 8 MiB of pins and secrets together is refused with 413, and so is an
rqlite import over 256 MiB. A request announcing more than that (a restore:
256 MiB plus its headers) is refused 413 at the cluster gateway before any of
it is sent on; one that does not announce its length is refused 413 once it has
sent more than that. (The cluster gateway's own /v1/rqlite/import, an
operator's replacement of the registry, is streamed with no cap and no check
beyond the SQLite header.) While it seals a backup the gateway holds about three copies of
the database.
These four routes run on the namespace's own gateway, so orama namespace backup, restore, restore-key and rqlite export and import go to the
namespace host you signed in to (ns-<name>.<domain>), not to the
environment's gateway. With ORAMA_TOKEN, set ORAMA_API_URL to that host. A
gateway's HTTP server cuts every other request off 60 seconds after its headers
(reading) and 120 seconds after its handler starts (writing). On these four
routes, both the cluster gateway's proxy and the namespace gateway move both
deadlines to five minutes from the start of the request, and the proxy waits at
most five minutes: a transfer slower than that fails, and a database of 256 MiB
needs about 1 MiB/s.
RQLite 8 forwards /db/backup and /db/load from a follower to the leader
itself. The gateway never follows a redirect from RQLite (a redirected POST
would be re-sent without its body) and reports one as an error instead.
What it does not do: create the namespace, redeploy deployments or functions onto the new cluster's nodes, or copy the pinned content. IPFS Cluster accepts each pin; the content only arrives if it is still reachable on IPFS. Backups are taken when you run the command; the cluster does not take, store or schedule them, and holds no key that could pay for a storage deal.
A backup in a storage deal
A backup survives its cluster only if a copy lives elsewhere. A private
storage deal on the Orama chain keeps it with providers, under your own keys.
The backup stays sealed to your X25519 key; the deal adds its own slot layer
under your orama-storage-v1 key and repair seed (both read from 0600 files,
never from the command line).
orama namespace backup --key <public key hex> --deal-dir ./slots \
--deal-nonce <32 bytes hex> --deal-replicas 3 \
--storage-key-file ./storage.key --repair-seed-file ./repair.seedThis writes slot-0 ... slot-2, one distinct ciphertext per replica, and
prints each slot's piece root and the orama storage create --class private
command that opens the deal for exactly those pieces (the same nonce and
replica count). Run that command, then orama storage put --deal-id <id> --dir ./slots --rpc <oramad RPC>. --out can be given as well, to keep the sealed
file.
On the new cluster, restore from the deal:
orama namespace restore --from-deal <id> --rpc <oramad RPC> \
--storage-key-file ./storage.key --repair-seed-file ./repair.seed \
--key-file ./backup.key --namespace myapp --dest-key <restore key>The first slot a provider serves with the on-chain root is fetched and opened,
then restored as above. A wrong storage key or repair seed, a truncated slot,
or the wrong backup key stops before anything is sent to the gateway. The deal
runs for the epochs you bought; extend it with orama storage extend. Each
backup is a new deal, and the deal's price comes from your account. Keeping
several (daily, weekly) is your choice of how often you run the command and
which deals you extend.
orama namespace backup-seal and backup-open seal and open any file with
the same keys.