Operations

Self-hosting Backenly

The repository is the whole platform — runtime, governance, and the complete self-healing engine, not a stripped community edition. The platform is Apache-2.0 and the client libraries are MIT. This is what running it involves, including the parts that are genuinely your problem afterwards.

Section
How it works
Reading time
4 min
Last updated
August 29, 2026

What runs

ProcessPortServes
Next.js3000The dashboard and the platform APIs
Express runtime3001The public end-user API at /api/v1/*, realtime, presence, broadcast

Both sit behind a reverse proxy that sends `/api/v1/*` to the runtime and everything else to Next. They share one PostgreSQL instance. In development `npm run dev` starts both together; in production `ecosystem.config.js` runs them under PM2 as `backenly-nextjs` and `backenly-runtime`.

First run

From a clean checkout
git clone https://github.com/backenly/backenly.git
cd backenly
npm install

cp .env.example .env      # then set DATABASE_URL, JWT_SECRET, OPENAI_API_KEY

# PostgreSQL + Redis, matching the defaults already in .env.example
docker compose -f docker-compose.dev.yml up -d

npm run db:generate && npm run db:push
npm run db:seed           # seeds the billing plans

npm run dev               # dashboard :3000 · runtime :3001

Node 20 is what the Dockerfile and CI build against. Two variables are not optional: `JWT_SECRET` signs every platform session — generate one per deployment with `openssl rand -hex 32` — and `OPENAI_API_KEY` powers planning. The autonomy loop runs no model, so it does not consume that key; only planning and generation do.

The PostgreSQL setting that changes what you can detect

`pg_stat_statements` is the only source of measured query latency in the platform. Without it, Backenly can find missing indexes by shape — this column is a foreign key — but not by measurement, where Postgres is actually spending time filtering. It needs `shared_preload_libraries`, which needs a server restart, which is why it is set at the server rather than in a migration.

postgresql.conf, then restart
shared_preload_libraries = 'pg_stat_statements'
Then, per database
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

`docker-compose.dev.yml` preloads the library and `docker/postgres-init/` creates the extension for you on a fresh data directory. On a volume that already exists, run the statements by hand — extensions are per-database, not per-cluster.

On a database without it, the measured slow-query invariant reports UNCHECKED rather than satisfied. That distinction is deliberate: an empty result from a probe whose data source does not exist is indistinguishable from a healthy backend, and treating the two the same is how a detector once read green while it was dead.

Deploying an update

`scripts/deploy.sh` is the single entry point. It pulls, syncs the Prisma schema when `prisma/schema.prisma` changed in the pulled range, builds, restarts only after the post-build step completes, and health-checks. Prefer it over running the steps by hand — the ordering constraints are the part people get wrong.

  • Never restart before `npm run build` has finished, including `postbuild`. That step copies static assets into the standalone output; restarting early serves a build with no CSS or JavaScript.
  • Run `npm run db:generate` after any `schema.prisma` change, before the build. A stale Prisma client fails at runtime, not at build time.
  • Pass `--update-env` when restarting under PM2 if `.env` changed, or the process keeps its old environment.

The build itself carries gates before `next build` runs: a v1 parity check and an assertion that nothing writes API-definition rows. They fail the build rather than shipping a divergence, so a red build here is usually telling you something true.

What you take on

Backenly does

  • Ships the complete engine — governance, verification, and the autonomy loop — under Apache-2.0.
  • Keeps clients MIT, so the SDK, CLI, and MCP server impose nothing on what embeds them.
  • Reports a probe it cannot run as UNCHECKED rather than passing.
  • Moves data in either direction between self-hosted and Cloud with pg_dump.

You own

  • The servers, the PostgreSQL instance, backups, and TLS.
  • Your own OpenAI key, and the cost of planning and generation on it.
  • Upgrades, and the schema sync that goes with them.
  • Reverse-proxy configuration, and keeping the two processes supervised so a crash restarts.

Supervision is worth calling out specifically. The runtime process serves every end-user API call, so if it dies unsupervised, every `/api/v1/*` request for every project fails until someone notices. Run it under something that restarts it — PM2 with `autorestart`, or a systemd unit with `Restart=always`.

Backenly Cloud runs this same codebase and takes the infrastructure, backups, upgrades, and the planning tokens. Self-hosting is free and complete; the trade is operational work, not features.

Contributing

Pull requests are open. Two things are worth knowing before you write code: tests run against a real PostgreSQL instance and the database is never mocked, because mocking it has caused production incidents here before. And every schema mutation goes through the governed kernel — a patch that writes DDL around it will be sent back regardless of how correct the SQL is.

Before opening a PR
npm run lint
npx tsc --noEmit
npm test
npx tsx scripts/preflight-oss.ts --tree   # no credentials in what you committed

In short

Two Node processes, one PostgreSQL, and a reverse proxy in front. Preload `pg_stat_statements` if you want the measured detectors rather than only the structural ones, use the deploy script so the build finishes before the restart, and supervise the runtime process. Everything the hosted product runs is in the repository.

Adarsh Chiriyamkandath Jose

Founder, Backenly · Updated August 29, 2026

Try it on a live project

One free project, no credit card. Connect your agent over MCP and read the verification evidence yourself.

Create a project