Ir al contenido

Deploying the panel

Esta página aún no está disponible en tu idioma.

The enterprise panel (cloud/api, serving the dashboard in cloud/dashboard) is a small, self-hostable Node service. It is built on Node built-ins — node:http, node:crypto (Ed25519) and node:sqlite — with one optional runtime dependency: the pg driver, loaded only when Postgres is selected.

  • Node ≥ 22.5 (developed on 26). The default SQLite mode runs and tests with just node, no install step.
  • pnpm install (for the pg driver) only if you use Postgres locally; the Docker image installs it.
Terminal window
just panel-run # or: cd cloud/api && node src/index.ts

On boot the panel loads (or generates) its Ed25519 signing key, seeds the default catalog, and prints the two values daemons need to connect:

CHRONOS_PANEL_URL=http://127.0.0.1:8787
CHRONOS_PANEL_PUBKEY=<64-hex public key>

One storage interface, two backends:

  • SQLite (default) — zero dependencies, single-tenant. The database file lives at PANEL_DB (defaults to panel.db).
  • Postgres / Neon — set DATABASE_URL (for Neon, the connection string with ?sslmode=require); it takes precedence over PANEL_DB. The schema is created on boot, and the signing key persists in the database, so daemons keep verifying across restarts and redeploys.

Signing key persistence — read this before production

Section titled “Signing key persistence — read this before production”

The panel signs every catalog, context and pack bundle with its Ed25519 private key, and every daemon pins the corresponding public key at login. If the key is regenerated — for example, a redeploy without persistent storage — every daemon that pinned the old public key stops trusting the panel and must reconnect.

So the key must survive redeploys:

  • In production, provide it via PANEL_SIGNING_KEY from a KMS or secret manager. Never commit it, never store it in cleartext.
  • With Postgres, the key persists in the database automatically.
  • With SQLite, put PANEL_DB on a mounted volume (the Docker image defaults to /app/data/panel.db for exactly this reason).

Planned rotation is different from key loss: rotating from the dashboard’s Keys tab keeps previous keys in the trusted-key set, so live daemons keep verifying.

Set PANEL_ADMIN_PASSWORD (and optionally PANEL_ADMIN_IDENTITY, default admin@corp). At boot the panel creates — or password-resets — the admin account, so a fresh deployment is administrable without touching the database. From there, create real users in the dashboard (one-time passwords, scrypt-hashed) — see the Administrator guide.

PANEL_SEED_PASSWORD gives the demo accounts credentials for local development and tests only; without it they cannot sign in.

VariablePurpose
PANEL_DBSQLite database path (default panel.db)
DATABASE_URLPostgres/Neon connection string; wins over PANEL_DB
PANEL_SIGNING_KEYEd25519 signing key (production: from a KMS/secret manager)
PANEL_ADMIN_PASSWORDbootstraps/resets the admin account at boot
PANEL_ADMIN_IDENTITYadmin identity (default admin@corp)
PANEL_SEED_PASSWORDdev/test only: credentials for the demo accounts

cloud/Dockerfile builds a production image on node:24-slim. The build context is cloud/ so the API can serve its sibling dashboard as static files. It installs runtime dependencies only (pg), listens on port 8787, and sets PANEL_DB=/app/data/panel.db — mount a volume at /app/data so the signing key and catalog survive redeploys.

Terminal window
cd cloud
docker build -t chronos-panel .
docker run -p 8787:8787 \
-v panel-data:/app/data \
-e PANEL_ADMIN_PASSWORD=<bootstrapPassword> \
chronos-panel

The panel deploys to Railway from the same Dockerfile (build context cloud/). Two points matter:

  • Persistence. Either attach a Railway volume at /app/data (SQLite), or set DATABASE_URL to a Postgres/Neon instance — with Postgres the signing key persists in the database, so redeploys are safe by default.
  • Variables. Set PANEL_ADMIN_PASSWORD (and PANEL_SIGNING_KEY if you manage the key externally) as service variables.

Developers normally connect with chronos login (device flow; the bearer lives in the OS keychain and the panel keys are pinned at login). For CI or ops, environment variables take precedence:

Terminal window
export CHRONOS_PANEL_URL=https://<yourPanelHost>
export CHRONOS_PANEL_PUBKEY=<hexPublicKey> # the daemon pins the PUBLIC key only
export CHRONOS_IDENTITY=<userEmail>
export CHRONOS_TEAMS=<teamA>,<teamB>
just run-daemon

With CHRONOS_PANEL_URL unset the daemon runs offline with no governed MCPs — CI never needs the panel.

Terminal window
just panel-test # node --test (SQLite suite; the pg suite self-skips)
TEST_DATABASE_URL=postgres://<connectionString> just panel-test # also runs the Postgres suite

The Postgres suite drops the panel tables — point TEST_DATABASE_URL at throwaway databases only.