Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cryguy/hashboard/llms.txt

Use this file to discover all available pages before exploring further.

pm2 is the recommended supervision method for bare-metal or VM deployments where Docker is not used. It restarts Hashboard on crash, persists the process list across reboots, and reads configuration from ecosystem.config.cjs checked into the repository. Migrations are applied automatically at boot — there is no separate migration step.
Never set exec_mode: 'cluster' or instances greater than 1. Hashboard uses better-sqlite3 in WAL mode with a single writer. Running two processes against the same database file causes write contention and will corrupt it. The app is designed as a single process — the pm2 config enforces this and the comment in ecosystem.config.cjs explains why.

Deployment

1

Install Node.js and pm2

Hashboard requires Node.js 24 or later. Install pm2 globally and run the startup hook once so pm2 survives reboots:
npm install -g pm2
pm2 startup   # follow the printed instructions to enable the init hook
pm2 startup prints a command to run as root (or with sudo) that registers pm2 with your init system. Run that command before continuing.
2

Clone the repository and configure .env

git clone https://github.com/cryguy/hashboard.git
cd hashboard
cp .env.example .env
Edit .env and set the values for your deployment. At minimum, set ORIGIN to your public URL and uncomment ADDRESS_HEADER and XFF_DEPTH if you are behind a reverse proxy:
ORIGIN=https://hashboard.example.com
ADDRESS_HEADER=X-Forwarded-For
XFF_DEPTH=1
See .env.example for all available options.
3

Install dependencies and build

npm ci && npm run build
This produces the production bundle in build/ and prunes dev dependencies. The compiled better-sqlite3 native binding is kept in place.
4

Start under pm2

pm2 start ecosystem.config.cjs
pm2 reads ecosystem.config.cjs, starts build/index.js with --env-file=.env, and begins supervising the process. Migrations are applied on first boot and on every subsequent restart where new migration files are present.
5

Save the process list

pm2 save
This writes the current process list to pm2’s dump file so it is restored automatically after a reboot. Run pm2 save again whenever you add or change processes.

ecosystem.config.cjs reference

The ecosystem.config.cjs file checked into the repository is the authoritative pm2 configuration for Hashboard:
// pm2 process file. Start with: pm2 start ecosystem.config.cjs
//
// NEVER set exec_mode: 'cluster' or instances > 1. The app is designed as a
// single process: better-sqlite3 holds one WAL database with one writer, and
// the service layer's transaction guarantees assume no second process.
module.exports = {
	apps: [
		{
			name: 'hashboard',
			script: 'build/index.js',
			node_args: '--env-file=.env',
			// cwd pins the working directory so a relative DATABASE_URL in .env
			// and the healthcheck both resolve against the repo root.
			cwd: __dirname,
			max_restarts: 10,
			restart_delay: 1000
		}
	]
};
cwd: __dirname ensures that a relative DATABASE_URL in .env (such as local.db) resolves to the repository root, not wherever pm2 was invoked from. Do not change this to an absolute path unless you also update DATABASE_URL.
Health check endpoint: GET /api/v1/health is unauthenticated by design and always returns 200 OK when the process is up. Use it as the target for uptime monitors, load balancer health checks, or any external watchdog. It does not require a session cookie or bearer token.

Deploying an update

A production deploy consists of the following files — copy them to the target and run npm ci --omit=dev there. Migrations apply automatically when the process restarts.
# Files that must be present on the target
build/
package.json
package-lock.json
drizzle/
scripts/migrate.mjs

# Then on the target:
npm ci --omit=dev
pm2 restart hashboard
The drizzle/ directory contains the committed SQL migration files. The runtime migrator reads them at boot, so the production install carries its own migrations without needing dev dependencies like drizzle-kit.

Monitoring and logs

pm2 status              # process list and restart counts
pm2 logs hashboard      # tail stdout + stderr
pm2 monit               # live dashboard
After pm2 save, the process is automatically restarted by the init system on reboot. If the process crashes more than max_restarts times in a short window (default: 10), pm2 stops restarting it and marks the process as errored — check pm2 logs hashboard to diagnose the cause before restarting manually with pm2 restart hashboard.

Build docs developers (and LLMs) love