Leave the clouds
without losing a byte.

Bring your Takeout, your 20 years of email and your unsorted photos. With undo.

What this actually is

NoCloud is the local-first layer between your archive and the services you already use. It brings folders and supported exports into your files, your SQLite database and your private portal, then connects dashboards, automations and optional Store apps without turning your life into another hosted account.

Why not just run Immich + Firefly + Paperless?

Run them — NoCloud does, where they're best-of-breed (Immich for photos, Home Assistant for the home). The gap sits underneath: those apps host the present, starting from zero, while your past — Takeouts, mailboxes, chat archives, bank statements — stays scattered across single-purpose importers that don't talk to each other. NoCloud is the layer that digests twenty years of exports into one normalized, searchable database, reversibly: every curation decision is journaled with its inverse, uncertain ones wait for your review, and supported operations can be undone. That migration-and-memory layer — with undo — is the product.

Reversibility is the point

Leaving a cloud is a one-way door for most people: you export, you import, and whatever got mangled on the way is gone. Here the pipeline that got your data in can always be run backward.

Snapshot first

Every database write goes through an append-only ledger that snapshots before it touches anything. Any change can be restored.

Nothing vanishes

Nothing is permanently deleted without confirmation. Uninstalling an app quarantines its tables — it doesn't drop them.

Glass box

Curation runs on an explicit vocabulary of reversible operations, not an opaque classifier. You can see why a file ended up where it did.

Everything in the engine reduces to a small, closed vocabulary: six algebra primitives for what an op does (COPY MOVE ANNOTATE RECORD LINK DELETE, each with an exact inverse), and five Python constructs for what decides it — wired end to end. One tab each:

# a rule proposes a fact from a pattern, and always says why @rule("exif_date") def exif_date(asset, ctx): date = read_exif(asset.path).get("DateTimeOriginal") if date: yield Evidence("date", date[:10], source="EXIF", confidence="high") # the proposal is resolved and checked before it can be lowered facts = curation.evaluate(asset, ctx) # then compile_facts() writes only accepted facts through a journaled run
# a constraint vetoes any candidate that can't be right @constraint("lifespan") def lifespan(asset, field, value, ctx): if field != "date": return True for person in ctx.depicts(asset): _, died = ctx.person_bounds(person) if died and value > str(died): return False, f"{person} died in {died}" return True # runs on every candidate before any fact is written
# a classifier proposes group membership — here, an album @classifier("coastal_trip") def coastal_trip(asset, ctx): date = inferred_date(asset, ctx) place = inferred_place(asset, ctx) if place == "coast" and date_in("2023-08", date): yield Evidence("album", "Summer 2023 · Coast", source="place + date cluster", confidence="medium")
# when two sources disagree on a date, a policy resolves it @resolution("date", name="min-date") def min_date(evidences): # the earliest plausible date wins — upload timestamps are almost # always later than the actual capture date return sorted(evidences, key=lambda e: (e.value, -CONF[e.confidence])) # the chosen fact records which candidates it beat and why
# every write goes through the algebra — any op can be reversed $ python3 scripts/journal.py runs a1e3f9 ANNOTATE beach.jpg.xmp date=1994-08-14 [exif_date · lifespan ✓] b4c12a LINK beach.jpg → belongs_to "Summer 2023 · Coast" [coastal_trip] c7f820 MOVE Sink/IMG_0453.jpg → Photos/1994/beach.jpg [category] $ python3 scripts/journal.py revert a1e3f9 ✓ reverted: ANNOTATE beach.jpg.xmp (date annotation removed)

Local-first, literally

Not a slogan — the mechanism. Your data lives on hardware you own (a workstation and an always-on home server — a Raspberry Pi is a common choice, but any Linux machine or WSL instance works) and is reachable only over your own VPN. There is no account, no tenant, and nothing of yours leaves the house.

Native apps

Two native companions let you skip the browser entirely.

NoCloud Desktop

Your self-hosted dashboard as a standalone window — no browser, no extra tabs, no hunting through history. It discovers local instances, guides first-time setup, and keeps the Sink import queue visible. Download →

NoCloud Agent

A persistent icon in your menu bar, not the Dock. One glance to see whether your server is reachable and your services are healthy. One click to connect, disconnect, deploy new code, or open your dashboard — no terminal needed. Starts automatically at login. Docs →

Status

NoCloud is actively developed as a complete local-first platform: core runtime, Store contracts, desktop wrapper, menu-bar companion and product manual. The current desktop release lists the installers available for each platform; builds remain unsigned while release-signing credentials are being added.

In the store:

Google Takeout

Import your photo archive from Google. Dates, descriptions, and metadata — everything intact, nothing lost.

WhatsApp archive

Years of conversations and attachments in one local, searchable archive — organized by chat room.

Webmail

Read and write email from your own mailbox — no third-party app, no subscription, no scanning.

Mailbox rules

Route incoming email by sender or subject. Applied locally, before you open anything.

Review Queue

Inspect every curation decision the engine wasn't confident enough to apply on its own. You approve, it writes.

Rule Coach

Describe a curation rule in plain language. Review the typed expression before it touches anything.

Synced documents

Browse folders synced between your Mac and home server — one view, both sides.

DB Explorer

A live map of every table in your unified database — row counts, ownership, nothing hidden.