Your entire backend. Realtime by default.
100% open source and self-hosted. You get a database, live queries, auth, file storage, cron jobs and a complete dashboard, all packed into a single binary.
There is no Concile cloud. We built this exclusively for you to self-host.
From an empty folder to a live app in five steps
You write one language for the entire backend. Define your tables, create mutations and queries, and subscribe right from the client. When you're ready to deploy, it ships as a single container or binary. You don't have to design an API, and there are no migrations to run.
See the full quickstart- start with the shape
Describe your data
One file names your tables and their fields. A write that does not match is rejected before it lands.
concile/schema.tsexport default defineSchema({ messages: defineTable({ body: v.string(), done: v.boolean(), }).index("by_done", ["done"]), }); - a write is one transaction
Write a mutation
No resolvers and no glue. Args are checked first, then the handler runs against a snapshot.
concile/messages.tsexport const add = mutation({ args: { body: v.string() }, handler: (ctx, args) => ctx.db.insert("messages", { body: args.body, done: false, }), }); - a read is pure
Read it with a query
A query only reads. Concile records what it touched, so it knows when the answer has changed.
concile/messages.tsexport const list = query({ args: {}, handler: (ctx) => ctx.db.query("messages", "by_creation").collect(), }); - the client just reads
Subscribe from the client
The hook holds the subscription open. Every commit re-renders every client that read it.
app/Chat.tsxconst messages = useQuery(api.messages.list); // the component re-renders on every commit - one binary or a container
Ship it
The same code runs in dev, in a container, and as a static binary. Nothing is rewritten on the way out.
terminal$ npx concile dev # live sync + dashboard $ docker compose up # one container, one volume $ concile build # a single binary
Watch your data change as it happens
We include a built-in dashboard that runs on the exact same origin as your app. It uses live subscriptions, meaning you see writes land in the database immediately. We didn't even build a refresh button, because you'll never need it.
Change feeds tell you a row changed. Concile tells you the answer changed.
Most backends just stream raw row events, forcing your client to figure out if that new row actually belongs in your filtered, sorted, paginated view. That means writing a query engine twice. Concile is different: it subscribes to the query itself, and only pushes a new result when a write actually affects your data.
Read about reactivityOne process. Your entire backend.
We've packed reactivity, auth, scheduling, file storage, and a live dashboard into a single running program. There's no complex stack to piece together or network configurations to manage. Take a look below. Every card is a live, working piece of the real system.
Thousands of live subscribers
Cron and durable jobs
Sign-in, built in
Files on disk or S3
A live data browser
| Run | Job | Status |
|---|---|---|
| #1041 | deploy | queued |
| #1040 | build | done |
| #1039 | seed | done |
Six core components. Nothing you didn't ask for.
Features like auth, authorization, scheduling, workflows, triggers, and notifications come as separate packages. They all plug into the core engine exactly the same way. Just list what you need in your config file, and the rest won't even load.
import { defineConfig } from "@concile/component";
import { defineAuth } from "@concile/auth";
import { defineScheduler } from "@concile/scheduler";
export default defineConfig({
components: [
defineAuth({ /* … */ }),
defineScheduler(),
],
});Nothing is installed for you, and nothing runs that you did not list. The core engine does not know what auth or workflows are, and it does not need to.
- @concile/authAuthSessions, email, OAuth, MFA, and passkeys.
- @concile/authzAuthorizationRBAC, ReBAC, and row policies.
- @concile/schedulerSchedulingrunAfter, runAt, and cron jobs.
- @concile/workflowWorkflowsDurable multi-step orchestration.
- @concile/triggersTriggersReact to table changes.
- @concile/notificationsNotificationsEmail, SMS, in-app, and push.
Five things you can't just bolt on later
This isn't just another feature list. These are fundamental properties of how Concile is built. They're exactly why your app behaves differently when a user's network drops, when your database schemas change, or when you suddenly outgrow a single node.
It keeps working with no network
Writes you make on a plane go into a durable queue on the device. Close the tab, crash the browser, reload a day later. They are still there, and they drain in order the moment you reconnect. The server keeps a receipt for every one, written in the same transaction as the write itself, so a resend can never apply twice.
How the outbox worksimport {
ConcileClient,
indexedDBOutbox,
} from "@concile/client";
const client = new ConcileClient(
transport,
{ outbox: indexedDBOutbox() },
);- Survives a reloadand a crash
- Exactly oncereceipts, not guesses
- One queueshared across every tab
SQLite and Postgres today. Anything tomorrow.
The engine never imports a database driver. It talks to one TypeScript interface called DocStore, and whatever sits behind that doorway is invisible to everything above it. Four backends ship today. Adding a fifth means implementing one interface, and nothing in the transactor, the query engine or the reactivity layer changes.
- SQLiteembedded, zero setup
- Postgresone flag, no code change
- Durable Object SQLiteCloudflare
- Cloudflare D1relational
- Yoursone interface to implement
Add a field. Deploy. That is the whole migration.
Your tables, fields and indexes live as data inside a small fixed set of internal tables that never change shape as your schema evolves. There is no CREATE TABLE and no ALTER TABLE, on SQLite or on Postgres. Additive changes go live with nothing to run first and no migration file to write.
messages: defineTable({
body: v.string(),
author: v.string(), // new
}),Deploy it. There is no second step, on either database.
Six targets, one command
concile deploy --target decides where it lands. Your own server, Docker, Cloudflare, Railway, Fly, or AWS App Runner. No provider SDK is ever bundled into Concile. Each target shells out to the CLI you already have installed, and tells you exactly what is missing if you do not.
- servelive hot-swap
- dockercompose up
- cloudflareDurable Objects
- railwayrailway up
- flyfly deploy
- awsApp Runner
Every node runs the same command
There is no primary flag, no replica flag, and no coordinator process to run. Start the same binary as many times as you like and the fleet sorts out its own roles. The first node to take the lease becomes the writer, every other node serves reads from its own local copy, and a client can connect to any of them without knowing the difference. Kill the writer and another one takes over in about two seconds.
Scaling and the fleetconcile serve --fleet \
--database-url $PG \
--advertise-url $SELF- node-1writer, self-elected
- node-2reads, local replica
- node-3reads, local replica
We borrowed the best ideas
We loved Convex's reactive queries, Supabase's self-hosting, Firebase's resilient offline mode, and PocketBase's zero-setup single binary. So we brought those ideas together. Here's a transparent look at how we compare, including the areas where we fall short.
| Capability | Concilethis one | Convex | Supabase | Firebase | PocketBase |
|---|---|---|---|---|---|
| Live results from your own server code | YesRead-set precise | YesCached, subscribable queries | PartlyRow change events | PartlyDocument listeners | PartlyRecord events |
| Backend is plain TypeScript functions | YesQuery, mutation, action | YesQuery, mutation, action | PartlyEdge functions, off to the side | PartlyCloud Functions | PartlyGo or JS hooks |
| Authorization is ordinary code, not a rules language | YesFunctions you can unit test | YesOrdinary TypeScript functions | NoRow-level security in SQL | NoSecurity rules DSL | NoAPI rule expressions |
| Self-host the whole product | YesOne process, one command | PartlyOpen-source backend, cloud is the product | PartlySeveral composed services | NoGoogle cloud only | YesOne binary |
| Ships as a single binary with no database to run | Yesconcile build | No | NoPostgres required | No | YesSQLite embedded |
| Swap the database without touching app code | YesSQLite or Postgres, one flag | NoFixed store | NoPostgres only | NoFirestore or Realtime Database | NoSQLite only |
| Durable workflows with rollback | YesSaga compensation | PartlyWorkflow component | NoBring your own | NoBring your own | NoBring your own |
| Offline writes that survive a reload | YesDurable outbox, exactly once | PartlyOptimistic updates only | No | YesFirestore persistence | No |
| Full-text and vector search | NoNot built yet | YesBoth, built in | Yestsvector and pgvector | PartlyVector only, text needs an add-on | PartlyFilter matching, no index |
Checked against each project’s public documentation on 21 September 2026. These tools all move fast. If a cell is wrong or out of date, tell us and we will fix it.
Where our model ends
Reactive functions aren't the right tool for everything, and honestly, some features just aren't built yet. We put them all right here so you find out now, rather than halfway through your next sprint.
When the model does not fit
- ActionsRun outside the transaction for fetch, timers, and randomness.
- HTTP endpointsPublic routes for webhooks. A Request goes in, a Response comes out.
- Crons and schedulesrunAfter, runAt, and cron expressions, durable across restarts.
What it doesn't do yet
- No searchQuery by index and range. Full-text and vector are reserved seams.
- One writer by defaultMulti-node write scale-out ships under a separate commercial license. It is the newest part of the system. Start on one node.
- No built-in TLSPlain HTTP. Front it with nginx, Caddy, or Traefik.
- In-process functionsA V8-isolate sandbox for untrusted code is a reserved seam.
Questions people actually ask
The short answers. Every one of them links to the longer version in the docs, where the caveats live.
For a single node, yes. The engine, client, dashboard and CLI are real, and the whole component set is exercised end to end through the shipped CLI, not just unit tests.
Multi-node write scale-out is the newest part, and it lives under a separate license in ee/. Start on one node with SQLite or Postgres. Reach for the fleet once you have measured a real ceiling.
The part nobody likes saying out loud: Firebase has a decade of production history at Google’s scale, and we have none. Weigh that honestly.
Read the full answerBecause they solve different problems, and the gap shows the moment a query is more than “give me every row”. A change feed streams raw row events. Your client has to decide, for every event, whether that row still belongs in the filtered, sorted, paginated list on screen. That is a query engine, running on the client, that you now maintain.
A live query is subscribed to the result instead. The server works out whether a write could have changed that result, and pushes the new one when it did.
The honest caveat: a change feed is plenty for an app that only needs to know when a table changed. It stops being enough the moment the view is anything more than flat.
How reactivity worksThe reactive model is deliberately modelled on Convex’s published architecture. A query records its read set, a mutation commits a write set, and a subscription re-runs when the two intersect. Same idea, built clean-room from public docs. Same license family too, so this is not an open-versus-closed story.
The difference is posture. Convex’s own docs point you at their cloud for a more hands-off setup. There is no Concile cloud to point you at. Self-hosting is the product, storage is pluggable, and the whole thing compiles to a single binary.
Migrating from ConvexYes, and not as a trial. Concile is licensed FSL-1.1-Apache-2.0, the same license Convex uses. You can use it, change it and self-host it commercially. Each release turns into plain Apache 2.0 two years after it ships. There is no phone-home and no metered usage.
The license forbids one thing: reselling Concile itself as a competing hosted service.
One detail we will not bury. Single-node self-hosting is free forever. Multi-node write scale-out lives in ee/ under a separate commercial license, and it is free to use today with no license key.
No. SQLite is the default and there is nothing to install. It is also the faster of the two on one node, because there is no network hop and no fsync on every commit.
Point it at Postgres with one flag when you want the backups and replicas you already run. Your application code does not change, and neither store ever needs a migration file.
SQLite and Postgres comparedNot yet, and we do not pretend otherwise anywhere in the docs. There is no search index builder, and the Convex migration tool reports search usage as unsupported rather than translating it badly.
If your app needs search today, put an external service next to it.
The rest of what is not builtYes, with one command. concile migrate export pulls a full point-in-time dump from a running deployment, and import pushes it into a fresh one. The same tool works between any two hosts.
Your functions move too. The same code runs on dev, Docker, a binary, a fleet or Cloudflare without edits.
How portability worksWrite one query. Watch it stay live.
The quickstart takes you from an empty folder to a live app in a few minutes. It runs on your machine today, and on your own server tomorrow.