Self Hosting - UpstandPlatform/OpenRive GitHub Wiki
Run OpenRive on a server, NAS or your own Docker Desktop so a team can share it. Three options:
| Option | Database | Best for |
|---|---|---|
| Docker Compose + PostgreSQL | PostgreSQL service | Teams and servers (recommended) |
| Docker, single container | Embedded (PGlite) in a volume | Personal use, NAS, quick trials |
| Bun without Docker | Either | Servers where you manage the runtime yourself |
A self-hosted server (one with a DATABASE_URL) asks for a sign-in: an email and a password. The first person to
sign up becomes the administrator; OPENRIVE_SIGNUP decides whether anyone else may. Configure SMTP to enable
verification email and workspace invitations. See Accounts and sign-in.
For a second, coarser gate — or to keep the instance completely private — set an access token, which puts HTTP Basic auth in front of everything:
OPENRIVE_ACCESS_TOKEN=a-long-random-passwordEvery request then requires HTTP Basic auth with that password (any user name). Browsers show a sign-in prompt once. Serve it over HTTPS (see reverse proxy) so the password is not sent in plain text.
Requires Docker Desktop (Windows/macOS/Linux) or Docker Engine with the Compose plugin.
git clone https://github.com/UpstandPlatform/OpenRive.git
cd OpenRive
cp .env.example .envEdit .env:
OPENRIVE_URL=https://openrive.example.com
OPENRIVE_SIGNUP=first
OPENRIVE_AUTH_SECRET=a-long-random-secret-at-least-32-characters
POSTGRES_PASSWORD=another-long-random-password
# Optional second gate:
# OPENRIVE_ACCESS_TOKEN=a-long-random-password
# OPENRIVE_PORT=3000
# Redis is included by the Compose stack; its URL inside the stack is
# redis://openrive-redis:6379 and its data is memory-only by design.Start it:
docker compose up -dOpen http://<server>:3000 (or http://localhost:3000 with Docker Desktop).
What runs:
| Service | Image | Data |
|---|---|---|
openrive |
ghcr.io/upstandplatform/openrive:latest |
volume openrive-data (CLI imports/exports) |
db |
postgres:17-alpine |
volume openrive-db
|
redis |
redis:7.4-alpine |
no volume; rate limits, locks and queues are ephemeral |
Nothing is compiled on your server: the image is published for linux/amd64 and linux/arm64 and simply pulled.
See Which image you get to pin a version or follow main.
Drizzle migrations run automatically when the app starts, and the app waits (up to OPENRIVE_DB_WAIT_SECONDS) while
PostgreSQL finishes starting beside it, so the order the two containers come up in does not matter.
Redis is also started as part of this stack. It uses neither RDB snapshots nor
the append-only log, and has no disk volume. Redis loss clears only ephemeral
coordination state; the PostgreSQL volume remains the source of truth for
accounts, sessions and projects.
- Run
docker compose up -donce in the project folder (in a terminal or Docker Desktop's built-in terminal). - The openrive stack then appears under Containers, where you can start, stop, view logs and open port 3000.
- Volumes (
openrive-db,openrive-data) are listed under Volumes and can be backed up or exported there.
docker compose logs -f openrive # logs
docker compose pull && docker compose up -d # update to the newest image
docker compose down # stop (data is kept in volumes)
docker compose exec openrive openrive list # use the CLI inside the container
docker compose exec openrive openrive users add Sam --role editorghcr.io/upstandplatform/openrive is public — no registry sign-in — and is built for linux/amd64 and
linux/arm64 by the Image workflow.
| Tag | What it is |
|---|---|
latest |
the newest build, moved on every publish (default) |
1850d37 |
the exact commit it was built from |
0.0.6, 0.0
|
a release, or the newest patch of that minor |
Pin a tag in .env to stay on one version:
OPENRIVE_TAG=0.0.6To run your own build instead of the published image — a fork, or a change to the Dockerfile:
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --buildNo database service: OpenRive runs PostgreSQL (PGlite) inside the /data volume.
docker compose -f docker-compose.standalone.yml up -dOr with plain Docker:
docker run -d --name openrive -p 3000:3000 \
-e OPENRIVE_ACCESS_TOKEN=a-long-random-password \
-v openrive-data:/data ghcr.io/upstandplatform/openrive:latestProjects live in the embedded database inside the openrive-data volume, mounted at /data.
To use an existing PostgreSQL database instead, add -e DATABASE_URL=postgres://user:pass@host:5432/openrive.
bun install --frozen-lockfile
bun run build
OPENRIVE_ACCESS_TOKEN=... bun run cli serve --host 0.0.0.0 --port 3000Add DATABASE_URL=postgres://… to use a PostgreSQL server instead of the embedded database. Keep it running with a
process manager, for example systemd:
# /etc/systemd/system/openrive.service
[Unit]
Description=OpenRive
After=network.target
[Service]
WorkingDirectory=/opt/openrive
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=OPENRIVE_DATA_DIR=/var/lib/openrive
Environment=OPENRIVE_ACCESS_TOKEN=change-me
ExecStart=/usr/local/bin/bun run start
Restart=always
User=openrive
[Install]
WantedBy=multi-user.targetTo build a standalone server bundle, which is what the Docker image uses, run NEXT_OUTPUT=standalone bun run build,
then node .next/standalone/server.js (copy public/ and .next/static/ next to it).
Point the platform at the repository and let it run docker-compose.yml. Set
OPENRIVE_URL to the public HTTPS domain, POSTGRES_PASSWORD to a strong
password, and either OPENRIVE_AUTH_SECRET to a strong 32+ character value or
leave it unset so OpenRive generates and persists one in the database. Keep
OPENRIVE_SIGNUP=first or off after the initial administrator is created.
The optional OPENRIVE_ACCESS_TOKEN adds HTTP Basic auth in front of the
application. The published image is pulled, so the deploy host does not need
to build the project.
The Compose file provisions the memory-only Redis service automatically; do not
attach a persistent volume to it. If you override the service, keep
--save "" and --appendonly no so rate-limit and lock state cannot become a
second data store.
These platforms clone with --recurse-submodules. The Rive SDK forks under vendor/ are marked update = none, so
they are skipped: they are developer tooling, and one of them has an ssh:// submodule that a build server cannot
read. If a platform forces submodules anyway and the clone fails with "Host key verification failed", tell git to
use https instead of ssh on the build host:
git config --global url."https://github.com/".insteadOf "[email protected]:"Use docker-compose.cloud.yml when the deployment is intended to be the cloud edition. It runs PostgreSQL, ephemeral
Redis, and MinIO. MinIO's data volume is persistent because it contains project files; Redis deliberately has no
volume and no RDB/AOF persistence. Set these values in the deployment platform rather than committing them:
OPENRIVE_EDITION=cloud
OPENRIVE_URL=https://openrive.example.com
OPENRIVE_TAG=0.0.12
OPENRIVE_AUTH_SECRET=<random 32+ character secret>
RESEND_API_KEY=<Resend API key>
OPENRIVE_EMAIL_FROM=[email protected]
POSTGRES_PASSWORD=<random database password>
MINIO_ROOT_USER=<random storage access key>
MINIO_ROOT_PASSWORD=<random storage secret>
OPENRIVE_STORAGE_ACCESS_KEY_ID=<random application access key>
OPENRIVE_STORAGE_SECRET_ACCESS_KEY=<random application storage secret>
OPENRIVE_STORAGE_BUCKET=openriveThe cloud stack can later point at AWS S3 or Cloudflare R2 by replacing the storage endpoint and credentials in the application environment; the application code does not change. Billing is not part of this edition.
Put OpenRive behind a proxy that handles TLS.
Caddy (automatic certificates):
openrive.example.com {
reverse_proxy localhost:3000
}
nginx:
server {
listen 443 ssl http2;
server_name openrive.example.com;
ssl_certificate /etc/letsencrypt/live/openrive.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/openrive.example.com/privkey.pem;
client_max_body_size 100m; # large .riv files and images
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}| Mode | Backup | Restore |
|---|---|---|
| PostgreSQL (compose) | docker compose exec db pg_dump -U openrive openrive > openrive.sql |
docker compose exec -T db psql -U openrive openrive < openrive.sql |
| Embedded (volume) | docker run --rm -v openrive-data:/data -v $PWD:/b alpine tar czf /b/openrive.tgz -C /data . |
extract back into the volume |
| Embedded (Bun) | copy OPENRIVE_DATA_DIR
|
copy it back |
Single projects travel as .riv files: openrive export <project> file.riv and openrive import file.riv.
Projects stored by older versions in data/projects/<id>/ are imported automatically the first time the new version
starts with an empty database, or by hand with openrive db import. See Storage.
GET /api/health returns HTTP 200 when the app, its database, and configured
Redis are working. It includes storage and redis ("ready" or
"disabled"). A configured but unavailable Redis returns 503, so a deployment
cannot appear healthy while distributed rate limiting is broken. It needs no
access token, and the Docker image uses it as its HEALTHCHECK.
OpenRive is light: about 150 MB of RAM for the app, plus PostgreSQL. Rendering happens in each user's browser, not on the server.
See also: Configuration · Storage · Troubleshooting
This page is generated from docs/self-hosting.md. Suggest an edit.