NotusBase

5. First server

No package is public yet: the downloads open with the first public version. The steps below show how NotusBase works once you have one.

From the Linux package to a system online: the binary under systemd with its own HTTPS, the server set up from a page in the browser, and the functions deployed. Five commands, two on the server and three on your machine. The day to day after that (updating, restoring, alerts) is in Operations.

What you need

A Linux machineWith systemd, x86_64 or aarch64, and glibc 2.38 or newer: Ubuntu 24.04, Debian 13. Debian 12 is too old. You reach it by SSH, with sudo
A name pointing at itFor the functions, such as api.example.com, with ports 80 and 443 open and nothing else. The binary gets the certificate itself, with no proxy. No name: Without a domain
The Linux packagenotusbase-VERSION-linux-x86_64.tar.gz or -linux-aarch64, already on the server
Your machineThe project, working with notusbase dev as in the quick start, and the notusbase binary of the same version

The server gets no Node, no Go, no proxy and no copy of your project. The code goes up through notusbase deploy, from your machine.

1. Install

On the server:

tar xzf notusbase-VERSION-linux-x86_64.tar.gz
sudo notusbase-VERSION-linux-x86_64/install.sh --env production

--env names the environment, and the first install refuses without it: production for the server your customers use, staging for a copy, where email is captured and fetch reaches only the names you list (Environments). The script then asks one thing, the domain, and starts the service:

Domain of this server, pointing here already (empty: none, the dashboard only through an SSH tunnel): api.example.com
the guide: /opt/notusbase/releases/VERSION/guide/05-first-server.md
installed VERSION and started.
open https://api.example.com/_/#/setup/Xq3...k8 within 30 min. The link works once. Or, through the tunnel (ssh -N -L 8091:127.0.0.1:8091 <server>): http://localhost:8091/_/#/setup/Xq3...k8

It creates the notusbase user, puts the binary in /opt/notusbase and the data in /var/lib/notusbase. The settings are two files. /etc/notusbase/notusbase.env is root's, and the service only reads it: the folders, the ports and the flags. /var/lib/notusbase/setup.env is the service's own: what the setup page saves. The binary reads it when it starts, over the first one, so a name there wins; systemd never reads it, so nothing that runs as root gets its lines. In a script, pass the name instead of answering: install.sh --domain api.example.com. The domain is asked only on the first install; to set or change it later, see Changing the domain.

With a domain, the binary answers on 443 and gets its certificate from Let's Encrypt on the first visit; port 80 only redirects to HTTPS and answers the certificate challenge. The certificate renews by itself. The dashboard, deploy and metrics stay on a second port, 127.0.0.1:8091, which only the SSH tunnel reaches.

2. The setup page

Open the link in the browser. It works once, for 30 minutes, and the page closes an hour after the sign-in, finished or not; sudo systemctl restart notusbase writes a new link, which sudo cat /var/lib/notusbase/pb_data/.setup_link shows. The link stays out of the system log, which more users than root can read. If the name does not point here yet, or the certificate fails, the second link of the message works through the tunnel of step 3. The page, in order:

StepWhat it does
Sign inCreates the first superuser, the dashboard account. If one exists, signs in with it
Who calls the serverThe origins your UI is served from (https://app.example.com), the login collection (users) and whether anyone can sign up
Backup off this machineAn S3 or R2 bucket and the encryption key, which the browser generates. The page writes a test file, reads it back and deletes it before saving. Keep a copy of the key off the server: without it the backup does not open. "Go without, for now" writes NOTUSBASE_S3=off
EmailThe SMTP server for login codes, password resets and alerts. A test message goes out before saving
Deploy keyShows the key the CLI deploys with, and the two commands of step 3
CheckRuns doctor.sh as the service user and shows the result
FinishWrites NOTUSBASE_SETUP=done to setup.env, closes the page for good and restarts the service with the new settings

What the page does not do: it never runs anything as root and never shows a secret back (a saved key shows as "saved"). It writes only setup.env, and only the names it sets, plus the email settings of the dashboard; notusbase.env stays as install.sh left it. The settings of the page are in Reference, and editing setup.env by hand, followed by sudo systemctl restart notusbase, does the same. To open the page again: NOTUSBASE_SETUP=pending in setup.env, and restart.

3. Deploy the code

On your machine, the tunnel to the administration port, open in another terminal:

ssh -N -L 8091:127.0.0.1:8091 YOUR-SERVER

Then, in the project folder, with the key shown on the setup page:

export NOTUSBASE_DEPLOY_KEY=...
notusbase deploy --url http://localhost:8091
plan: new table: teams
plan: new table: members, with 2 indexes
plan: new table: tasks, with 2 indexes
deploy 2 live on http://localhost:8091: 8 functions, 63 ms on the server, 487 ms in total

Use localhost, not 127.0.0.1: with NOTUSBASE_ADMIN_HOSTS=localhost, any other name is refused with Error: Unknown host. The dashboard is at http://localhost:8091/_/ through the same tunnel, with the superuser of step 2. Turn its second factor on: Dashboard and second factor.

The deploy key deploys code, reads the logs and creates accounts: keep it like a password. To read it again later: ssh YOUR-SERVER 'cd / && sudo -u notusbase /opt/notusbase/current/notusbase deploy-key --dir /var/lib/notusbase/pb_data'.

That is the server. Accounts of the app come from signup, if it is open, or from notusbase user add [email protected] --url http://localhost:8091, which works with signup closed.

The UI

The UI is static files, and goes to any static host: a CDN, a bucket, or Caddy on this same machine (below). It needs the address of the functions at build time:

VITE_NOTUSBASE_URL=https://api.example.com npx vite build

The address in VITE_NOTUSBASE_URL and the origin set on the setup page are written exactly as the browser shows them: with https://, and with the port when it is not 443.

Check from outside

From a machine outside the server's network. Every line must say 404:

for p in /_/ /api/notusbase/stats /api/notusbase/deploy /api/sql /api/collections/_superusers/records /api/settings /api/backups; do curl -s -o /dev/null -w "%{http_code} $p\n" https://api.example.com$p; done

And curl -s -o /dev/null -w "%{http_code}\n" https://api.example.com/api/health says 200. While the setup page is open, /_/ answers on the public name too, so run this after finishing it.

Changing the domain

In the dashboard, Settings → Domain. Point the new name at the server in your DNS first, then type it there:

  1. The server checks that the name reaches it: a token on port 80, fetched through the name, the way Let's Encrypt will. When it cannot confirm from inside the machine (some providers do not let a machine reach its own public address), the page says what it found and offers "Save anyway".
  2. It saves the name in setup.env, with the one before it as NOTUSBASE_DOMAIN_PREVIOUS, and restarts the service. With the domain coming from setup.env, the binary takes the ports a domain needs: 80 and 443 for the app, 127.0.0.1:8091 for the dashboard, no --behind-proxy, and the name in --origins.
  3. After it comes back, it asks for the certificate. The page and notusbase doctor show where that is: asking, ready until a date, expired, or the authority's error. Both look at the certificate the server holds now, so a renewal shows its new date.

The apps already published point at the old name. "Keep the old name too" keeps both, each with its own certificate, until the apps are published again. "Go back to …" returns to the name in use before the last change, with no check; nothing goes back by itself.

What the screen refuses: ports 80 or 443 held by another program (a proxy such as Caddy: the domain is in your proxy, change it there), a dashboard on a public port (set NOTUSBASE_ADMIN_HTTP=127.0.0.1:8091 in notusbase.env first), and a server not started by the unit of install.sh. The dashboard never moves to the public name: it stays on the tunnel. NOTUSBASE_DOMAIN in notusbase.env still works; a name saved from the page wins over it, and going back to it takes the page's line out. A NOTUSBASE_DOMAIN in setup.env that is not a valid name, written by hand, never stops the service: it starts without it, with the ports of notusbase.env, and the page and notusbase doctor say why.

Without a domain

Leave the question empty. The server then listens on loopback only, 127.0.0.1:8090 for the functions and 127.0.0.1:8091 for the rest, and the link is http://localhost:8091/_/#/setup/.... Open the tunnel of step 3 first and the link works in your browser. The page never answers in plain HTTP on a public address.

To publish it later behind a proxy that holds the certificate, such as Caddy, the package brings Caddyfile.example: three blocks, the functions (reverse_proxy 127.0.0.1:8090), the UI (file_server) and, commented out, the dashboard under a name of its own. That setup keeps --behind-proxy in NOTUSBASE_FLAGS, which is how install.sh leaves it without a domain. To let the binary hold the certificate instead, with no proxy, give it a domain from the dashboard: Changing the domain.

sudo apt-get install -y caddy
sudo sed -e 's/api\.example\.com/YOUR-API-NAME/g' -e 's/app\.example\.com/YOUR-APP-NAME/g' notusbase-VERSION-linux-x86_64/Caddyfile.example | sudo tee /etc/caddy/Caddyfile > /dev/null
sudo systemctl restart caddy

Without a public name

For a test or an intranet, with names that no certificate authority will sign. Add tls internal inside each block of the Caddyfile (names ending in .localhost get it by themselves). Caddy then creates a certificate authority of its own and signs with it, and every client has to be told to trust it. The root certificate is public, not a secret. With Caddy installed by apt:

ssh YOUR-SERVER sudo cat /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt > caddy-root.crt
ClientHow it trusts the file
curlcurl --cacert caddy-root.crt https://...
Node: a test, a script, ConvexHttpClient, ConvexClientNODE_EXTRA_CA_CERTS=caddy-root.crt node script.mjs
notusbase, on macOS and on Linux, calling an https:// address (deploy, run, logs, user add with --url)SSL_CERT_FILE=caddy-root.crt notusbase deploy --url https://...
A browserImport the file as a trusted authority in the system or in the browser. Until then the page shows the certificate warning, and the sync does not connect

Without the file, Node stops with unable to get local issuer certificate and notusbase with tls: failed to verify certificate. Through the SSH tunnel the address is http://localhost:8091, with no certificate involved, so notusbase deploy needs nothing.

The dashboard under a name

Optional, and only behind a proxy. The third block of the Caddyfile publishes the administration port under a name of its own, such as admin.example.com: uncomment it, add the name to NOTUSBASE_ADMIN_HOSTS keeping localhost, restart the service and reload Caddy. Then notusbase deploy --url https://admin.example.com works without the tunnel.

A superuser password on the internet opens everything: it deploys code, runs SQL and exports the database. Use a long password and turn the second factor on first: Dashboard and second factor.

What comes next

A new version of your codenotusbase deploy --url http://localhost:8091 again, through the tunnel. No restart, no client drops. --plan first shows what changes in the database
A new version of NotusBasesudo /opt/notusbase/update.sh <new package dir>, with automatic rollback: Updating
Checking the machinesudo /opt/notusbase/doctor.sh, which sees more as root than on the setup page: Checking the machine
The backup, checkednotusbase backup-check --url http://localhost:8091; the server repeats it every 24 hours: Backup
Knowing when it breaksMetrics and alerts
Every variable of notusbase.env and setup.envReference