Development setup
Running the whole platform on your own machine, and the two values you have to supply.
Prerequisites
| Tool | Version | Notes |
|---|---|---|
| Node.js | >=22 | Declared in the root engines.node. |
| pnpm | ^11 | Pinned via devEngines.packageManager — it downloads itself if missing. |
| PostgreSQL | >=16 | Reached through DATABASE_URL. No test command needs one. |
| Redis | >=7 | For BullMQ and the live metric cache. Set maxmemory-policy noeviction. |
| Docker | Any recent, with the Compose v2 plugin | Required to deploy anything. Without it, container panels report "unreachable", deployments fail at the build step, and everything else still works. |
Install
pnpm install
cp apps/backend/.env.example apps/backend/.env
cp apps/dashboard/.env.example apps/dashboard/.envSupply the two values with no safe default in apps/backend/.env:
DATABASE_URL=postgresql://…
AUTH_SECRET=$(openssl rand -hex 32)Every other variable ships with a working default. Only .env.example is ever committed.
To store environment variables for deployed
applications, generate a master key as well and point SECRET_ENCRYPTION_KEY_FILE at it.
Create the schema
pnpm --filter @sthora/backend db:deployRun
| App | Command | URL |
|---|---|---|
| API | pnpm --filter @sthora/backend dev | http://localhost:4600 — health at /health, OpenAPI at /docs |
| Email previews | pnpm --filter @sthora/backend email:dev | http://localhost:4601 |
| Dashboard | pnpm --filter @sthora/dashboard dev | http://localhost:3600 |
| Marketing site | pnpm --filter @sthora/website dev | http://localhost:3601 |
| This site | pnpm --filter @sthora/docs dev | http://localhost:3602 |
Everything under apps/ | pnpm dev | In parallel |
Production builds:
pnpm --filter @sthora/backend build && pnpm --filter @sthora/backend start
pnpm --filter @sthora/dashboard build && pnpm --filter @sthora/dashboard startLocal development is split-origin
The dashboard runs on 3600 and the API on 4600, so the API needs both:
CORS_ALLOWED_ORIGINS=http://localhost:3600
AUTH_TRUSTED_ORIGINS=http://localhost:3600A self-hosted install serves both on one origin and leaves the first empty.
Writable paths
By default the pipeline writes to /var/lib/sthora on the managed server and stages
uploads in /var/lib/sthora/uploads on the control plane. On a development machine point
DEPLOY_DATA_ROOT and DEPLOY_UPLOAD_STAGING_PATH somewhere writable instead.
Database commands
pnpm --filter @sthora/backend db:generate # regenerate the Prisma client
pnpm --filter @sthora/backend db:migrate # create and apply a migration
pnpm --filter @sthora/backend db:deploy # apply committed migrations
pnpm --filter @sthora/backend db:studio # browse the data
pnpm --filter @sthora/backend db:validate # check the schemaprisma generate also runs on install, so a fresh clone builds without a manual step.
Prisma 7 note
The connection URL lives in apps/backend/prisma.config.ts only. Prisma 7
removed url from the schema's datasource block, and the running app passes
a @prisma/adapter-pg adapter to the client instead.
Editing these docs
Pages are MDX under apps/docs/content/docs/. Adding a file adds a page — the sidebar, the
search index, the sitemap, and llms.txt are all generated from the same tree. Order a folder
with its meta.json.
Every page needs a title and a description in its frontmatter. A test asserts it, because
a missing description is invisible until a search result renders empty.