Starting the Database
Start the NukeBase server with configuration options by calling startDB() once at the end of your configuration:
// Basic setup - pass the domain object to startDB
const nukebase = addDomain({
authPath: ["users"],
host: "127.0.0.1", // optional
port: 3000 // optional
});
startDB(nukebase);
addDomain Configuration Options:
- authPath: Array - path to user authentication data (e.g.,
["users"]) - host: String (optional) - the IP address to bind to
- Use "127.0.0.1" to accept connections only from the local machine (default)
- Use a specific IP address like "126.23.45.1" to bind to that server address
- Use "0.0.0.0" to accept connections from any IP
- port: Number (optional) - the port to listen on (default: 3000)
Environment Variables
The server reads these from the environment. Every one has a working default — you normally set none of them.
Deployment
| Variable | Default | Effect |
|---|---|---|
DOMAIN | — | Public origin used to build magic-link URLs and console output (e.g. https://your-app.nukebase.com). |
SOCKET | — | If set, the server listens on this Unix domain socket instead of host/port (useful behind nginx/Caddy). The socket file is created, chmoded to 777, and replaced if it already exists. |
TRUST_PROXY | auto | Peer-only override. See the note below — you almost certainly do not need this. |
DB_ALLOW_MISSING_SPLITS | 0 | Set to "1" to downgrade a $split marker whose subdirectory (or its data.json) is missing or empty from a fatal startup error to a logged warning, treating the subtree as empty. A data.json that does not parse is always fatal. Default is fail-fast so a bad disk state can't silently produce data loss on the next flush. |
BACKUP_KEEP_DAYS | 7 | How many days of daily backups to keep under server/backup/. Dated directories older than this are removed after each successful backup; nothing else in that directory is touched. 0 keeps every backup forever. |
The client IP is worked out from where the connection came from — there is nothing to configure. A forwarding header (x-real-ip, then x-forwarded-for) is honored only when the peer could plausibly be a proxy you put there:
- Unix socket — no peer address exists, so the header is the only source of a client IP. This is how the managed platform runs.
- Loopback or private address (127.0.0.0/8, ::1, RFC1918, fc00::/7, fe80::/10) — a local proxy. The header is used if present, otherwise the peer.
- Public address — the open internet. The peer address wins and the header is ignored, because a caller on a public port can set and rotate that header themselves.
This replaced an earlier TRUST_PROXY flag that had exactly one correct value per deployment and no way for anyone to know which. Setting TRUST_PROXY="false" or "0" still forces the peer address unconditionally — useful only for a topology that answers the question wrongly, such as a reverse proxy on another host reaching the engine over a public address. Any other value, including "true", has no effect.
On the hosted platform
A hosted project's process is started by the platform, so its environment has to come from the platform too. Keep a .env file in your project root — the same file node --env-file=.env server/database.js loads locally — and push it separately from your code:
nukebase env push # make the hosted environment equal to ./.env, then restart
nukebase env list # variable names currently set (add --show to print values)
Every variable in the file lands in process.env — your own settings and any of the engine tunables above. env push replaces the whole set, so removing a line locally and pushing removes it on the server. .env is never carried by an ordinary push or pull; on the server it is kept outside the project directory, root-owned, where no sync, ZIP download or restore point can reach it. SOCKET and DOMAIN are set by the platform and are refused. Values are single-line and up to 8 KB each; encode anything multi-line, such as a PEM key, before putting it in the file.
Files
| Variable | Default | Effect |
|---|---|---|
FILES_DIR | server/files | Root of the file store. Nothing outside it is reachable. |
FILE_MAX_BYTES | 67108864 (64 MB) | Hard ceiling on a single file, enforced on the bytes that actually arrive. |
FILE_MAX_DIR_ENTRIES | 10000 | Entries returned by one directory listing before it is truncated. |
FILE_WRITE_RATE_MAX | 120 | Uploads + deletes per IP per minute. |
Limits and capacity
| Variable | Default | Effect |
|---|---|---|
WS_RATE_LIMIT_MAX | 10000 | WebSocket messages per second per connection. Exceeding it sends a rateLimit notice and closes the socket. Also caps { durable: true } writes per session per second. Raise it for bulk loaders. |
HTTP_MAX_BODY_BYTES | 1048576 (1 MB) | Default body cap for postWithBody handlers. Override per route with { maxBytes }. |
QUERY_MAX_RESULTS | 1000000 | Maximum records one query may return before it is truncated. |
QUERYSUB_MAX_RESULTS | 1000 | Matching records above which a querySub is refused or dropped. |
WINDOW_SUB_SCAN_MAX | 1000 | Collection size above which a windowed subscription requires an index-order plan. |
SPLIT_THRESHOLD_MB | 128 | Size in MB above which a data.json is split into per-key subdirectories. A flush re-serializes each dirty file in full, so this also bounds how long one flush can stall the engine (roughly 2.7 ms per MB). Lower it to trade more, smaller files for shorter stalls. |
FLUSH_CACHE_MB | 256 | Memory budget for the flush cache, which keeps the serialized text of each top-level key so a flush only re-serializes keys written since the last one. Costs about the on-disk size of the cached files. Once full, further keys are simply re-serialized as before. 0 disables it. |
MAX_QUERY_BUCKETS_PER_PATH | 64 | Distinct query-subscription windows allowed on one path/event/action. |
MAX_TOTAL_INDEX_ENTRIES | 8000000 | Total index entries held across the whole process. |
KEY_INDEX_MIN_RECORDS | 1000 | Collection size at which an automatic $key index becomes worth building. |
Graceful shutdown is automatic. On SIGTERM or SIGINT the server clears its flush timer, writes every dirty subtree to disk, and exits — so a normal restart never loses the last few seconds of writes. It exits with process.exitCode (0 unless you set it): to flush and report a failure — from an uncaughtException handler, say — set process.exitCode = 1 before sending yourself SIGTERM, so a supervisor can tell a crash from a deliberate restart.