Single-binary backend core

bkn

A backend you own, small enough to read, that outlives the tool that wrote it. Documents, settings, identity, files, an event log, a scheduler and signed webhooks — over one embedded SQLite file. No database server, no runtime to install, no dashboard, no account.

If you want a UI, use PocketBase. It is the closest thing to this and it is very good. bkn makes the opposite trade deliberately — no dashboard, a CLI that is the interface, and a sandboxed script runtime as the extension point instead of compiled hooks. If a human is going to click through your data, PocketBase is the better tool.

Install

curl -L https://github.com/javimosch/bkn/releases/latest/download/bkn -o bkn chmod +x bkn && ./bkn install # copies it onto ~/.local/bin bkn guide # the mental model, embedded and offline bkn help-json # the machine-readable command catalog

Linux x86_64, statically linked, no runtime dependencies. Versions are content hashes, not semver: a server publishes a binary at /version and /dl/bkn, and bkn update verifies and smoke-tests the download before an atomic swap that keeps a .bak.

Seven primitives

Everything a backend keeps rebuilding, and nothing that belongs to your domain. The nine worked examples — forms, i18n, redirects, flags, Stripe webhooks, a CMS — are roughly 1,700 lines of JavaScript between them, and not one needed a line of Go added to the core.

store

Namespaced documents

six verbs, one sort field, a total

Collections of JSON documents with normalization declared on the collection, atomic update operators under compare-and-set, preconditions, self-bounding retention and grouped counts.

kv

Typed settings

string · json · encrypted

Configuration with types, optionally encrypted at rest. An encrypted write with no key configured is an error, never a plaintext write.

auth

Identity, without billing

users · orgs · memberships · tokens

Short-lived JWTs, single-use rotating refresh tokens, platform and organization roles. What it deliberately does not hold is billing — coupling identity to a payment provider is what makes an auth layer impossible to change later.

files

Namespaced blobs

local or S3 · content-addressed

Type allow-lists verified from the bytes, atomic name claims, and signed URLs for private namespaces. Storage is keyed by content hash, not by a caller-supplied path.

events

Append-only log

emit · list · stats · prune

A stream per namespace with grouped stats. An append-only log with no retention is a disk-space incident waiting to happen, so prune is part of the surface.

cron

Scheduled scripts

cron expressions and @shortcuts

Jobs claimed with a compare-and-set so two tickers — or a manual tick racing a daemon — cannot fire the same job twice. Overlapping runs are skipped, not piled up.

script

The escape hatch

sandboxed JavaScript over all of it

Most of what a backend does is not core — it is a script. Stripe webhooks, forms, CSV exports, i18n, redirects, a CMS: each is a handler over documents plus a public URL.

hooks

The inbound counterpart

signature checks · CORS · rate limits

bkn.http.fetch lets a script call out; hooks let the world call in — with origin scoping, a rate limit and a body cap declared per hook.

A session

bkn store create myapp/users --normalize email=trim_lower bkn store put myapp/users --data '{"email":" Ada@Example.IO ","name":"Ada"}' bkn store find myapp/users --where email=ADA@EXAMPLE.IO # normalized on both sides # operators are computed from the current value, under compare-and-set bkn store patch myapp/runs r1 --data '{"tries":{"$inc":1},"log":{"$append":"started\n"}}' bkn store patch myapp/runs r1 --data '{"worker":"w1"}' --if-absent worker # the collection bounds itself — no trim query, no schedule bkn store create app/memories --retain-last 20 --retain-per tag,repo_id export BKN_ENCRYPTION_KEY=$(openssl rand -hex 32) bkn kv set myapp.stripe sk_live_xxx --type encrypted bkn script create waitlist-digest --file digest.js bkn cron create nightly --schedule "0 3 * * *" --script waitlist-digest bkn daemon start # the same primitives over HTTP curl -s localhost:7799/_health

What it deliberately will not do

Each of these was asked for by a real codebase and refused on purpose. Refusing is how the admission rule earns its keep — a rule that only ever admits is not a rule.

RefusedBecause
TransactionsEvery write that must be atomic is one statement. A transaction is caller-held state, and a one-shot CLI over a stateless API has nowhere to hold it — locks is the multi-statement answer.
JoinsDenormalize and pay the write amplification knowingly. Admitting joins is where bkn becomes SQL.
Regex / LIKEA substring guard is usually a flag field that was never written down.
Multi-field sortOrdering is already total — a tiebreak never has to be asked for.
Age-based retentionCount-based is what was measured; events prune --older-than covers the log-shaped case.
An admin UIA UI is a client, not an interface. A CLI can be introspected; a screen cannot.

What gets in

Admit a primitive that removes a class of application code from every embedder. Refuse a query feature that only moves application code into bkn.

bkn should make a complex system smaller, not merely possible. The bar is not “could this app run on bkn” but “would this app be less code on bkn”. An application bkn cannot serve is a gap to close, not a boundary to defend — and the failure mode that rule guards against is well documented: a core that grows to 85k lines because every need became a feature.

Fit-checked against a 131k-line control plane, three primitives took it from 33 statements beyond the store’s surface to 11 — and all 11 were refused rather than absorbed. The core grew by three primitives and no query language.

The contract is the product

A clean-room reimplementation in another language, machin-bkn, passes the same 113-assertion live suite, unmodified. The contract is portable, not a description of one codebase. When bkn stops being useful what remains is a .db file of plain JSON documents and a directory of ordinary JavaScript — nothing has to be migrated off, because nothing was ever locked in.

github.com/javimosch/bkn · VISION.md · AGENTS.md · Deploying · bkn-recipes
Apache-2.0 · one static binary · bkn guide teaches itself offline.