concile

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.

npm i concile && npx concile dev

There is no Concile cloud. We built this exclusively for you to self-host.

Works with the stack you already use
TypeScriptReactNode.jsBunPostgreSQLSQLiteDockerCloudflareMinIOViteNext.js
Just TypeScript

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
  1. 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.ts
    export default defineSchema({
      messages: defineTable({
        body: v.string(),
        done: v.boolean(),
      }).index("by_done", ["done"]),
    });
  2. 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.ts
    export const add = mutation({
      args: { body: v.string() },
      handler: (ctx, args) =>
        ctx.db.insert("messages", {
          body: args.body,
          done: false,
        }),
    });
  3. 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.ts
    export const list = query({
      args: {},
      handler: (ctx) =>
        ctx.db.query("messages", "by_creation").collect(),
    });
  4. 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.tsx
    const messages = useQuery(api.messages.list);
    // the component re-renders on every commit
  5. 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
The Dashboard

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.

Live queries, not change feeds

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 reactivity
Everything in the box

One 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.

reactivity

Queries that stay live

messages.count()1,284rows, live
scale

Thousands of live subscribers

2,000 online · 21 KB each
scheduler

Cron and durable jobs

cron · retries · runs at 00:00
auth

Sign-in, built in

you@example.com••••••••••Sign in
storage

Files on disk or S3

▾ uploads/
avatar.png
report.pdf
▾ backups/
2026-09-14.db
dashboard

A live data browser

RunJobStatus
#1041deployqueued
#1040builddone
#1039seeddone
Batteries included. You pick which ones to use.

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.

concile.config.ts
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.

Why this one

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.

offline first

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 works
client.ts
import {
  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
bring your own database

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.

Write a storage adapter
  • SQLiteembedded, zero setup
  • Postgresone flag, no code change
  • Durable Object SQLiteCloudflare
  • Cloudflare D1relational
  • Yoursone interface to implement
no migrations

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.

Schema and tables
concile/schema.ts
messages: defineTable({
  body: v.string(),
  author: v.string(), // new
}),

Deploy it. There is no second step, on either database.

deploy anywhere

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.

Deploy and build
  • servelive hot-swap
  • dockercompose up
  • cloudflareDurable Objects
  • railwayrailway up
  • flyfly deploy
  • awsApp Runner
one binary that scales

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 fleet
every node, identical
concile serve --fleet \
  --database-url $PG \
  --advertise-url $SELF
  • node-1writer, self-elected
  • node-2reads, local replica
  • node-3reads, local replica
The honest version

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.

How Concile compares to other backend-as-a-service tools
CapabilityConcilethis oneConvexSupabaseFirebasePocketBase
Live results from your own server codeYesRead-set preciseYesCached, subscribable queriesPartlyRow change eventsPartlyDocument listenersPartlyRecord events
Backend is plain TypeScript functionsYesQuery, mutation, actionYesQuery, mutation, actionPartlyEdge functions, off to the sidePartlyCloud FunctionsPartlyGo or JS hooks
Authorization is ordinary code, not a rules languageYesFunctions you can unit testYesOrdinary TypeScript functionsNoRow-level security in SQLNoSecurity rules DSLNoAPI rule expressions
Self-host the whole productYesOne process, one commandPartlyOpen-source backend, cloud is the productPartlySeveral composed servicesNoGoogle cloud onlyYesOne binary
Ships as a single binary with no database to runYesconcile buildNoNoPostgres requiredNoYesSQLite embedded
Swap the database without touching app codeYesSQLite or Postgres, one flagNoFixed storeNoPostgres onlyNoFirestore or Realtime DatabaseNoSQLite only
Durable workflows with rollbackYesSaga compensationPartlyWorkflow componentNoBring your ownNoBring your ownNoBring your own
Offline writes that survive a reloadYesDurable outbox, exactly oncePartlyOptimistic updates onlyNoYesFirestore persistenceNo
Full-text and vector searchNoNot built yetYesBoth, built inYestsvector and pgvectorPartlyVector only, text needs an add-onPartlyFilter 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.

No magic

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.

Escape hatches

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.
Honest limits

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.
0live subscribers on one core
0 msmedian hot-push latency
0.0 msreactive propagation, p50
0 %CPU at that load
Run the benchmark yourself
before you ask

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 answer

Because 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 works

The 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 Convex

Yes, 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.

Read the license terms

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 compared

Not 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 built

Yes, 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 works
Get started

Write 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.