What runs
| Process | Port | Serves |
|---|---|---|
| Next.js | 3000 | The dashboard and the platform APIs |
| Express runtime | 3001 | The 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
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 :3001Node 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.
shared_preload_libraries = 'pg_stat_statements'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.
npm run lint
npx tsc --noEmit
npm test
npx tsx scripts/preflight-oss.ts --tree # no credentials in what you committedIn 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