PRIVATE · MULTI-TENANT · ENCRYPTED · SELF-HOSTED

Object storage thatshows its work.

Lockwell is a private, encrypted object store with an S3-compatible API, a native JSON API, and first-party Go, Node, and Java SDKs: one binary, no second store, no glue. The claims on this page are measurements, and the repository carries the harnesses to re-run them.

MEASURED, NOT CLAIMEDPASS
crash takeover, acked writes intact
16 s
disk used for the same bytes, vs MinIO
0.54x
multipart PUT, 64 MiB at 64 clients
2.2x MinIO
S3 production suite, 24 SDK clients
0 failures
public buckets, anonymous reads
refused

measured 2026-06-11 · one host, Docker Compose · methodology · reproduce: make bench · make prod-authority-test

THE GATE

Every request meets the gate

This is the whole security model in one scene. A request that presents a tenant key passes and its object is sealed, encrypted, at rest. A request without one is refused with a 401. There is no anonymous path to refuse carefully or configure away.

carries a tenant key: passes, sealed at rest no key: refused at the gate, 401

Sharing still works the safe way: mint an expiring signed link (a 7-day client download, a one-hour browser upload) and hand it out. The link carries the permission, nobody gets a key, and it dies on schedule.

REFUSED BY DESIGNpublic bucketsanonymous readsSSE-KMS without a KMSsilent fallbacks for unsupported S3 callsthe full list →
FOR THE PERSON WHO PAYS FOR IT

The case for replacing a working MinIO

Benchmarks do not move a production system; lower bills, fewer moving parts, and a safe exit do. This is what changes the day you switch, and the honest way to find out without risking anything.

The same bytes, about half the disk

Measured 0.54x MinIO's disk for an identical write set, before deduplication gets to work on real data. Storage is the line item that grows forever; this halves its slope.

Tenancy you do not have to build

Per-customer isolation, scoped keys, quotas, and an audit trail are the server's job here, not an auth layer your team writes around a bucket and maintains for years.

Compliance in the box

Always-on encryption at rest, Object Lock retention and legal hold, audit logging, scrub and repair, and rehearsed backup-restore drills. The evidence your auditor asks for is the product's normal output.

One binary to operate

No external database, no broker, no cache to feed. Upgrades replace one binary; recovery is a drill that runs in CI, not a wiki page nobody has tested.

Exits are standard

It speaks S3, so leaving is an aws s3 sync away. A storage system you can leave easily is the only kind worth moving into.

Keep MinIO running. Audit the move first.

The migration planner reads your existing MinIO or S3-compatible store and refuses blocked features instead of silently dropping them. Run Lockwell beside production, copy with verification and checkpoints, and switch only when its own ledger has convinced you.

$ lockwell migration s3 plan --json
How migration works →
REAL SETUPS

What teams actually run on it

Three concrete shapes, not personas. Each one is a tenant, a few scoped keys, and the parts of the server that already exist.

Client deliverables with expiring links

An agency keeps each client in its own tenant. Deliverables are shared as signed links that die after seven days: no client accounts, no public bucket, and the audit log shows every download.

Signed URLs →

User files in a multi-tenant SaaS

Each customer is a tenant. Browsers upload straight to storage with signed PUT URLs, webhooks fire the thumbnail pipeline, and a leaked key can only ever see one tenant's world.

The app kit →

The archive an auditor will visit

Invoices and records land under Object Lock retention with always-on encryption. Lifecycle expires what may expire, legal hold freezes what may not, and the restore drill proves the backups are real.

Object Lock →
EVIDENCE

Watch it work, then check the math

The terminal replays the real lifecycle against the real CLI surface. The bars come from the committed benchmark evidence, and the harness ships in the repository, so every number can be re-run on your own hardware.

lifecycle.sessionreplayed, not faked
$ lockwell tenant-create acme --yes
tenant "acme" created, isolated by default
$ lockwell key-create app --tenant acme --read --write --delete
access key AKIA...W4 minted, secret shown once
$ aws s3 cp report.pdf s3://acme-inbox/ --endpoint-url https://objects.example.com
upload: ./report.pdf to s3://acme-inbox/report.pdf
$ lockwell cluster disaster-drill --tenant acme --json
{ "unrecoverable_data_loss": 0, ... }
drill: PASS
multipart PUT, 64 MiB parts at 64 clients2.2x
lockwell1287 MiB/s
minio579 MiB/s
GET, 64 MiB at 16 clients2.9x
lockwell13736 MiB/s
minio4659 MiB/s
disk used for the same 44 GiB write set0.54x less
lockwell44.0 GiB
minio81.2 GiB

measured 2026-06-11 · one host, Docker Compose, zero errors in all rows · methodology · reproduce: make bench

CROSS-CHECKED WITH MINIO'S OWN TOOL · WARP 1.3.1
PUT, 1 MiB objects at 16 clients10.9x
lockwell2561 MiB/s
minio236 MiB/s
GET, 1 MiB objects at 16 clients1.3x
lockwell6857 MiB/s
minio5454 MiB/s
GET inside warp's mixed workload1.8x
lockwell2628 MiB/s
minio1500 MiB/s

measured 2026-06-12 with warp 1.3.1, identical parameters against both servers, zero errors · reproduce: make bench-warp

full results →
CAPABILITIES

Everything an app's storage layer needs

Provisioning, signed uploads, encryption, edge support, and webhooks, all built into one server and one SDK. You do not stitch together a bucket, a queue, a signer, and a secrets store.

01 · tenancy

Multi-tenant provisioning

Ensure a tenant exists and mint a fresh, scoped access key in one call: read/write/delete, an optional bucket scope, the secret shown once. Every tenant is isolated by default.

02 · surfaces

Native + S3 + admin APIs

A native JSON data plane, S3 SigV4 compatibility, and a JSON admin API. Three surfaces over one server, reached from one SDK. Talk to storage the way your stack prefers.

03 · uploads

Browser signed URLs

Sign a direct upload or download URL server-side and hand it straight to the browser. The native API signs write URLs, not only GET, so object bytes never round-trip your app.

04 · edge

Edge-ready

The native and kit clients import nothing from node:*, so they run on Cloudflare Workers, Vercel Edge, Bun, and Deno through a dedicated /edge entry with zero node:crypto.

05 · encryption

Always-on encryption

Authenticated at-rest encryption is on by default: AES-256-GCM or XChaCha20-Poly1305 over object data, with per-tenant data keys. There is no flag to remember to set.

06 · webhooks

Webhooks you can verify

Bucket event notifications POST to your endpoint with a constant-time HMAC-SHA256 signature. verifyWebhook checks the signature in one call, and runs on the edge.

THREE SURFACES, ONE SERVER

Three APIs over the same encrypted store

Lockwell exposes the same multi-tenant, encrypted object store through three interfaces. Pick the one that fits, or mix them freely.

S3 compatibleSigV4 · XML

Drop in where S3 already is

SigV4 auth, path-style and virtual-host addressing, versioning, multipart, conditional writes, presigned URLs, Object Lock, tagging and lifecycle. The parity ledger documents every operation, and anything outside it fails closed instead of pretending.

Native JSON/api/v1/

Talk to storage natively

A JSON data plane on the public listener, with no SigV4 signing and no XML. Bearer tokens are minted from access keys and refreshed for you. Native signed URLs cover GET and PUT.

Admin JSON/admin/api/v1/

Provision and manage

A bearer-authenticated JSON admin API on a private listener: tenants, scoped keys, RBAC, audit, and an OpenAPI document. Provisioning lives outside the data plane, by design.

ONE SDK

No custom storage layer to maintain

The usual object-storage stack is a bucket, a second store for metadata, a signer service, a webhook verifier, and the glue holding them together. Lockwell collapses that into one server and one SDK: provision a tenant, get a scoped client, sign a browser upload, and verify the webhook, end to end, in the same library, on the same encrypted store.

npm i @kelphect/sdk
provision.ts@kelphect/sdk
import { LockwellKit } from '@kelphect/sdk';

const kit = new LockwellKit({
  admin:  { endpoint: 'https://admin.example.com', token } },
  native: { endpoint: 'https://objects.example.com' },
});

// 1. provision a tenant + mint a fresh scoped key
const { key } = await kit.provisionTenant('acme', {
  defaultBucket: 'inbox',
});

// 2. sign a browser upload, then hand the URL to the client
const client = kit.clientForTenant('acme', key);
const up = await kit.signedUploadUrl(client, 'inbox', 'photo.jpg', {
  ttl: 300, contentType: 'image/jpeg',
});
DEFERRED, NOT DENIED

What is not here yet, and what to do meanwhile

A feature ships when it passes the same release gates everything else passed: drills, suites, documented behavior. Until then the server says no, and this page says so out loud.

in design, behind the gates

Multi-node replication

Lockwell is deliberately single-node today; the replicated profile stays suspended until it can pass the same takeover and durability drills. Meanwhile teams run a warm standby: a second instance fed by scheduled S3-level sync, with restore drills proving the seam.

webhooks today

Event buses (SNS, SQS, Lambda)

Bucket notifications deliver to signed webhooks you can verify in one call. A queue between Lockwell and your pipeline is one consumer away, on infrastructure you already trust.

lifecycle today

Storage tiering

Lifecycle rules already expire and clean up; cold archival is a scheduled job copying via the S3 API to whatever cold store your bills prefer.