MindooDB Blog

MindooDB on crazy hardware, part two - a whole server on 14 grams of Orange Pi

Karsten Lehmann 10 September 2026 09:00:00

Three weeks ago I did something silly with a phone. The question behind it was serious - what is the smallest machine that can run a MindooDB server? - and the answer turned out to be “smaller than you would like”, because a Debian VM running emulated x86_64 inside UTM SE on an iPhone 17 managed it. It took 75 minutes to build the Docker image and 102 seconds to generate a single server identity, but it ran. That story is here, and it ended with a promise: the hardware actually meant for this was in the post, and I would write up how it did.

It was in the post for rather longer than anyone expected. But it is here now, and it is very small.

The Orange Pi Zero 3W held between two fingers on a desk, wearing a black finned heatsink and a small round fan over the SoC, with USB-C adapters for power and a hub plugged into both of its ports and a mini-HDMI cable running to a monitor - the whole board is shorter than the space bar of the Apple keyboard lying next to it

Ten days, two continents, and a week in Cologne

The board is an Orange Pi Zero 3W, ordered from Amazon for $85 plus tax and shipping. It left the shipper in Dallas on Friday 28 August and was handed over at my door in Karlsruhe on Monday 7 September - ten days to move 65 by 32 millimetres of circuit board across the Atlantic.

The tracking history is the best part of the whole purchase, so it gets a screenshot of its own.

The UPS tracking history for the shipment, listed newest first: delivered in Karlsruhe-Groetzingen on Monday 7 September at 1:20 PM, preceded by five consecutive days in Koeln DE that each repeat the same three entries - "Package in transit. Flight containing package has arrived", "Package arrived at a carrier facility", "Package in transit. Flight containing package has arrived" - two customs clearance delays, and Louisville KY and Dallas TX entries at the bottom

Read from the bottom: Dallas on the Friday evening, Louisville in the small hours of Saturday, customs cleared in Cologne at 8:17 on Saturday morning - and then, at 12:43 that same day, the package leaves a carrier facility in Louisville, Kentucky again. From Monday 31 August to Friday 4 September it repeats the identical three-line refrain every evening in Cologne: package in transit, flight containing package has arrived; package arrived at a carrier facility; package in transit, flight containing package has arrived. Five nights running, in the same city, apparently getting on and off aeroplanes.

My working theory is that the board is so small it kept slipping down the back of the sorting machine. :-)

So what is an Orange Pi Zero 3W?

It is a single-board computer measuring 65 x 32 mm and weighing about 14 grams - roughly a Raspberry Pi Zero, which is to say roughly a large stick of chewing gum. What is inside it is the surprising part.

ComponentSpecification
SoCAllwinner A733
CPU8-core 64-bit ARM: 2x Cortex-A76 up to 2.0 GHz + 6x Cortex-A55 up to ~1.8 GHz
Additional coreXuantie E902 RISC-V real-time core, up to 200 MHz
RAM1 to 16 GB LPDDR5-4800, depending on model
GPUImagination BXM-4-64, OpenGL ES 3.2 / Vulkan 1.3 / OpenCL 3.0
NPUIntegrated AI accelerator, up to 3 TOPS
StoragemicroSD; optional eMMC and UFS 3.0 configurations
WirelessDual-band Wi-Fi 6, Bluetooth 5.4
EthernetNone onboard
USBUSB-C USB 3.1 OTG plus USB-C power
ExpansionPCIe Gen 3 x1 via FPC connector, 40-pin GPIO with UART, I²C, SPI, PWM
DisplayMini HDMI 2.0 up to 4K60, DisplayPort 1.4 Alt Mode over USB-C, MIPI DSI
Camera2x MIPI CSI
Power5 V / 3 A over USB-C
Size / weight65 x 32 mm, about 14 g
OSOrange Pi OS, Ubuntu, Debian, Android

For a MindooDB server most of that list is irrelevant - the GPU, the NPU, the camera inputs and the 4K output are for other kinds of projects. What matters is the combination in the middle: eight ARM64 cores, several gigabytes of RAM, Wi-Fi 6, a normal Linux distribution, and a five-volt power supply.

The two Cortex-A76 cores are the reason this is not just another cheap board with more memory bolted on. The A733 is a heterogeneous design: two performance cores for the work that is actually hard, six A55 efficiency cores for everything else. Mine reports about 2.0 GHz on the A76 pair and 1.79 GHz on the A55s.

Mine is the 6 GB version, which turns out to be a comfortable size for a small MindooDB installation: room for 64-bit Linux, Docker, the server and the filesystem caches, with enough left over that nothing is ever operating at its limit. 4 GB would do this job too; 12 or 16 GB is more than an edge server of this kind will ever ask for.

Two caveats worth stating plainly. The first is storage: a microSD card is fine for an experiment, but for a server that runs permanently I would take an eMMC or UFS variant every time, for endurance as much as for throughput. The second is that there is no Ethernet port. Networking is Wi-Fi 6, or a USB adapter, or something on the PCIe connector. For a stationary server in a rack that would be a real objection. For the sort of compact, movable, occasionally-isolated node this article is about, it is not.

Ubuntu, in about ten minutes

There is not much of a story here, which is itself the story.

I downloaded the Ubuntu 26 image from the Orange Pi site, wrote it to a microSD card with balenaEtcher on macOS, plugged in a USB-C hub with keyboard and mouse, a second USB-C cable for power, and a mini-HDMI cable to a monitor. A few seconds later there was an Ubuntu desktop. I set up Wi-Fi and installed the Avahi daemon so the board would announce itself as orangepi.local instead of an address I would have to remember.

In principle it does not even need the Wi-Fi. The USB-C port is an OTG port, so the board can act as a USB device rather than a host, and Linux’s USB gadget subsystem can present it to whatever it is plugged into as a network adapter - USB Ethernet gadget mode, g_ether and its configfs successors, appearing as CDC ECM or NCM on macOS and Linux and as RNDIS on Windows. One cable to a laptop then carries power and a private point-to-point network, with no router and no access point in the picture at all. That is the configuration I would use for a server you hand to somebody in an envelope. Mine, for now, sits upstairs on the desk with Wi-Fi.

Docker was already there

Which meant I could go straight to the interesting part: clone the MindooDB repository and run the standard setup.

git clone https://github.com/klehmann/MindooDB.git
cd MindooDB
./serversetup.sh

No ARM64 branch, no special flags, no patched Dockerfile. The same serversetup.sh that runs on a laptop, asking the same questions: data directory, server name, bind address, port, unlock password. I called the server cn=orangepi1/o=mindoo and left it on 0.0.0.0:1661.

The interactive serversetup.sh run on the Orange Pi: git clone of the MindooDB repository at 3.78 MiB/s, then the configuration summary for server name cn=orangepi1/o=mindoo, data directory ../mindoodb-data and container port 1661, the data directory being created, and the Docker build starting with the header "[+] Building 137.6s (19/19) FINISHED"

That header is the number I had been waiting for. 137.6 seconds for the nineteen-step build - the same build that took 4539.4 seconds on the phone.

The step-by-step log makes the comparison even less kind to the phone.

The Docker build log on the Orange Pi: pnpm install --frozen-lockfile at 48.7s, the TypeScript compile step at 22.7s, the production dependency install at 12.9s, the layers exported, and the closing line "Image mindoodb-server built successfully", followed by the start of the server identity initialisation

StepiPhone, emulated x86_64Orange Pi Zero 3W, native ARM64
Docker image build, 19 steps4539.4 s (1 h 15 min)137.6 s
pnpm install --frozen-lockfile1328.7 s48.7 s
TypeScript compile2183.5 s22.7 s
Ed25519 signing keypair277 ms6 ms
RSA-3072 encryption keypair46,477 ms377 ms
Server identity, total101,863 ms649 ms

The TypeScript compile is the one to look at: 36 minutes against 23 seconds, a factor of about ninety-six. None of which says anything bad about the phone - it says what it costs to interpret x86_64 instructions one at a time on a CPU that speaks something else. The Orange Pi is simply running ARM64 code on an ARM64 processor, the way everyone intended.

An identity in 649 milliseconds

Generating a server identity is the most cryptographically expensive thing the setup does, and on the phone it was the part that hurt: 102 seconds, 46 of them spent on a single RSA-3072 keypair.

The MindooDB server init log on the Orange Pi: the Ed25519 signing keypair generated in 6 ms, the RSA-3072 encryption keypair in 377 ms, two PBKDF2 passes at 310,000 iterations taking 128 ms and 125 ms, "User ID created successfully in 649 ms total", and the printed Ed25519 signing and RSA-OAEP encryption public keys for cn=orangepi1/o=mindoo

649 milliseconds, all in. The Ed25519 signing keypair in 6 ms, the RSA-3072 encryption keypair in 377 ms, and the two PBKDF2 passes - 310,000 iterations each, deliberately expensive so that a stolen identity file is not worth attacking - at 128 and 125 ms. That is laptop territory, from a board you could post in an envelope.

The setup then offers to create a system administrator, which is a second identity and therefore a second round of the same work. RSA key generation is a hunt for large primes and how long it takes is partly luck, so this one came in at 1033 ms rather than 649.

A system administrator is the superuser of the server, not of anybody’s data - it still holds no key that decrypts anything. Its job is the handful of /system/* routes: creating and removing tenants, changing the server configuration, wiring up sync to other servers. Which of those routes a caller may use is decided by a capability list in config.json, matched per HTTP method and path against the caller’s username and signing key, so “may create tenants whose name starts with demo_” is as easy to express as “may do anything”.

The default is that only the system administrator may create a tenant. On this board I did what our public demo server does instead, and used the open variant: one rule granting the wildcard principal { "username": "*", "publicsignkey": "*" } on POST:/system/tenants/*, so anyone may publish a tenant to it. That wildcard is deliberately narrow - it unlocks tenant creation and nothing else, not listing tenants and not editing the config - but it exists for demo and test machines, and it is not what you want on a production server. Both variants are written out in the server README.

The system admin creation log: a second identity for cn=orangesysadmin/o=mindoo generated in 1033 ms, the identity file written password-encrypted to /data, the note that the raw private key is never stored on disk, the docker-compose.override.yml being written, and the "Setup complete" summary listing server name, data directory, password file, bind address and container port

Worth saying out loud, because it is the least dramatic sentence in this article: nobody had ever run the MindooDB server on ARM64 before. Not as a supported configuration, not as an experiment, not once. There was no porting work, no failed first attempt, nothing to debug.

The reason is in the very first line the server prints, before it has done anything at all: Using WASM Automerge (Browser/Node.js mode). The part of the stack most likely to need an architecture-specific build - and therefore most likely to be the thing that breaks on an unfamiliar CPU - is the CRDT engine that merges concurrent edits, which is written in Rust. On Node.js it is loaded as WebAssembly rather than as a compiled native module, and WebAssembly is portable bytecode: the same artefact that runs on a laptop runs here. Everything above it is JavaScript in a Node.js container. There is nothing in the whole stack that knows or cares what an A733 is.

Listening on port 1661

docker compose up -d, then the logs.

The Orange Pi terminal after docker compose up -d: the mindoodb-mindoodb-1 container started, and the server log opening with "Using WASM Automerge (Browser/Node.js mode)" and then showing the data directory /data, port 1661, TLS disabled, the config loaded with 1 capability rule and 1 principal, "Loaded server identity: cn=orangepi1/o=mindoo", 0 trusted servers, the global and per-tenant rate limits, and the final line "Listening on port 1661"

Server identity loaded, one system-admin principal, rate limits in place, zero trusted servers because this one has no peers yet, listening on 1661. A complete MindooDB server, on a board that has been powered by a phone charger this whole time.

Handing it a tenant

The rest happens in the browser. I opened Haven at haven.mindoodb.com - the live, hosted one, not a local build - went to Preferences → Tenants and pressed Push to server.

The Haven "Push to server" dialog: base URL http://orangepi.local:1661, the server name cn=orangepi1/o=mindoo and signing key fingerprint 4d:06:d1:68:46:33:fe:77 filled in automatically, an admin user id of Administrator/Testorg1 and the administrator passphrase field with an "Unlock for this session" button

The only thing I typed was http://orangepi.local:1661 - the mDNS name Avahi is publishing on my LAN. Haven fetched the server’s public identity from it, showed me the name it claims to be and the fingerprint of its signing key to check, and after the administrator passphrase it registered the tenant. The administrator it asks for there is the tenant’s own - Administrator/Testorg1 - and not the server’s, which is precisely what the open tenant-creation rule above buys you.

The fingerprint is worth pausing on, because publishing the tenant writes this server into its trusted-witness list. From then on the server stamps each arriving entry with the time it accepted it and signs that receipt, and every other device checks the signature against that key before believing the timestamp. It is what stops somebody with a backdated laptop clock from slipping a change past a rule that had already been withdrawn.

The witness key is not the root of trust, though - and the root of trust never goes near the server. That is the tenant administrator’s signing key. Everything in the directory database is signed with it: the access control policies and rules, the groups, the grants that make a particular device part of a particular person’s account, and the Haven policies that install apps and set up the Sync page on a new joiner’s machine. Every client verifies those signatures itself before acting on any of it, so a rule or a policy that does not carry the administrator’s signature is ignored, no matter which server handed it over. The trusted-witness list is itself one of those admin-signed entries - which is why registering a server is an act of tenant administration, and not something a server can arrange for itself.

Then the Sync page, and several thousand encrypted entries going up the stairs over Wi-Fi.

The Haven Sync page: a tenant syncing bidirectionally with orangepi1/mindoo, one row per database - directory completed with 9 entries scanned, teacher_core completed with 783 transferred, teacher_grades mid-flight on batch 3 of 4 with 2000 of 3000 transferred, teacher_materials reporting 190 attachments pushed, and teacher_notes completed with 92

The tenant I picked was a Mindoo Teacher’s Desk demo data set, which is convenient because it is not small: the tenant directory, then a class and course database at 783 entries, a grades database of 3000 going up in batches, 190 attachments, and the lesson notes behind them. Everything in those progress bars is ciphertext. The Orange Pi is storing it, indexing the envelopes, chaining the history and stamping each entry with a signed witness receipt - and it cannot read a single word of any of it.

One client, several servers

The row above is one of several. A Haven client is not tied to a single server, and the Sync page lists a row per database per server, each with its own direction and its own schedule.

That is more useful than it sounds. The same tenant can sync with a server standing in the room and with a backup in a hosted environment - the near one for speed and for working when the uplink is not there, the far one so that a fire in the building is an inconvenience rather than an ending. Neither of them can read anything, so “which one holds my data” stops being a question about trust and becomes a question about latency and physics.

Which is where the small, self-contained server earns its keep. Put one on a ship, an oil platform, a research station - or, if you insist, the ISS - and the people there work against it at LAN speed, indifferent to whether the satellite link is up. When a connection does come back, that server reconciles with the one on the mainland. Being cut off is not a degraded mode; the island is just a node that has been offline for a while, which is exactly what MindooDB already assumes about your laptop on a train.

And servers do not only talk to clients. Server-to-server sync is part of the protocol: two servers exchange public keys, each records the other in its trusted-servers list, and from then on they mirror tenants on a schedule you configure - relaying encrypted entries without ever decrypting them, exactly as they do for clients. Data centres in two countries for disaster recovery, an edge node in front of an origin, a mesh with no single point of failure: all the same protocol, all a deployment decision rather than a code change.

Where your server lives

$85 and 14 grams buys eight ARM64 cores that build the entire MindooDB server in a little over two minutes and mint a cryptographic identity in two thirds of a second. It draws five volts, needs no rack and no contract, and its entire cooling system is a finned heatsink and a fan the size of a coin. As a demonstration of how little a MindooDB server actually needs, that is the end of the argument.

It is not an argument that you should go and operate one. Running a server is real work - patching, disks, monitoring, backups, the call at three in the morning - and for most teams it is not the work they are in business to do. MindooDB Cloud is the same server, run by us: a managed home that syncs every Haven app, with 200 GB per user and more on request. The details are on the pricing page. Self-hosting stays free and open source either way.

What makes that a genuine choice rather than a compromise is the encryption. “Where is our data?” has become a board-level question, and the usual answer is to own the hardware, because whoever runs the machine can normally read what is on it. Here they cannot: the server holds ciphertext and public keys, and the keys never leave your devices. Digital sovereignty - your data under your control, in a jurisdiction you chose - holds whether the box is in your cupboard, in our data centre, or both at once.

So where the machine lives comes back down to practical things: latency, whether the uplink is reliable, whether a regulator wants to see the rack, whether the site is a ship. And when the answer is “in the building”, this is what that now costs - a computer that fits in a drawer of the cupboard.

The next unlikely machine is already on the list. If you have a favourite candidate - the more awkward, the better - I would genuinely like to hear about it.

MindooDB is open source under the Apache 2.0 licence, and Haven Community is free for private and commercial projects at haven.mindoodb.com. Everything else is at mindoodb.com.