vaultbase

mcp
Security Audit
Warn
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 9 GitHub stars
Code Warn
  • network request — Outbound network request in admin/src/api.ts
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Self-hosted backend in a single binary.

README.md

Vaultbase

Your whole backend. One file.
Self-hosted backend in a single binary: collections, REST, auth, realtime, files, hooks, queues and MCP for AI agents.

Latest release Release build Downloads MIT license
@vaultbase/sdk @vaultbase/mcp Linux · macOS · Windows Built with Bun

Live demo · Website · Docs · Releases · TypeScript SDK · MCP bridge

Try it

The live demo is the admin UI on a sample blog. The login is filled in for you, and the data resets every hour.

Install

Linux or macOS (detects x64 / arm64, and glibc / musl on Linux):

curl -fsSL https://get.vaultbase.dev | sudo sh

Windows, in PowerShell:

irm https://get.vaultbase.dev/install.ps1 | iex

On Linux it installs the latest release to /usr/local/bin/vaultbase, creates a vaultbase service user and a systemd unit, and starts it on :8091. On macOS it installs the binary to /usr/local/bin and you run vaultbase yourself. On Windows it installs to %LOCALAPPDATA%\Programs\Vaultbase and adds it to your PATH. Open http://localhost:8091/_/ for the setup wizard.

  • sudo sh -s -- --verify-sig checks the binary's cosign keyless signature first (needs cosign on PATH).
  • --version vX.Y.Z, --port N, --no-start, --no-systemd, --install-dir DIR are also available.
  • Or download a binary from Releases.

From source:

bun install
bun run build           # admin UI + binary → ./vaultbase[.exe]
./vaultbase             # serves on :8091

Features

  • Collections + REST — a real table per collection (vb_<name>), 16 field types, base / auth / view collections. Filter, sort, expand, field projection, batch, ETag concurrency
  • Auth — email + password, OAuth2 with PKCE (Google, GitHub, Apple, Microsoft, Discord, GitLab and more, plus generic OIDC), OTP / magic links, MFA / TOTP, anonymous users, admin impersonation
  • Realtime — WebSocket + SSE; subscribe to a collection, a record, one kind of event (posts.create) or *, checked against your API rules
  • Files — local disk, S3 or R2; thumbnails, MIME / size validation, protected files with one-time tokens
  • Server-side logic — hooks, custom routes, cron, queue workers, webhooks, feature flags and push notifications, edited live in the admin UI with Monaco autocomplete, or kept as TypeScript files in a vb_hooks/ folder (with imports and npm packages) that the admin UI edits too
  • MCP for AI agents — built-in Model Context Protocol server; tools generated per collection, scope-gated tokens, every tool call written to the audit log
  • Operations — request logs, admin audit log, record history with point-in-time restore, encrypted fields (AES-GCM), scheduled backups (a file copy on SQLite, pg_dump on Postgres), Prometheus metrics
  • Database — SQLite by default: one file, read worker threads on spare cores. Postgres (VAULTBASE_DATABASE_URL), including several servers on one database. See Databases
  • Security — brute-force lockout, CORS allow-list, password policy with optional HIBP check, SSRF egress guard for hooks
  • One binary — no native deps, admin UI embedded; releases are cosign-signed with CycloneDX SBOMs

Stack

  • Runtime: Bun
  • Framework: Elysia
  • DB: SQLite (bun:sqlite), or Postgres (Bun.sql)
  • ORM: Drizzle
  • Admin UI: React 19 + Vite + React Router v7 + Zustand + PrimeReact + Monaco + Quill

Configuration (env vars)

Var Default Notes
VAULTBASE_PORT 8091 Listen port
VAULTBASE_DATA_DIR ./vaultbase_data DB, uploads, logs, secrets
VAULTBASE_DATABASE_URL none A postgres://… URL to run on Postgres instead of the SQLite file
VAULTBASE_READ_WORKERS cores − 2 Read worker threads for list and get-one requests on SQLite (at least 1 on two cores or more, at most 8; 0 keeps one thread)
VAULTBASE_JWT_SECRET auto-generated Persisted in <dataDir>/.secret
VAULTBASE_ENCRYPTION_KEY auto-generated Persisted in <dataDir>/.encryption-key. Encrypts secret settings and encrypted fields. Base64 / hex / 32-char string (32 bytes)
VAULTBASE_RATE_* rule-based See Settings → Rate limiting

Production deployment

Vaultbase is one process (with read worker threads on SQLite) and does not terminate TLS or
compress responses in-process
. Both responsibilities belong to a reverse
proxy in front of the binary — nginx, Caddy, or Cloudflare. In-process
compression was tried and removed: it blocked the event loop on
Bun.gzipSync, regressed RPS by ~14%, and doubled p99.9.

Recommended topology

Client ── HTTPS ──> nginx / Caddy / CF ── HTTP ──> Vaultbase :8091
                       │                              │
                       │ TLS termination              │ Bun + SQLite (WAL)
                       │ gzip / brotli                │ Single binary
                       │ HTTP/2                       │
                       │ Rate limit (defense)         │
                       └──────────────────────────────┘

Sample nginx config

upstream vaultbase {
    server 127.0.0.1:8091 keepalive 64;
    keepalive_timeout 60s;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    # Compression — handled here, not in Vaultbase.
    gzip on;
    gzip_types application/json text/plain;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_vary on;

    # WebSocket realtime
    location /realtime {
        proxy_pass http://vaultbase;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_read_timeout 86400s;
    }

    location / {
        proxy_pass http://vaultbase;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Sample Caddy config

api.example.com {
    encode gzip

    @ws path /realtime
    reverse_proxy @ws localhost:8091

    reverse_proxy localhost:8091 {
        header_up X-Forwarded-For {client_ip}
        header_up X-Forwarded-Proto {scheme}
    }
}

Backups

# Local file
vaultbase backup --to /var/backups/vaultbase-$(date +%F).db

# Gzip
vaultbase backup --to /var/backups/snap.db --gzip

# S3 / R2 / B2 (creds via env)
AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... \
  vaultbase backup --to s3://my-bucket/vb/snap.db

AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... R2_ENDPOINT=https://<accountid>.r2.cloudflarestorage.com \
  vaultbase backup --to r2://my-bucket/vb/snap.db --gzip

Snapshots are atomic via SQLite's VACUUM INTO — concurrent writers are
serialised but not blocked, and the resulting .db file is self-contained
(no *-wal / *-shm sidecars to copy alongside). Cron-friendly:

30 3 * * * vaultbase /usr/local/bin/vaultbase backup --to s3://bucket/vb/snap-$(date +\%F).db --gzip --quiet

Restore via POST /api/admin/restore with the snapshot bytes (admin only)
or by replacing the DB file while the service is stopped. An online restore
checks the file first, stops the read workers and the checkpointer while it
swaps it, and starts them again; it is refused under cluster mode. A database
backup is the records, schema and settings: uploaded files, the hooks folder,
.secret / .encryption-key and your environment are separate, so back up
<dataDir>/ as a whole.

Cluster mode (multi-process, deprecated)

Cluster mode is deprecated. Read worker threads (on by default) spread SQLite reads over
spare cores in one process, and several servers can share one Postgres database;
use those. Cluster mode remains for existing setups, without online restore and with
If-Match checked per process.

A single Bun process is single-threaded — caps at one CPU core. For higher
throughput on multi-core hosts, use the cluster orchestrator:

# Auto: one worker per available CPU core
bun src/cluster.ts

# Or explicit count
VAULTBASE_WORKERS=4 bun src/cluster.ts

# Same via npm script
bun run start:cluster

The parent process spawns N workers, all sharing port VAULTBASE_PORT via
Bun.serve({ reusePort: true }). On Linux the kernel load-balances incoming
connections across workers. Workers run identical code; SQLite WAL handles
concurrent readers natively.

Health check: GET /_/health returns the responding worker's id + pid +
uptime — useful to verify load balancing. It only says the process is up; point
readiness probes at GET /_/ready, which checks the database, storage, read
workers, queues and log writes and answers 503 when the server can't serve.

Graceful shutdown: SIGTERM / SIGINT to the parent broadcasts to
workers; each drains its log buffer and closes its DB handle. 30s timeout,
then SIGKILL.

Crashed worker → automatic restart with 1s backoff.

Platform notes:

  • Linux: full SO_REUSEPORT load balancing. ~Nx throughput on N cores
    (real ~0.85x — some contention is unavoidable).
  • macOS and Windows: the port can be shared, but the kernel doesn't
    distribute connections: one worker takes all of them and the rest sit idle
    (measured on macOS: 4 workers, one at 100% CPU, three at 0%). Cluster mode
    runs but does not multiply throughput there. Use it for fault-tolerance
    (worker auto-restart) only; deploy on Linux for performance.

SQLite under cluster mode (Phase 6a — current):
All workers open the same DB file. WAL allows concurrent readers; writes
serialize on the file lock. Read-heavy workloads scale near-linearly with
worker count. Write-heavy workloads may see lock contention — measure
before adopting the dedicated-writer-process pattern.

Operational notes

  • Run Vaultbase under a process supervisor (systemd, runit, pm2). It does
    not daemonize itself.
  • Back up <dataDir>/ periodically: on SQLite it holds the database, uploads
    (local storage), the hooks folder and the keys. On Postgres, back up the
    database with pg_dump (scheduled backups do this when it's installed) and
    keep <dataDir>/ for uploads and the keys.
  • Set VAULTBASE_JWT_SECRET explicitly in production. The auto-generated
    fallback is per-host and will rotate if <dataDir>/.secret is wiped,
    invalidating every issued token.
  • Keep VAULTBASE_ENCRYPTION_KEY (or <dataDir>/.encryption-key) with your
    backups: encrypted values can't be read without it. Several servers on one
    Postgres database need the same JWT secret and encryption key.

Cross-compile

bun run build:linux-x64
bun run build:macos-arm64
bun run build:windows-x64
# or all five
bun run build:all

Output: releases/vaultbase-<target>[.exe]

Development

bun install && (cd admin && bun install)   # the admin has its own dependencies
bun run dev         # backend on :8091
bun run dev:admin   # admin on :5173 (proxies /api → :8091)

bun test            # backend tests
bun run typecheck

Note on local bun run build

The compile pipeline patches node_modules/imagescript/codecs/node/index.js
in place to stub the native encoder (Bun --compile cannot bundle
dlopen-able .node binaries — the patched stub keeps top-level imports
working in the single-file binary; JPEG/GIF encode paths fall through to
PNG / @jsquash codecs).

Side effect: after running any bun run build:*, the imagescript native
encoder is gone from node_modules and 2 GIF/JPEG-encode tests will fail
locally. Restore with:

bun install --force

CI is unaffected (every workflow run installs fresh).

Community and support

License

MIT.

Reviews (0)

No results found